Sa-Token

news2025/8/2 17:41:18

介绍

类似于Security的认证授权的解决方案,但是使用起来非常方便,1.支持登录认证,授权权限验证,踢人;2.支持自定义Token,并且能够结合Redis完成前后端分离认证方案;3.支持单点登录(前端同域,后端同Redis+前端不同域,后端不同Redis+两者都不同)

请添加图片描述

2.快速上手

1.demo所用依赖

<dependencies>

        <!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
        <dependency>
            <groupId>cn.dev33</groupId>
            <artifactId>sa-token-spring-boot-starter</artifactId>
            <version>1.33.0</version>
        </dependency>

        <!-- springboot集成redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- 提供Redis连接池 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

        <!-- swagger2-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
            <exclusions>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-annotations</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.swagger</groupId>
                    <artifactId>swagger-models</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- swagger2-UI-->
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
        <!--防止进入swagger页面报类型转换错误,排除2.9.2中的引用,手动增加1.5.21版本-->
        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.5.21</version>
        </dependency>

        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-models</artifactId>
            <version>1.5.21</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${spring-boot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>

2.登录相关测试

1.登录接口

这里是直接进行username和password比对,然后将id传入登录参数得到token返回——>我们一样可以结合数据库,通过用户名密码得到用户信息,存在就将用户唯一标识传入StpUtil.login()

// 1.测试登录,浏览器访问: http://localhost:8081/user/doLogin?username=zhang&password=123456
    @ApiOperation(value = "登录测试")
    @RequestMapping("doLogin")
    public SaResult doLogin(String username, String password) {
        // 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对 
        if ("zhang".equals(username) && "123456".equals(password)) {
            //1.这里我们可以数据库判断业务后,StpUtil.login()用户的id
            StpUtil.login(10001,false);
            SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
            return SaResult.ok().data(tokenInfo).setMsg("登录成功");
        }
        return SaResult.error("登录失败");
    }
    

在这里插入图片描述

2.查询登录状态

// 2.查询登录状态,浏览器访问: http://localhost:8081/user/isLogin
    @RequestMapping("isLogin")
    @ApiOperation(value = "登录状态查询")
    public String isLogin() {
        return "当前会话是否登录:" + StpUtil.isLogin();
    }

3.退出登录
单体架构一般就是删除session中相关信息,结合token的话就是将redis中的token删除,如果是token放在cookie中保存,就删除cookie即可

这里注销的是当前用户

  @RequestMapping("logout")
    @ApiOperation(value = "退出登录")
    public SaResult logout() {
        StpUtil.logout();
        return SaResult.ok();
    }

源码

1.我们来看下Sa-Token中注销用户的核心代码:
它这里是引入了一个Storage存储器的概念,这个存储器每个用户都会有一个类似Session——>这里是由请求进行封装的

	/** 
	 * 会话注销 
	 */
	public void logout() {
		// 如果连 Token 都没有,那么无需执行任何操作
		String tokenValue = getTokenValue();
 		if(SaFoxUtil.isEmpty(tokenValue)) {
 			return;
 		}
 		
 		// 如果打开了 Cookie 模式,则把 Cookie 清除掉
 		if(getConfig().getIsReadCookie()){
 			SaCookieConfig cookie = getConfig().getCookie();
 			SaHolder.getResponse().deleteCookie(getTokenName(), cookie.getPath(), cookie.getDomain());
		}

 		// 从当前 [Storage存储器] 里删除 Token
 		SaHolder.getStorage().delete(splicingKeyJustCreatedSave());

 		// 清除当前上下文的 [临时有效期check标记]
 	 	SaHolder.getStorage().delete(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY);

 		// 清除这个 Token 的相关信息
 		logoutByTokenValue(tokenValue);
	}
	

2.会话注销token

	/**
	 * 会话注销,根据指定 Token 
	 * 
	 * @param tokenValue 指定token
	 */
	public void logoutByTokenValue(String tokenValue) {
		// 1. 清理 token-last-activity
		clearLastActivity(tokenValue); 	
		
		// 2. 注销 Token-Session 
		deleteTokenSession(tokenValue);

		// 3. 清理 token -> id 索引
 		String loginId = getLoginIdNotHandle(tokenValue);
 		if(loginId != null) {
 			deleteTokenToIdMapping(tokenValue);
 		}

		// if. 无效 loginId 立即返回
 	 	if(isValidLoginId(loginId) == false) {
 			return;
 		}
 	 	
 	 	// $$ 发布事件:某某Token注销下线了 
 		SaTokenEventCenter.doLogout(loginType, loginId, tokenValue);
 		
		// 4. 清理User-Session上的token签名 & 尝试注销User-Session 
 	 	SaSession session = getSessionByLoginId(loginId, false);
 	 	if(session != null) {
 	 	 	session.removeTokenSign(tokenValue); 
 			session.logoutByTokenSignCountToZero();
 	 	}
	}

3.可以看出来我们的storage就是由request进行封装的,并且可以进行封装kv

    public SaStorage getStorage() {
        return new SaStorageForServlet(SpringMVCUtil.getRequest());
    }

3.查询用户权限

首先我们赋予用户所有权限

这里我们可以给每个用户调用getPermissionList(Object id,String Type)方法,给指定用户传入权限,重写StpInterface接口即可得到权限

package com.wyhtoken.quicktoken.service;

import java.util.ArrayList;
import java.util.List;

import org.springframework.stereotype.Component;

import cn.dev33.satoken.stp.StpInterface;

/**
 * 自定义权限认证接口扩展,Sa-Token 将从此实现类获取每个账号拥有的权限码 
 * 
 * @author kong
 * @since 2022-10-13
 */
@Component	// 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展 
public class StpInterfaceImpl implements StpInterface {

	/**
	 * 1.添加权限集合
	 * @param loginId:账户id
	 * @param loginType:账户类型
	 * @return
	 */
	@Override
	public List<String> getPermissionList(Object loginId, String loginType) {
		// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
		List<String> list = new ArrayList<String>();	
		list.add("101");
		list.add("user.add");
		list.add("user.update");
		list.add("user.get");
		// list.add("user.delete");
		list.add("art.*");
		return list;
	}

	/**
	 * 2.返回指定id的用户权限
	 * @param loginId
	 * @param loginType
	 * @return
	 */
	@Override
	public List<String> getRoleList(Object loginId, String loginType) {
		// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
		List<String> list = new ArrayList<String>();	
		list.add("admin");
		list.add("super-admin");
		return list;
	}

}

1.用户所有权限

 @RequestMapping("checkPermission")
    @ApiOperation(value = "用户所有权限")
    public SaResult getPermissionList() {
        List<String> permissionList = StpUtil.getPermissionList();
        return SaResult.ok().setData(permissionList).setMsg("所有权限返回成功");
    }

2.判断用户是否有某权限
这里我们可以跟上述结合起来一起用,查询用户所有权限后,判断用户是否有某权限返回状态,进行兜底服务

//1.返回boolean
StpUtil.hasPermission("user.addd");
//2.抛出异常
StpUtil.checkPermission("user.add");

3.检验用户是否有指定的所有权限

    /**
     * 4.该账号判断是否有指定的多个权限(必须全部通过)
     *
     * @return
     */
    @ApiOperation(value = "判断用户是否有所有权限")
    @RequestMapping("checkAll")
    public SaResult checkPermissionAnd() {
        StpUtil.checkPermissionAnd("user.add", "user.delete", "user.get");
        return SaResult.ok().setData("所有指定权限都有");
    }

    @ApiOperation(value = "判断用户是否有所有权限其中之一")
    @RequestMapping("checkOr")
    public SaResult checkPermissionOr(){
        StpUtil.checkPermissionOr("user.add", "user.delete", "user.get");
        return SaResult.ok().setData("所有指定权限有其中之一");
    }

检验权限的源码

你会发现调用了getPermissionList(用户的标识)方法,而这个方法是StpInterface的实现类重写的方法,可以根据用户标识得到所有权限

 	/** 
 	 * 校验:当前账号是否含有指定权限 [指定多个,只要其一验证通过即可] 
 	 * @param permissionArray 权限码数组
 	 */
 	public void checkPermissionOr(String... permissionArray){
 		Object loginId = getLoginId();
 		List<String> permissionList = getPermissionList(loginId);
 		for (String permission : permissionArray) {
 			if(hasElement(permissionList, permission)) {
 				// 有的话提前退出
 				return;		
 			}
 		}
		if(permissionArray.length > 0) {
	 		throw new NotPermissionException(permissionArray[0], this.loginType).setCode(SaErrorCode.CODE_11051);
		}

串起来所有东西就能理解了

	/**
	 * 获取:指定账号的权限码集合 
	 * @param loginId 指定账号id
	 * @return / 
	 */
	public List<String> getPermissionList(Object loginId) {
		return SaManager.getStpInterface().getPermissionList(loginId, loginType);
	}

5.踢人下线

给指定用户踢出下线

@ApiOperation(value = "踢人下线")
    @RequestMapping("Kick")
    public SaResult KickOut(Integer id){
        StpUtil.kickout(10001);
        return SaResult.ok().setMsg("踢人下线成功");
    }

6.忽略权限能够访问接口的注解(游客访问)

 // 此接口加上了 @SaIgnore 可以游客访问 
    @SaIgnore
    @RequestMapping("getList")
    public SaResult getList() {
        // ... 
        return SaResult.ok(); 
    }

3.注解测试权限

package com.wyhtoken.quicktoken.controller;

import cn.dev33.satoken.annotation.*;
import cn.dev33.satoken.util.SaResult;
import io.swagger.annotations.Api;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Api(tags = "Sa-Token接口测试")
@RestController
@RequestMapping("/api/")
public class ApiController {

    // 登录校验:只有登录之后才能进入该方法
    @SaCheckLogin
    @RequestMapping("info")
    public String info() {
        return "查询用户信息";
    }

    // 角色校验:必须具有指定角色才能进入该方法
    @SaCheckRole("super-admin")
    @RequestMapping("add1")
    public String add1() {
        return "用户增加";
    }

    // 权限校验:必须具有指定权限才能进入该方法
    @SaCheckPermission("user-add")
    @RequestMapping("add2")
    public String add2() {
        return "用户增加";
    }

    // 二级认证校验:必须二级认证之后才能进入该方法
    @SaCheckSafe()
    @RequestMapping("add3")
    public String add3() {
        return "用户增加";
    }

    // Http Basic 校验:只有通过 Basic 认证后才能进入该方法
    @SaCheckBasic(account = "sa:123456")
    @RequestMapping("add4")
    public String add4() {
        return "用户增加";
    }

    // 校验当前账号是否被封禁 comment 服务,如果已被封禁会抛出异常,无法进入方法
    @SaCheckDisable("comment")
    @RequestMapping("send")
    public String send() {
        return "查询用户信息";
    }

    // 注解式鉴权:只要具有其中一个权限即可通过校验
    @RequestMapping("atJurOr")
    @SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
    public SaResult atJurOr() {
        return SaResult.data("用户信息");
    }

    // 角色权限双重 “or校验”:具备指定权限或者指定角色即可通过校验
    @RequestMapping("userAdd")
    @SaCheckPermission(value = "user.add", orRole = "admin")
    public SaResult userAdd() {
        return SaResult.data("用户信息");
    }
}

4.全局拦截器


@Configuration
public class SaTokenConfigure implements WebMvcConfigurer {
    // 注册拦截器
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。
        registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()))
                .addPathPatterns("/**")
                .excludePathPatterns("/user/doLogin","/swagger-ui.html");
    }

    /**
     * 注册全局过滤器
     */
    @Bean
    public SaServletFilter getSaServletFilter(){
        return new SaServletFilter()
                .setAuth(obj->{
                    System.out.println("--------进入Sa-Token全局认证--------");
                    //1.登录认证 拦截所有路由,并且排除/user/doLogin用于开放登录
                    SaRouter.match("/**", "/user/doLogin", () -> StpUtil.checkLogin());
                })

                .setError(e->{
                    System.out.println("---------- 进入Sa-Token异常处理 -----------");
                    return SaResult.error(e.getMessage());
                })

                // 前置函数:在每次认证函数之前执行
                .setBeforeAuth(r -> {
                    // ---------- 设置一些安全响应头 ----------
                    SaHolder.getResponse()
                            // 服务器名称
                            .setServer("sa-server")
                            // 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
                            .setHeader("X-Frame-Options", "SAMEORIGIN")
                            // 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
                            .setHeader("X-XSS-Protection", "1; mode=block")
                            // 禁用浏览器内容嗅探
                            .setHeader("X-Content-Type-Options", "nosniff")
                    ;
                });

    }

    /**
     * 重写 Sa-Token 框架内部算法策略
     */
    @Autowired
    public void rewriteSaStrategy() {
        // 重写 Token 生成策略
        SaStrategy.me.createToken = (loginId, loginType) -> {
            return SaFoxUtil.getRandomString(60);    // 随机60位长度字符串
        };
    }

}

5.重写算法策略

@Autowired
    public void rewriteSaStrategy() {
        // 重写 Token 生成策略
        SaStrategy.me.createToken = (loginId, loginType) -> {
            return SaFoxUtil.getRandomString(60);    // 随机60位长度字符串
        };
    }

5.前后端分离

常规 Web 端鉴权方法,一般由 Cookie模式 完成,而 Cookie 有两个特性:

可由后端控制写入。
每次请求自动提交。
这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)
而在app、小程序等前后台分离场景中,一般是没有 Cookie 这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊?

我们也可以加一个验证是否登录

// 登录接口
@RequestMapping("doLogin")
public SaResult doLogin() {
    // 第1步,先登录上 
    StpUtil.login(10001);
    // 第2步,获取 Token  相关参数 
    SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
    // 第3步,返回给前端 
    return SaResult.data(tokenInfo);
}

6.记住我

Sa-Token默认StpUil.login(id,type)实现了记住我功能

实现原理:

Cookie作为浏览器提供的默认会话跟踪机制,其生命周期有两种形式,分别是:

临时Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie就会消失。
持久Cookie:有效期为一个具体的时间,在时间未到期之前,即使用户关闭了浏览器Cookie也不会消失。
利用Cookie的此特性,我们便可以轻松实现 [记住我] 模式:

勾选 [记住我] 按钮时:调用StpUtil.login(10001, true),在浏览器写入一个持久Cookie储存 Token,此时用户即使重启浏览器 Token 依然有效。
不勾选 [记住我] 按钮时:调用StpUtil.login(10001, false),在浏览器写入一个临时Cookie储存 Token,此时用户在重启浏览器后 Token 便会消失,导致会话失效。

我们可以设置token的有效时间

// 示例1:
// 指定token有效期(单位: 秒),如下所示token七天有效
StpUtil.login(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));

// ----------------------- 示例2:所有参数
// `SaLoginModel`为登录参数Model,其有诸多参数决定登录时的各种逻辑,例如:
StpUtil.login(10001, new SaLoginModel()
            .setDevice("PC")                // 此次登录的客户端设备类型, 用于[同端互斥登录]时指定此次登录的设备类型
            .setIsLastingCookie(true)        // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
            .setTimeout(60 * 60 * 24 * 7)    // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的 timeout 值)
            .setToken("xxxx-xxxx-xxxx-xxxx") // 预定此次登录的生成的Token 
            .setIsWriteHeader(false)         // 是否在登录后将 Token 写入到响应头
            );

7.同端互斥功能实现

1.介绍: 请添加图片描述
如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号。
同端互斥登录,指的就是:像腾讯QQ一样,在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线。

首先在配置文件中,将 isConcurrent 配置为false,然后调用登录等相关接口时声明设备类型即可:
StpUtil.login(用户id标识,"PC");

2.查询当前登录的设备类型

// 返回当前token的登录设备类型
StpUtil.getLoginDevice();    

3.获取指定登录设备类型的tokenValue

// 获取指定loginId指定设备类型端的tokenValue 
StpUtil.getTokenValueByLoginId(10001, "APP");    

8.账号封禁

// 封禁指定账号 
StpUtil.disable(10001, 86400); 

参数含义:

参数1:要封禁的账号id。
参数2:封禁时间,单位:秒,此为 86400秒 = 1天(此值为 -1 时,代表永久封禁)

1.我们一般采用先踢掉,再封禁的方法

// 先踢下线
StpUtil.kickout(10001); 
// 再封禁账号
StpUtil.disable(10001, 86400); 

2.然后我们再下次登录可以验证一下

// 校验指定账号是否已被封禁,如果被封禁则抛出异常 `DisableServiceException`
StpUtil.checkDisable(10001); 

// 通过校验后,再进行登录:
StpUtil.login(10001); 

9.禁用用户的某个权限

// 封禁指定用户评论能力,期限为 1天
StpUtil.disable(10001, "comment", 86400);

1、封禁评价能力:账号A 因为多次虚假好评,被限制订单评价功能。
2、封禁下单能力:账号B 因为多次薅羊毛,被限制下单功能。
3、封禁开店能力:账号C 因为店铺销售假货,被限制开店功能。

参数释义:

参数1:要封禁的账号id。
参数2:针对这个账号,要封禁的服务标识(可以是任意的自定义字符串)。
参数3:要封禁的时间,单位:秒,此为 86400秒 = 1天(此值为 -1 时,代表永久封禁)。

/*
 * 以下示例中:"comment"=评论服务标识、"place-order"=下单服务标识、"open-shop"=开店服务标识
 */

// 封禁指定用户评论能力,期限为 1天
StpUtil.disable(10001, "comment", 86400);

// 在评论接口,校验一下,会抛出异常:`DisableServiceException`,使用 e.getService() 可获取业务标识 `comment` 
StpUtil.checkDisable(10001, "comment");

// 在下单时,我们校验一下 下单能力,并不会抛出异常,因为我们没有限制其下单功能
StpUtil.checkDisable(10001, "place-order");

// 现在我们再将其下单能力封禁一下,期限为 7天 
StpUtil.disable(10001, "place-order", 86400 * 7);

// 然后在下单接口,我们添加上校验代码,此时用户便会因为下单能力被封禁而无法下单(代码抛出异常)
StpUtil.checkDisable(10001, "place-order");

// 但是此时,用户如果调用开店功能的话,还是可以通过,因为我们没有限制其开店能力 (除非我们再调用了封禁开店的代码)
StpUtil.checkDisable(10001, "open-shop");

10.密码加密

Sa-Token支持非对称和对称加密两种方式

1.对称加密:AES加密

// 定义秘钥和明文
String key = "123456";
String text = "Sa-Token 一个轻量级java权限认证框架";

// 加密 
String ciphertext = SaSecureUtil.aesEncrypt(key, text);
System.out.println("AES加密后:" + ciphertext);

// 解密 
String text2 = SaSecureUtil.aesDecrypt(key, ciphertext);
System.out.println("AES解密后:" + text2);

2.非对称加密:RSA加密

// 定义私钥和公钥 
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";

// 文本
String text = "Sa-Token 一个轻量级java权限认证框架";

// 使用公钥加密
String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
System.out.println("公钥加密后:" + ciphertext);

// 使用私钥解密
String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
System.out.println("私钥解密后:" + text2); 

11.全局侦听器

实现SaTokenListener接口即可
思路:我们可以每次下线将日志写入文件中,就可以用这个全局监听器

/**
 * 自定义侦听器的实现 
 */
@Component
public class MySaTokenListener implements SaTokenListener {

    /** 每次登录时触发 */
    @Override
    public void doLogin(String loginType, Object loginId, String tokenValue, SaLoginModel loginModel) {
        System.out.println("---------- 自定义侦听器实现 doLogin");
    }

    /** 每次注销时触发 */
    @Override
    public void doLogout(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doLogout");
    }

    /** 每次被踢下线时触发 */
    @Override
    public void doKickout(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doKickout");
    }

    /** 每次被顶下线时触发 */
    @Override
    public void doReplaced(String loginType, Object loginId, String tokenValue) {
        System.out.println("---------- 自定义侦听器实现 doReplaced");
    }

    /** 每次被封禁时触发 */
    @Override
    public void doDisable(String loginType, Object loginId, String service, int level, long disableTime) {
        System.out.println("---------- 自定义侦听器实现 doDisable");
    }

    /** 每次被解封时触发 */
    @Override
    public void doUntieDisable(String loginType, Object loginId, String service) {
        System.out.println("---------- 自定义侦听器实现 doUntieDisable");
    }

    /** 每次二级认证时触发 */
    @Override
    public void doOpenSafe(String loginType, String tokenValue, String service, long safeTime) {
        System.out.println("---------- 自定义侦听器实现 doOpenSafe");
    }

    /** 每次退出二级认证时触发 */
    @Override
    public void doCloseSafe(String loginType, String tokenValue, String service) {
        System.out.println("---------- 自定义侦听器实现 doCloseSafe");
    }

    /** 每次创建Session时触发 */
    @Override
    public void doCreateSession(String id) {
        System.out.println("---------- 自定义侦听器实现 doCreateSession");
    }

    /** 每次注销Session时触发 */
    @Override
    public void doLogoutSession(String id) {
        System.out.println("---------- 自定义侦听器实现 doLogoutSession");
    }

    /** 每次Token续期时触发 */
    @Override
    public void doRenewTimeout(String tokenValue, Object loginId, long timeout) {
        System.out.println("---------- 自定义侦听器实现 doRenewTimeout");
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/35032.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

柯桥成人英语培训机构哪家好,新陈代谢到底是什么?

新陈代谢到底是什么? Metabolism is a combination of biochemical processes that your body uses to convert food into energy. These metabolic processes include breathing, eating and digesting food, the delivery of nutrients to your cells through the blood, th…

【Linux】(四)VS Code远程开发方式-实验室服务器使用VS Code远程开发

VS code 方式系列文章一、服务器情况简介1.1服务器及用户1.2 cuda1.3 conda环境二、VS code连接使用说明2.1 下载VS code2.2 配置2.3 调试文件附录&#xff1a;VS code调试复杂配置公共数据集系列文章 &#xff08;一&#xff09;服务器初次配置及安装vncserver &#xff08;二…

磨金石教育摄影技能干货分享|古风人像拍摄要注意哪些问题

古风人像与普通人像的拍摄略有不同&#xff0c;有三个要素需要做好准备。服装、妆容、道具。 服装有不少考究&#xff0c;不同的服装有不同的风格&#xff0c;侠士、温婉、清新、可爱等等&#xff0c;今天我们要拍摄的主题风格是侠女。所以选择的是魏晋风汉服。 妆 容 妆容也是…

【无人机】基于PID控制器和A星算法实现无人机路径规划附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

生鲜电商十年:如何撞破盈利难墙?谁在竞速突围?

生鲜电商已十年。 这十年间&#xff0c;无数资本和公司前赴后继&#xff0c;试图探索出一条持续盈利路径&#xff0c;并完成商业模式的持续迭代&#xff0c;然而成功者寥寥。 这么大规模、长时间的摸索&#xff0c;在整个互联网行业都较为少见。一是因为它足够难&#xff0c;…

神经网络和深度学习-反向传播back propagation代码

反向传播back propagation代码 再简单模型中&#xff0c;按照下图的神经网络来完成模型的训练 在复杂的模型当中&#xff0c;输入&#xff0c;权重&#xff0c;隐藏层的数量都是很多的&#xff0c;例如下图中&#xff0c;输入x有5个神经元&#xff0c;第一层隐藏层h中有6个神经…

Rust China Hackathon 2022 达坦科技组空中宣讲会来啦!

Rust China Hackathon 2022 即将来袭&#xff01; 本届Hackathon的主题为「Rust For Fun」&#xff0c;分为「社区组」与「企业组」。 达坦科技作为本届Hackathon的协办方&#xff0c;赞助参与本次企业组赛道&#xff0c;将基于Xline这个开源项目&#xff0c;就Concurrent Inde…

【linux】物理磁盘挂载目录——(分区、格式化、重启自动挂载)

大家好&#xff0c;我是好学的小师弟&#xff01; 现有一全新的物理磁盘需要挂载到某个目录下&#xff0c;操作步骤如下。 目录 一、磁盘分区 二、磁盘格式化 三、磁盘挂载 四、重启自动挂载 注意点: 1.该目录要存在&#xff0c;不存在的话需手动mkdir创建 2.该目录需要为…

IMS各网元的主要功能

文章目录用户注册时&#xff1a; 手机发出一个注册消息到他所在的拜访地的P。 比如&#xff0c;他是山西太原的用户&#xff0c;他这时候到了北京&#xff0c;那么这个时候&#xff0c;他要注册到IMS网络里面的话&#xff0c;这个P-CSCF就是北京的P-CSCF&#xff0c;这个北京的…

[附源码]java毕业设计游戏账号交易平台

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【零基础入门MyBatis系列】第十一篇——动态SQL

一、概述 &#x1f4d6; 1、什么是动态SQL&#xff1f; 根据不同条件生成不同的SQL语句&#xff0c;是mybatis的一大优势。用于动态拼接SQL语句 &#x1f4d6; 2、如何实现动态SQL&#xff1f; mybatis 为我们提供了一些标签&#xff0c;在写SQL语句的时候&#xff0c;合理…

Qt | QListView、QListWidget、QTableView、QTableWidget的使用示例及区别

Qt | QListView、QListWidget、QTableView、QTableWidget的使用示例及区别 目录Qt | QListView、QListWidget、QTableView、QTableWidget的使用示例及区别1、简介2、使用示例1、QListView2、QTableView3、QListWidget4、QTableWidget1、简介 QListView、QListWidget是列表形式…

Maven安装配置

Maven安装配置 第一步 : 解压 解压 apache-maven-3.6.1.rar 既安装完成 解压缩后的目录结构如下&#xff1a; bin目录 &#xff1a; 存放的是可执行命令。mvn 命令重点关注。conf目录 &#xff1a;存放Maven的配置文件。settings.xml 配置文件后期需要修改。lib目录 &#xf…

基于DPDK(x86平台)应用性能优化实践

产生性能瓶颈有多方面的原因&#xff0c;包括硬件&#xff08;自身能力限制或BIOS设置不当&#xff09;、操作系统&#xff08;某些feature没打开&#xff09;和软件。软件方面的性能瓶颈主要是由于编码不当导致&#xff0c;常见原因有以下几种&#xff1a; 数据结构cache lin…

【多线程】读写锁ReentrantReadWriteLock源码分析

【多线程】读写锁ReentrantReadWriteLock源码分析&#xff08;一&#xff09;读写锁ReentrantReadWriteLock源码分析【1】类图结构和参数【2】写锁的获取和释放&#xff08;1&#xff09;lock方法&#xff08;2&#xff09;lockInterruptibly方法&#xff08;3&#xff09;tryL…

GPS+北斗定位借助Arduino的数值显示

GPS北斗定位借助Arduino的数值显示 一、前言二、硬件要求三、参数基础四、原理剖析五、使用方式六、程序概要七、成果展示八、数据解析九、总结一、前言 较多的导航软件在生活中层出不穷&#xff0c;但是卫星定位同样也适用于轨迹记录、经纬分析、授时系统等&#xff0c;多样的…

【杰理AC696X】外挂FLASH音乐播放及Bin文件制作

外挂FLASH音乐播放及Bin文件制作 测试SDK版本&#xff1a;《ac696n_soundbox_sdk_v1.6.0》 文章目录外挂FLASH音乐播放及Bin文件制作前言一、板级配置1.1 SPI配置1.2 FLASH配置二、FLASH音乐播放测试三、FLASH BIN文件制作1.1 FLASHPCWinHex1.2 U盘分区工具WinHex1.3 FLASHPCF…

嵌入式单片机智能药盒设计(含代码)

目录 前言 设计的内容 &#xff08;1&#xff09;显示 &#xff08;2&#xff09;定时时间与用药量的设定 &#xff08;3&#xff09;实时时间调节 &#xff08;4&#xff09;报警功能 时钟模块 蓝牙模块 系统软件设计 系统主程序 按键函数 中断服务函数 作品展示 测试药…

2023年天津农学院专升本专业课参考教材

2023年天津农学院高职升本科专业课参考教材一、人力资源管理专业 1、《人力资源管理实用教程》 (第2版)&#xff0c;吴宝华&#xff0c;北京大学出版社 2、《人力资源管理》&#xff08;第4版&#xff09;&#xff0c;刘昕&#xff0c;中国人民大学出版社 3、《人力资源管理概论…

kobject 与sysfs属性文件读写

kobject和kset的简单总结 • kobject是struct kobject类型的对象。Kobject 有一个名字和一个引用计数。kobject 也有一个父指针&#xff08;允许 kobjects 被安排到层次结构中&#xff09;&#xff0c;一个特定的类型&#xff0c;也许还有一个在 sysfs 虚拟文件系统中的表示。…