类似于Security的认证授权的解决方案,但是使用起来非常方便,1.支持登录认证,授权权限验证,踢人;2.支持自定义Token,并且能够结合Redis完成前后端分离认证方案;3.支持单点登录(前端同域,后端同Redis+前端不同域,后端不同Redis+两者都不同)
1.demo所用依赖
<dependencies>
<dependency>
<groupId>cn.dev33groupId>
<artifactId>sa-token-spring-boot-starterartifactId>
<version>1.33.0version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger2artifactId>
<version>2.9.2version>
<exclusions>
<exclusion>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
exclusion>
<exclusion>
<groupId>io.swaggergroupId>
<artifactId>swagger-modelsartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>io.springfoxgroupId>
<artifactId>springfox-swagger-uiartifactId>
<version>2.9.2version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-annotationsartifactId>
<version>1.5.21version>
dependency>
<dependency>
<groupId>io.swaggergroupId>
<artifactId>swagger-modelsartifactId>
<version>1.5.21version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
<exclusions>
<exclusion>
<groupId>org.junit.vintagegroupId>
<artifactId>junit-vintage-engineartifactId>
exclusion>
exclusions>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-dependenciesartifactId>
<version>${spring-boot.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
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());
}
首先我们赋予用户所有权限
这里我们可以给每个用户调用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);
}
给指定用户踢出下线
@ApiOperation(value = "踢人下线")
@RequestMapping("Kick")
public SaResult KickOut(Integer id){
StpUtil.kickout(10001);
return SaResult.ok().setMsg("踢人下线成功");
}
// 此接口加上了 @SaIgnore 可以游客访问
@SaIgnore
@RequestMapping("getList")
public SaResult getList() {
// ...
return SaResult.ok();
}
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("用户信息");
}
}
@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位长度字符串
};
}
}
@Autowired
public void rewriteSaStrategy() {
// 重写 Token 生成策略
SaStrategy.me.createToken = (loginId, loginType) -> {
return SaFoxUtil.getRandomString(60); // 随机60位长度字符串
};
}
常规 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);
}
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 写入到响应头
);
1.介绍:
如果你经常使用腾讯QQ,就会发现它的登录有如下特点:它可以手机电脑同时在线,但是不能在两个手机上同时登录一个账号。
同端互斥登录,指的就是:像腾讯QQ一样,在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线。
首先在配置文件中,将 isConcurrent 配置为false,然后调用登录等相关接口时声明设备类型即可:
StpUtil.login(用户id标识,"PC");
2.查询当前登录的设备类型
// 返回当前token的登录设备类型
StpUtil.getLoginDevice();
3.获取指定登录设备类型的tokenValue
// 获取指定loginId指定设备类型端的tokenValue
StpUtil.getTokenValueByLoginId(10001, "APP");
// 封禁指定账号
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);
// 封禁指定用户评论能力,期限为 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");
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);
实现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");
}
}