mirror of
https://gitee.com/dromara/sa-token.git
synced 2025-04-05 17:37:53 +08:00
v1.27.1 新增jwt集成插件
This commit is contained in:
parent
4a91553a77
commit
6d26761fd5
sa-token-core/src/main/java/cn/dev33/satoken
sa-token-demo/sa-token-demo-jwt
sa-token-doc
sa-token-plugin
sa-token-starter/sa-token-spring-boot-starter/src/test/java/com/pj/test
@ -63,7 +63,7 @@ public class SaTokenConfig implements Serializable {
|
||||
private Boolean isLog = false;
|
||||
|
||||
/**
|
||||
* jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
|
||||
* jwt秘钥 (只有集成 jwt 模块时此参数才会生效)
|
||||
*/
|
||||
private String jwtSecretKey;
|
||||
|
||||
@ -337,14 +337,14 @@ public class SaTokenConfig implements Serializable {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
|
||||
* @return jwt秘钥 (只有集成 jwt 模块时此参数才会生效)
|
||||
*/
|
||||
public String getJwtSecretKey() {
|
||||
return jwtSecretKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param jwtSecretKey jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
|
||||
* @param jwtSecretKey jwt秘钥 (只有集成 jwt 模块时此参数才会生效)
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaTokenConfig setJwtSecretKey(String jwtSecretKey) {
|
||||
|
@ -3,6 +3,8 @@ package cn.dev33.satoken.exception;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* 一个异常:代表会话未能通过登录认证
|
||||
* @author kong
|
||||
@ -24,23 +26,23 @@ public class NotLoginException extends SaTokenException {
|
||||
|
||||
/** 表示未提供token */
|
||||
public static final String NOT_TOKEN = "-1";
|
||||
public static final String NOT_TOKEN_MESSAGE = "未提供token";
|
||||
public static final String NOT_TOKEN_MESSAGE = "未提供Token";
|
||||
|
||||
/** 表示token无效 */
|
||||
public static final String INVALID_TOKEN = "-2";
|
||||
public static final String INVALID_TOKEN_MESSAGE = "token无效";
|
||||
public static final String INVALID_TOKEN_MESSAGE = "Token无效";
|
||||
|
||||
/** 表示token已过期 */
|
||||
public static final String TOKEN_TIMEOUT = "-3";
|
||||
public static final String TOKEN_TIMEOUT_MESSAGE = "token已过期";
|
||||
public static final String TOKEN_TIMEOUT_MESSAGE = "Token已过期";
|
||||
|
||||
/** 表示token已被顶下线 */
|
||||
public static final String BE_REPLACED = "-4";
|
||||
public static final String BE_REPLACED_MESSAGE = "token已被顶下线";
|
||||
public static final String BE_REPLACED_MESSAGE = "Token已被顶下线";
|
||||
|
||||
/** 表示token已被踢下线 */
|
||||
public static final String KICK_OUT = "-5";
|
||||
public static final String KICK_OUT_MESSAGE = "token已被踢下线";
|
||||
public static final String KICK_OUT_MESSAGE = "Token已被踢下线";
|
||||
|
||||
/** 默认的提示语 */
|
||||
public static final String DEFAULT_MESSAGE = "当前会话未登录";
|
||||
@ -99,6 +101,17 @@ public class NotLoginException extends SaTokenException {
|
||||
* @return 构建完毕的异常对象
|
||||
*/
|
||||
public static NotLoginException newInstance(String loginType, String type) {
|
||||
return newInstance(loginType, type, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 静态方法构建一个NotLoginException
|
||||
* @param loginType 账号类型
|
||||
* @param type 账号类型
|
||||
* @param token 引起异常的Token值
|
||||
* @return 构建完毕的异常对象
|
||||
*/
|
||||
public static NotLoginException newInstance(String loginType, String type, String token) {
|
||||
String message = null;
|
||||
if(NOT_TOKEN.equals(type)) {
|
||||
message = NOT_TOKEN_MESSAGE;
|
||||
@ -118,6 +131,9 @@ public class NotLoginException extends SaTokenException {
|
||||
else {
|
||||
message = DEFAULT_MESSAGE;
|
||||
}
|
||||
if(SaFoxUtil.isEmpty(token) == false) {
|
||||
message = message + ":" + token;
|
||||
}
|
||||
return new NotLoginException(message, loginType, type);
|
||||
}
|
||||
|
||||
|
@ -504,19 +504,19 @@ public class StpLogic {
|
||||
// 查找此token对应loginId, 如果找不到则抛出:无效token
|
||||
String loginId = getLoginIdNotHandle(tokenValue);
|
||||
if(loginId == null) {
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.INVALID_TOKEN);
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.INVALID_TOKEN, tokenValue);
|
||||
}
|
||||
// 如果是已经过期,则抛出已经过期
|
||||
if(loginId.equals(NotLoginException.TOKEN_TIMEOUT)) {
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT);
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT, tokenValue);
|
||||
}
|
||||
// 如果是已经被顶替下去了, 则抛出:已被顶下线
|
||||
if(loginId.equals(NotLoginException.BE_REPLACED)) {
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.BE_REPLACED);
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.BE_REPLACED, tokenValue);
|
||||
}
|
||||
// 如果是已经被踢下线了, 则抛出:已被踢下线
|
||||
if(loginId.equals(NotLoginException.KICK_OUT)) {
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.KICK_OUT);
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.KICK_OUT, tokenValue);
|
||||
}
|
||||
// 检查是否已经 [临时过期]
|
||||
checkActivityTimeout(tokenValue);
|
||||
@ -623,7 +623,7 @@ public class StpLogic {
|
||||
* @return 账号id
|
||||
*/
|
||||
public String getLoginIdNotHandle(String tokenValue) {
|
||||
return SaManager.getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
|
||||
return getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
|
||||
}
|
||||
|
||||
// ---- 其它操作
|
||||
@ -640,7 +640,7 @@ public class StpLogic {
|
||||
* @param tokenValue token值
|
||||
*/
|
||||
public void deleteTokenToIdMapping(String tokenValue) {
|
||||
SaManager.getSaTokenDao().delete(splicingKeyTokenValue(tokenValue));
|
||||
getSaTokenDao().delete(splicingKeyTokenValue(tokenValue));
|
||||
}
|
||||
/**
|
||||
* 更改 Token 指向的 账号Id 值
|
||||
@ -649,7 +649,7 @@ public class StpLogic {
|
||||
*/
|
||||
public void updateTokenToIdMapping(String tokenValue, Object loginId) {
|
||||
SaTokenException.throwBy(SaFoxUtil.isEmpty(loginId), "LoginId 不能为空");
|
||||
SaManager.getSaTokenDao().update(splicingKeyTokenValue(tokenValue), loginId.toString());
|
||||
getSaTokenDao().update(splicingKeyTokenValue(tokenValue), loginId.toString());
|
||||
}
|
||||
/**
|
||||
* 存储 Token-Id 映射
|
||||
@ -658,7 +658,7 @@ public class StpLogic {
|
||||
* @param timeout 会话有效期 (单位: 秒)
|
||||
*/
|
||||
public void saveTokenToIdMapping(String tokenValue, Object loginId, long timeout) {
|
||||
SaManager.getSaTokenDao().set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), timeout);
|
||||
getSaTokenDao().set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), timeout);
|
||||
}
|
||||
|
||||
|
||||
@ -672,10 +672,10 @@ public class StpLogic {
|
||||
* @return Session对象
|
||||
*/
|
||||
public SaSession getSessionBySessionId(String sessionId, boolean isCreate) {
|
||||
SaSession session = SaManager.getSaTokenDao().getSession(sessionId);
|
||||
SaSession session = getSaTokenDao().getSession(sessionId);
|
||||
if(session == null && isCreate) {
|
||||
session = SaStrategy.me.createSession.apply(sessionId);
|
||||
SaManager.getSaTokenDao().setSession(session, getConfig().getTimeout());
|
||||
getSaTokenDao().setSession(session, getConfig().getTimeout());
|
||||
}
|
||||
return session;
|
||||
}
|
||||
@ -786,10 +786,10 @@ public class StpLogic {
|
||||
* @param tokenValue token值
|
||||
*/
|
||||
public void deleteTokenSession(String tokenValue) {
|
||||
SaManager.getSaTokenDao().delete(splicingKeyTokenSession(tokenValue));
|
||||
getSaTokenDao().delete(splicingKeyTokenSession(tokenValue));
|
||||
}
|
||||
|
||||
// ------------------- [临时过期] 验证相关 -------------------
|
||||
// ------------------- [临时有效期] 验证相关 -------------------
|
||||
|
||||
/**
|
||||
* 写入指定token的 [最后操作时间] 为当前时间戳
|
||||
@ -801,7 +801,7 @@ public class StpLogic {
|
||||
return;
|
||||
}
|
||||
// 将[最后操作时间]标记为当前时间戳
|
||||
SaManager.getSaTokenDao().set(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout());
|
||||
getSaTokenDao().set(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -814,7 +814,7 @@ public class StpLogic {
|
||||
return;
|
||||
}
|
||||
// 删除[最后操作时间]
|
||||
SaManager.getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue));
|
||||
getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue));
|
||||
// 清除标记
|
||||
SaHolder.getStorage().delete(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY);
|
||||
}
|
||||
@ -842,7 +842,7 @@ public class StpLogic {
|
||||
}
|
||||
// -2 代表已过期,抛出异常
|
||||
if(timeout == SaTokenDao.NOT_VALUE_EXPIRE) {
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT);
|
||||
throw NotLoginException.newInstance(loginType, NotLoginException.TOKEN_TIMEOUT, tokenValue);
|
||||
}
|
||||
// --- 至此,验证已通过
|
||||
|
||||
@ -866,7 +866,7 @@ public class StpLogic {
|
||||
if(tokenValue == null || getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) {
|
||||
return;
|
||||
}
|
||||
SaManager.getSaTokenDao().update(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()));
|
||||
getSaTokenDao().update(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -886,7 +886,7 @@ public class StpLogic {
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public long getTokenTimeout() {
|
||||
return SaManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue()));
|
||||
return getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -895,7 +895,7 @@ public class StpLogic {
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public long getTokenTimeoutByLoginId(Object loginId) {
|
||||
return SaManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValueByLoginId(loginId)));
|
||||
return getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValueByLoginId(loginId)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -912,7 +912,7 @@ public class StpLogic {
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public long getSessionTimeoutByLoginId(Object loginId) {
|
||||
return SaManager.getSaTokenDao().getSessionTimeout(splicingKeySession(loginId));
|
||||
return getSaTokenDao().getSessionTimeout(splicingKeySession(loginId));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -929,7 +929,7 @@ public class StpLogic {
|
||||
* @return token剩余有效时间
|
||||
*/
|
||||
public long getTokenSessionTimeoutByTokenValue(String tokenValue) {
|
||||
return SaManager.getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue));
|
||||
return getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -957,7 +957,7 @@ public class StpLogic {
|
||||
// ------ 开始查询
|
||||
// 获取相关数据
|
||||
String keyLastActivityTime = splicingKeyLastActivityTime(tokenValue);
|
||||
String lastActivityTimeString = SaManager.getSaTokenDao().get(keyLastActivityTime);
|
||||
String lastActivityTimeString = getSaTokenDao().get(keyLastActivityTime);
|
||||
// 查不到,返回-2
|
||||
if(lastActivityTimeString == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
@ -1299,7 +1299,7 @@ public class StpLogic {
|
||||
* @return token集合
|
||||
*/
|
||||
public List<String> searchTokenValue(String keyword, int start, int size) {
|
||||
return SaManager.getSaTokenDao().searchData(splicingKeyTokenValue(""), keyword, start, size);
|
||||
return getSaTokenDao().searchData(splicingKeyTokenValue(""), keyword, start, size);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1310,7 +1310,7 @@ public class StpLogic {
|
||||
* @return sessionId集合
|
||||
*/
|
||||
public List<String> searchSessionId(String keyword, int start, int size) {
|
||||
return SaManager.getSaTokenDao().searchData(splicingKeySession(""), keyword, start, size);
|
||||
return getSaTokenDao().searchData(splicingKeySession(""), keyword, start, size);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1321,7 +1321,7 @@ public class StpLogic {
|
||||
* @return sessionId集合
|
||||
*/
|
||||
public List<String> searchTokenSessionId(String keyword, int start, int size) {
|
||||
return SaManager.getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size);
|
||||
return getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size);
|
||||
}
|
||||
|
||||
|
||||
@ -1394,7 +1394,7 @@ public class StpLogic {
|
||||
*/
|
||||
public void disable(Object loginId, long disableTime) {
|
||||
// 标注为已被封禁
|
||||
SaManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime);
|
||||
getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime);
|
||||
|
||||
// $$ 通知监听器
|
||||
SaManager.getSaTokenListener().doDisable(loginType, loginId, disableTime);
|
||||
@ -1406,7 +1406,7 @@ public class StpLogic {
|
||||
* @return see note
|
||||
*/
|
||||
public boolean isDisable(Object loginId) {
|
||||
return SaManager.getSaTokenDao().get(splicingKeyDisable(loginId)) != null;
|
||||
return getSaTokenDao().get(splicingKeyDisable(loginId)) != null;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1415,7 +1415,7 @@ public class StpLogic {
|
||||
* @return see note
|
||||
*/
|
||||
public long getDisableTime(Object loginId) {
|
||||
return SaManager.getSaTokenDao().getTimeout(splicingKeyDisable(loginId));
|
||||
return getSaTokenDao().getTimeout(splicingKeyDisable(loginId));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1423,7 +1423,7 @@ public class StpLogic {
|
||||
* @param loginId 账号id
|
||||
*/
|
||||
public void untieDisable(Object loginId) {
|
||||
SaManager.getSaTokenDao().delete(splicingKeyDisable(loginId));
|
||||
getSaTokenDao().delete(splicingKeyDisable(loginId));
|
||||
|
||||
// $$ 通知监听器
|
||||
SaManager.getSaTokenListener().doUntieDisable(loginType, loginId);
|
||||
@ -1607,13 +1607,21 @@ public class StpLogic {
|
||||
// ------------------- Bean对象代理 -------------------
|
||||
|
||||
/**
|
||||
* 返回配置对象
|
||||
* @return 配置对象
|
||||
* 返回全局配置对象
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenConfig getConfig() {
|
||||
// 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利
|
||||
return SaManager.getConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回持久化对象
|
||||
* @return /
|
||||
*/
|
||||
public SaTokenDao getSaTokenDao() {
|
||||
return SaManager.getSaTokenDao();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:集合中是否包含指定元素(模糊匹配)
|
||||
@ -1628,7 +1636,7 @@ public class StpLogic {
|
||||
// ------------------- 历史API,兼容旧版本 -------------------
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout(id) ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 会话注销,根据账号id (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
|
||||
@ -1639,7 +1647,7 @@ public class StpLogic {
|
||||
}
|
||||
|
||||
/**
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
|
||||
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout(id) ,使用方式保持不变 </h1>
|
||||
*
|
||||
* 会话注销,根据账号id and 设备标识 (踢人下线)
|
||||
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
|
||||
|
@ -340,7 +340,7 @@ public class StpUtil {
|
||||
}
|
||||
|
||||
|
||||
// =================== [临时过期] 验证相关 ===================
|
||||
// =================== [临时有效期] 验证相关 ===================
|
||||
|
||||
/**
|
||||
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
|
||||
|
@ -26,52 +26,32 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- jwt -->
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt</artifactId>
|
||||
<version>0.9.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
|
||||
<!-- <dependency>
|
||||
<!-- Sa-Token 整合 jwt -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis</artifactId>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency> -->
|
||||
|
||||
<!-- sa-token整合redis (使用jackson序列化方式) -->
|
||||
<!-- <dependency>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合 Redis (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency> -->
|
||||
|
||||
<!-- 提供redis连接池 -->
|
||||
<!-- <dependency>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency> -->
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token整合SpringAOP实现注解鉴权 -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-aop</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency> -->
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
@ -79,12 +59,6 @@
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>javax.xml.bind</groupId>
|
||||
<artifactId>jaxb-api</artifactId>
|
||||
<!-- <version>2.3.1</version> -->
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
|
@ -10,7 +10,7 @@ public class SaTokenJwtDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenJwtDemoApplication.class, args);
|
||||
System.out.println("\n启动成功:sa-token配置如下:" + SaManager.getConfig());
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
|
||||
import cn.dev33.satoken.jwt.StpLogicJwtForStateless;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册Sa-Token 的拦截器,打开注解式鉴权功能
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册注解拦截器
|
||||
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* Sa-Token 整合 jwt
|
||||
*/
|
||||
@Bean
|
||||
public StpLogic getStpLogicJwt() {
|
||||
return new StpLogicJwtForStateless();
|
||||
}
|
||||
|
||||
}
|
@ -1,235 +0,0 @@
|
||||
package com.pj.satoken.jwt;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.context.model.SaStorage;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.session.SaSession;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.jsonwebtoken.ExpiredJwtException;
|
||||
import io.jsonwebtoken.JwtBuilder;
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.MalformedJwtException;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
|
||||
@Component
|
||||
public class SaTokenJwtUtil {
|
||||
|
||||
/**
|
||||
* 秘钥 (随便手打几个字母就好了)
|
||||
*/
|
||||
public static final String BASE64_SECURITY = "79e7c69681b8270162386e6daa53d1dd";
|
||||
|
||||
/**
|
||||
* token有效期 (单位: 秒)
|
||||
*/
|
||||
public static final long TIMEOUT = 60 * 60 * 2;
|
||||
|
||||
|
||||
public static final String LOGIN_ID_KEY = "loginId";
|
||||
|
||||
|
||||
/**
|
||||
* 根据userId生成token
|
||||
* @param loginId 账号id
|
||||
* @param base64Security 秘钥
|
||||
* @return jwt-token
|
||||
*/
|
||||
public static String createToken(Object loginId) {
|
||||
// 判断,不可使用默认秘钥
|
||||
// if(BASE64_SECURITY.equals("79e7c69681b8270162386e6daa53d1dd")) {
|
||||
// throw new SaTokenException("请更换秘钥");
|
||||
// }
|
||||
// 在这里你可以使用官方提供的claim方法构建载荷,也可以使用setPayload自定义载荷,但是两者不可一起使用
|
||||
JwtBuilder builder = Jwts.builder()
|
||||
.setHeaderParam("type", "JWT")
|
||||
.claim(LOGIN_ID_KEY, loginId)
|
||||
.setIssuedAt(new Date()) // 签发日期
|
||||
.setExpiration(new Date(System.currentTimeMillis() + 1000 * TIMEOUT)) // 有效截止日期
|
||||
.signWith(SignatureAlgorithm.HS256, BASE64_SECURITY.getBytes()); // 加密算法
|
||||
//生成JWT
|
||||
return builder.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 从一个jwt里面解析出Claims
|
||||
* @param tokenValue token值
|
||||
* @param base64Security 秘钥
|
||||
* @return Claims对象
|
||||
*/
|
||||
public static Claims getClaims(String tokenValue) {
|
||||
// System.out.println(tokenValue);
|
||||
Claims claims = Jwts.parser()
|
||||
.setSigningKey(BASE64_SECURITY.getBytes())
|
||||
.parseClaimsJws(tokenValue).getBody();
|
||||
return claims;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从一个jwt里面解析loginId
|
||||
* @param tokenValue token值
|
||||
* @param base64Security 秘钥
|
||||
* @return loginId
|
||||
*/
|
||||
public static String getLoginId(String tokenValue) {
|
||||
try {
|
||||
Object loginId = getClaims(tokenValue).get(LOGIN_ID_KEY);
|
||||
if(loginId == null) {
|
||||
return null;
|
||||
}
|
||||
return String.valueOf(loginId);
|
||||
} catch (ExpiredJwtException e) {
|
||||
// throw NotLoginException.newInstance(StpUtil.TYPE, NotLoginException.TOKEN_TIMEOUT);
|
||||
return NotLoginException.TOKEN_TIMEOUT;
|
||||
} catch (MalformedJwtException e) {
|
||||
throw NotLoginException.newInstance(StpUtil.stpLogic.loginType, NotLoginException.INVALID_TOKEN);
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
static {
|
||||
|
||||
// 判断秘钥
|
||||
if(BASE64_SECURITY.equals("79e7c69681b8270162386e6daa53d1dd")) {
|
||||
String warn = "-------------------------------------\n";
|
||||
warn += "请更换JWT秘钥,不要使用示例默认秘钥\n";
|
||||
warn += "-------------------------------------";
|
||||
System.err.println(warn);
|
||||
}
|
||||
|
||||
// 提前调用一下方法,促使其属性初始化
|
||||
StpUtil.getLoginType();
|
||||
|
||||
// 修改默认实现
|
||||
StpUtil.stpLogic = new StpLogic("login") {
|
||||
|
||||
// 重写 (随机生成一个tokenValue)
|
||||
@Override
|
||||
public String createTokenValue(Object loginId) {
|
||||
return SaTokenJwtUtil.createToken(loginId);
|
||||
}
|
||||
|
||||
// 重写 (在当前会话上登录id )
|
||||
@Override
|
||||
public void login(Object loginId, SaLoginModel loginModel) {
|
||||
// ------ 1、获取相应对象
|
||||
SaStorage storage = SaManager.getSaTokenContext().getStorage();
|
||||
SaTokenConfig config = getConfig();
|
||||
// ------ 2、生成一个token
|
||||
String tokenValue = createTokenValue(loginId);
|
||||
storage.set(splicingKeyJustCreatedSave(), tokenValue); // 将token保存到本次request里
|
||||
if(config.getIsReadCookie() == true){ // cookie注入
|
||||
SaManager.getSaTokenContext().getResponse().addCookie(getTokenName(), tokenValue, "/", config.getCookie().getDomain(), (int)config.getTimeout());
|
||||
}
|
||||
}
|
||||
|
||||
// 重写 (获取指定token对应的登录id)
|
||||
@Override
|
||||
public String getLoginIdNotHandle(String tokenValue) {
|
||||
try {
|
||||
return SaTokenJwtUtil.getLoginId(tokenValue);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// 重写 (当前会话注销登录)
|
||||
@Override
|
||||
public void logout() {
|
||||
// 如果连token都没有,那么无需执行任何操作
|
||||
String tokenValue = getTokenValue();
|
||||
if(tokenValue == null) {
|
||||
return;
|
||||
}
|
||||
// 如果打开了cookie模式,把cookie清除掉
|
||||
if(getConfig().getIsReadCookie() == true){
|
||||
SaManager.getSaTokenContext().getResponse().deleteCookie(getTokenName());
|
||||
}
|
||||
}
|
||||
|
||||
// 重写 (获取指定key的session)
|
||||
@Override
|
||||
public SaSession getSessionBySessionId(String sessionId, boolean isCreate) {
|
||||
throw new SaTokenException("jwt has not session");
|
||||
}
|
||||
|
||||
// 重写 (获取当前登录者的token剩余有效时间 (单位: 秒))
|
||||
@Override
|
||||
public long getTokenTimeout() {
|
||||
// 如果没有token
|
||||
String tokenValue = getTokenValue();
|
||||
if(tokenValue == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
// 开始取值
|
||||
Claims claims = null;
|
||||
try {
|
||||
claims = SaTokenJwtUtil.getClaims(tokenValue);
|
||||
} catch (Exception e) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
if(claims == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
Date expiration = claims.getExpiration();
|
||||
if(expiration == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
return (expiration.getTime() - System.currentTimeMillis()) / 1000;
|
||||
}
|
||||
|
||||
// 重写 (返回当前token的登录设备)
|
||||
@Override
|
||||
public String getLoginDevice() {
|
||||
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
|
||||
}
|
||||
|
||||
// 重写 (获取当前会话的token信息)
|
||||
@Override
|
||||
public SaTokenInfo getTokenInfo() {
|
||||
SaTokenInfo info = new SaTokenInfo();
|
||||
info.tokenName = getTokenName();
|
||||
info.tokenValue = getTokenValue();
|
||||
info.isLogin = isLogin();
|
||||
info.loginId = getLoginIdDefaultNull();
|
||||
info.loginType = getLoginType();
|
||||
info.tokenTimeout = getTokenTimeout();
|
||||
// info.sessionTimeout = getSessionTimeout();
|
||||
// info.tokenSessionTimeout = getTokenSessionTimeout();
|
||||
// info.tokenActivityTimeout = getTokenActivityTimeout();
|
||||
info.loginDevice = getLoginDevice();
|
||||
return info;
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
@ -1,12 +1,9 @@
|
||||
package com.pj.test;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
@ -21,15 +18,6 @@ import cn.dev33.satoken.exception.NotRoleException;
|
||||
@RestControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
|
||||
public class GlobalException {
|
||||
|
||||
// 在每个控制器之前触发的操作
|
||||
@ModelAttribute
|
||||
public void get(HttpServletRequest request) throws IOException {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
|
||||
@ -55,46 +43,5 @@ public class GlobalException {
|
||||
|
||||
// 返回给前端
|
||||
return aj;
|
||||
|
||||
// 输出到客户端
|
||||
// response.setContentType("application/json; charset=utf-8"); // http说明,我要返回JSON对象
|
||||
// response.getWriter().print(new ObjectMapper().writeValueAsString(aj));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 全局异常拦截(拦截项目中的NotLoginException异常)
|
||||
// @ExceptionHandler(NotLoginException.class)
|
||||
// public AjaxJson handlerNotLoginException(NotLoginException nle, HttpServletRequest request, HttpServletResponse response)
|
||||
// throws Exception {
|
||||
//
|
||||
// // 打印堆栈,以供调试
|
||||
// nle.printStackTrace();
|
||||
//
|
||||
// // 判断场景值,定制化异常信息
|
||||
// String message = "";
|
||||
// if(nle.getType().equals(NotLoginException.NOT_TOKEN)) {
|
||||
// message = "未提供token";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.INVALID_TOKEN)) {
|
||||
// message = "token无效";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.TOKEN_TIMEOUT)) {
|
||||
// message = "token已过期";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.BE_REPLACED)) {
|
||||
// message = "token已被顶下线";
|
||||
// }
|
||||
// else if(nle.getType().equals(NotLoginException.KICK_OUT)) {
|
||||
// message = "token已被踢下线";
|
||||
// }
|
||||
// else {
|
||||
// message = "当前会话未登录";
|
||||
// }
|
||||
//
|
||||
// // 返回给前端
|
||||
// return AjaxJson.getError(message);
|
||||
// }
|
||||
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
@ -22,8 +23,6 @@ import cn.dev33.satoken.stp.StpUtil;
|
||||
@RequestMapping("/test/")
|
||||
public class TestJwtController {
|
||||
|
||||
|
||||
|
||||
// 测试登录接口, 浏览器访问: http://localhost:8081/test/login
|
||||
@RequestMapping("login")
|
||||
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
|
||||
@ -51,7 +50,7 @@ public class TestJwtController {
|
||||
System.out.println(tokenInfo);
|
||||
return AjaxJson.getSuccessData(tokenInfo);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 测试会话session接口, 浏览器访问: http://localhost:8081/test/session
|
||||
@RequestMapping("session")
|
||||
@ -70,11 +69,10 @@ public class TestJwtController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test
|
||||
@RequestMapping("test")
|
||||
@SaCheckLogin
|
||||
public AjaxJson test() {
|
||||
System.out.println();
|
||||
System.out.println("--------------进入请求--------------");
|
||||
StpUtil.login(10001);
|
||||
System.out.println(StpUtil.getTokenInfo().getTokenValue());
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,9 @@ sa-token:
|
||||
is-share: true
|
||||
# token风格
|
||||
token-style: uuid
|
||||
# jwt秘钥
|
||||
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
|
||||
|
||||
spring:
|
||||
# redis配置
|
||||
redis:
|
||||
|
@ -59,7 +59,7 @@
|
||||
|
||||
- **插件**
|
||||
- [AOP注解鉴权](/plugin/aop-at)
|
||||
- [临时Token验证](/plugin/temp-token)
|
||||
- [临时Token认证](/plugin/temp-token)
|
||||
- [Quick-Login快速登录插件](/plugin/quick-login)
|
||||
- [Alone独立Redis插件](/plugin/alone-redis)
|
||||
- [持久层扩展](/plugin/dao-extend)
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>Sa-Token</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="sa-token是一个java权限认证框架,功能全面,上手简单,登录验证、权限验证、Session会话、踢人下线、账号封禁、集成Redis、前后台分离、分布式会话、微服务网关鉴权、单点登录、OAuth2.0、临时Token验证、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成...,有了sa-token,你所有的权限认证问题,都不再是问题">
|
||||
<meta name="description" content="sa-token是一个java权限认证框架,功能全面,上手简单,登录认证、权限认证、Session会话、踢人下线、账号封禁、集成Redis、前后台分离、分布式会话、微服务网关鉴权、单点登录、OAuth2.0、临时Token验证、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成...,有了sa-token,你所有的权限认证问题,都不再是问题">
|
||||
<meta name="keywords" content="sa-token,sa-token框架,sa-token文档,java权限认证">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="logo.png">
|
||||
|
@ -83,7 +83,7 @@ public class SaTokenConfigure {
|
||||
.addExclude("/favicon.ico")
|
||||
// 鉴权方法:每次访问进入
|
||||
.setAuth(obj -> {
|
||||
// 登录验证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
|
||||
// 登录校验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
|
||||
SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin());
|
||||
|
||||
// 权限认证 -- 不同模块, 校验不同权限
|
||||
|
@ -12,7 +12,7 @@ SaManager.getStpInterface(); // 获取权限认证对象
|
||||
SaManager.getSaTokenAction(); // 获取框架行为对象
|
||||
SaManager.getSaTokenContext(); // 获取上下文处理对象
|
||||
SaManager.getSaTokenListener(); // 获取侦听器对象
|
||||
SaManager.getSaTemp(); // 获取临时令牌验证模块对象
|
||||
SaManager.getSaTemp(); // 获取临时令牌认证模块对象
|
||||
SaManager.getStpLogic("type"); // 获取指定账号类型的StpLogic对象
|
||||
```
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# 临时Token令牌验证
|
||||
# 临时Token令牌认证
|
||||
|
||||
---
|
||||
|
||||
@ -37,7 +37,7 @@ http://xxx.com/apply?token=oEwQBnglXDoGraSJdGaLooPZnGrk
|
||||
|
||||
### 相关API
|
||||
|
||||
**[sa-token-temp临时验证模块]** 已内嵌到核心包,无需引入其它依赖即可使用
|
||||
**[sa-token-temp临时认证模块]** 已内嵌到核心包,无需引入其它依赖即可使用
|
||||
|
||||
``` java
|
||||
// 根据 value 创建一个 token
|
||||
@ -55,7 +55,7 @@ SaTempUtil.deleteToken(token);
|
||||
|
||||
|
||||
### 集成jwt
|
||||
提到 [临时Token验证],你是不是想到一个专门干这件事的框架?对,就是JWT!
|
||||
提到 [临时Token认证],你是不是想到一个专门干这件事的框架?对,就是JWT!
|
||||
|
||||
**[sa-token-temp]** 模块允许以JWT作为逻辑内核完成工作,你只需要引入以下依赖,所有上层API保持不变
|
||||
|
||||
|
@ -198,7 +198,7 @@ public class SaSsoClientApplication {
|
||||
|
||||
至此,测试完毕!
|
||||
|
||||
可以看出,除了在`Client1`端我们需要手动登录一次之外,在`Client2端`和`Client3端`都是可以无需验证,直接登录成功的。
|
||||
可以看出,除了在`Client1`端我们需要手动登录一次之外,在`Client2端`和`Client3端`都是可以无需再次认证,直接登录成功的。
|
||||
|
||||
我们可以通过 F12控制台 Netword跟踪整个过程
|
||||
|
||||
|
@ -107,6 +107,14 @@ public Object myinfo() {
|
||||
|
||||
访问测试:[http://sa-sso-client1.com:9001/sso/myinfo](http://sa-sso-client1.com:9001/sso/myinfo)
|
||||
|
||||
#### 3.3、疑问
|
||||
|
||||
群里有小伙伴提问:`SaSsoUtil.getUserinfo` 提供的参数太少,只有一个 loginId,无法满足业务需求怎么办?
|
||||
|
||||
答:SaSsoUtil.getUserinfo只是为了避免你在项目中硬编码认证中心 url 而提供的简易封装,如果这个API无法满足你的业务需求,
|
||||
你完全可以在 Server 端自定义一些接口然后从 Client 端使用 http 工具调用即可。
|
||||
|
||||
|
||||
|
||||
|
||||
### 4、无刷单点注销
|
||||
|
@ -42,7 +42,7 @@ public class SaTokenConfigure {
|
||||
.setAuth(obj -> {
|
||||
System.out.println("---------- 进入Sa-Token全局认证 -----------");
|
||||
|
||||
// 登录验证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
|
||||
// 登录认证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
|
||||
SaRouter.match("/**", "/user/doLogin", () -> StpUtil.checkLogin());
|
||||
|
||||
// 更多拦截处理方式,请参考“路由拦截式鉴权”章节
|
||||
|
@ -5,7 +5,7 @@
|
||||
有的时候,我们会在一个项目中设计两套账号体系,比如一个电商系统的 `user表` 和 `admin表`,
|
||||
在这种场景下,如果两套账号我们都使用 `StpUtil` 类的API进行登录鉴权,那么势必会发生逻辑冲突
|
||||
|
||||
在Sa-Token中,这个问题的模型叫做:多账号体系验证
|
||||
在Sa-Token中,这个问题的模型叫做:多账号体系认证
|
||||
|
||||
要解决这个问题,我们必须有一个合理的机制将这两套账号的授权给区分开,让它们互不干扰才行
|
||||
|
||||
@ -22,7 +22,7 @@
|
||||
|
||||
### 2、解决方案
|
||||
|
||||
前面几篇介绍的api调用,都是经过 StpUtil 类的各种静态方法进行授权验证,
|
||||
前面几篇介绍的api调用,都是经过 StpUtil 类的各种静态方法进行授权认证,
|
||||
而如果我们深入它的源码,[点此阅览](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java) <br/>
|
||||
就会发现,此类并没有任何代码逻辑,唯一做的事就是对成员变量`stpLogic`的各个API包装一下进行转发
|
||||
|
||||
@ -33,8 +33,8 @@
|
||||
|
||||
### 3、操作示例
|
||||
|
||||
比如说,对于原生`StpUtil`类,我们只做`admin账号`权限验证,而对于`user账号`,我们则:
|
||||
1. 新建一个新的权限验证类,比如: `StpUserUtil.java`
|
||||
比如说,对于原生`StpUtil`类,我们只做`admin账号`权限认证,而对于`user账号`,我们则:
|
||||
1. 新建一个新的权限认证类,比如: `StpUserUtil.java`
|
||||
2. 将`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java`里
|
||||
3. 更改一下其 `LoginType`, 比如:
|
||||
|
||||
|
@ -112,7 +112,8 @@ StpUtil.checkRoleOr("super-admin", "shop-admin");
|
||||
|
||||
### 拦截全局异常
|
||||
有同学要问,鉴权失败,抛出异常,然后呢?要把异常显示给用户看吗?**当然不可以!** <br>
|
||||
你可以创建一个全局异常拦截器,统一返回给前端的格式,参考:[码云:GlobalException.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java)
|
||||
你可以创建一个全局异常拦截器,统一返回给前端的格式,参考:
|
||||
[码云:GlobalException.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/current/GlobalException.java)
|
||||
|
||||
|
||||
### 权限通配符
|
||||
|
@ -2,7 +2,7 @@
|
||||
---
|
||||
|
||||
假设我们有如下需求:
|
||||
> 项目中所有接口均需要登录验证,只有'登录接口'本身对外开放
|
||||
> 项目中所有接口均需要登录认证,只有'登录接口'本身对外开放
|
||||
|
||||
我们怎么实现呢?给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。<br/>
|
||||
在这个需求中我们真正需要的是一种基于路由拦截的鉴权模式, 那么在Sa-Token怎么实现路由拦截鉴权呢?
|
||||
|
@ -4,7 +4,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<title>Sa-Token</title>
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="description" content="sa-token是一个java权限认证框架,功能全面,上手简单,登录验证、权限验证、Session会话、踢人下线、账号封禁、集成Redis、前后台分离、分布式会话、微服务网关鉴权、单点登录、OAuth2.0、临时Token验证、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成...,有了sa-token,你所有的权限认证问题,都不再是问题">
|
||||
<meta name="description" content="sa-token是一个java权限认证框架,功能全面,上手简单,登录认证、权限认证、Session会话、踢人下线、账号封禁、集成Redis、前后台分离、分布式会话、微服务网关鉴权、单点登录、OAuth2.0、临时Token验证、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成、WebFlux集成...,有了sa-token,你所有的权限认证问题,都不再是问题">
|
||||
<meta name="keywords" content="sa-token,sa-token框架,sa-token文档,java权限认证">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
|
||||
<link rel="shortcut icon" type="image/x-icon" href="doc/logo.png">
|
||||
|
@ -25,6 +25,7 @@
|
||||
<module>sa-token-quick-login</module>
|
||||
<module>sa-token-spring-aop</module>
|
||||
<module>sa-token-temp-jwt</module>
|
||||
<module>sa-token-jwt</module>
|
||||
</modules>
|
||||
|
||||
<dependencies>
|
||||
|
12
sa-token-plugin/sa-token-jwt/.gitignore
vendored
Normal file
12
sa-token-plugin/sa-token-jwt/.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.factorypath
|
||||
|
||||
.idea/
|
33
sa-token-plugin/sa-token-jwt/pom.xml
Normal file
33
sa-token-plugin/sa-token-jwt/pom.xml
Normal file
@ -0,0 +1,33 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-plugin</artifactId>
|
||||
<version>1.27.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>sa-token-jwt</name>
|
||||
<artifactId>sa-token-jwt</artifactId>
|
||||
<description>sa-token-jwt</description>
|
||||
|
||||
<dependencies>
|
||||
<!-- sa-token-core -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-core</artifactId>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency>
|
||||
<!-- hutool -->
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.7.14</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,235 @@
|
||||
package cn.dev33.satoken.jwt;
|
||||
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.jwt.JWT;
|
||||
import cn.hutool.jwt.JWTException;
|
||||
|
||||
/**
|
||||
* jwt操作工具类封装
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaJwtUtil {
|
||||
|
||||
/**
|
||||
* key:账号类型
|
||||
*/
|
||||
public static final String LOGIN_TYPE = "loginType";
|
||||
|
||||
/**
|
||||
* key:账号id
|
||||
*/
|
||||
public static final String LOGIN_ID = "loginId";
|
||||
|
||||
/**
|
||||
* key:登录设备
|
||||
*/
|
||||
public static final String DEVICE = "device";
|
||||
|
||||
/**
|
||||
* key:有效截止期 (时间戳)
|
||||
*/
|
||||
public static final String EFF = "eff";
|
||||
|
||||
/**
|
||||
* 当有效期被设为此值时,代表永不过期
|
||||
*/
|
||||
public static final long NEVER_EXPIRE = SaTokenDao.NEVER_EXPIRE;
|
||||
|
||||
|
||||
/**
|
||||
* 创建 jwt (简单方式)
|
||||
* @param loginId 账号id
|
||||
* @param keyt 秘钥
|
||||
* @return jwt-token
|
||||
*/
|
||||
public static String createToken(Object loginId, String keyt) {
|
||||
|
||||
// 秘钥不可以为空
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
|
||||
// 构建
|
||||
String token = JWT.create()
|
||||
.setPayload(LOGIN_ID, loginId)
|
||||
// 混入随机字符
|
||||
.setPayload("rn", SaFoxUtil.getRandomString(32))
|
||||
.setKey(keyt.getBytes())
|
||||
.sign();
|
||||
|
||||
// 返回
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建 jwt (全参数方式)
|
||||
* @param loginType 账号类型
|
||||
* @param loginId 账号id
|
||||
* @param device 设备标识
|
||||
* @param timeout token有效期 (单位 秒)
|
||||
* @param keyt 秘钥
|
||||
* @return jwt-token
|
||||
*/
|
||||
public static String createToken(String loginType, Object loginId, String device, long timeout, String keyt) {
|
||||
|
||||
// 秘钥不可以为空
|
||||
SaTokenException.throwByNull(keyt, "请配置jwt秘钥");
|
||||
|
||||
// 计算有效期
|
||||
long effTime = timeout;
|
||||
if(timeout != NEVER_EXPIRE) {
|
||||
effTime = timeout * 1000 + System.currentTimeMillis();
|
||||
}
|
||||
|
||||
// 构建
|
||||
String token = JWT.create()
|
||||
.setPayload(LOGIN_TYPE, loginType)
|
||||
.setPayload(LOGIN_ID, loginId)
|
||||
.setPayload(DEVICE, device)
|
||||
.setPayload(EFF, effTime)
|
||||
.setKey(keyt.getBytes())
|
||||
.sign();
|
||||
|
||||
// 返回
|
||||
return token;
|
||||
}
|
||||
|
||||
/**
|
||||
* jwt 解析(校验签名和密码)
|
||||
* @param token Jwt-Token值
|
||||
* @param keyt 秘钥
|
||||
* @return 解析后的jwt 对象
|
||||
*/
|
||||
public static JWT parseToken(String token, String keyt) {
|
||||
|
||||
// 如果token为null
|
||||
if(token == null) {
|
||||
throw NotLoginException.newInstance(null, NotLoginException.NOT_TOKEN);
|
||||
}
|
||||
|
||||
// 解析
|
||||
JWT jwt = null;
|
||||
try {
|
||||
jwt = JWT.of(token);
|
||||
} catch (JWTException e) {
|
||||
// 解析失败
|
||||
throw NotLoginException.newInstance(null, NotLoginException.INVALID_TOKEN, token);
|
||||
}
|
||||
JSONObject payloads = jwt.getPayloads();
|
||||
|
||||
// 校验 Token 签名
|
||||
boolean verify = jwt.setKey(keyt.getBytes()).verify();
|
||||
if(verify == false) {
|
||||
throw NotLoginException.newInstance(payloads.getStr(LOGIN_TYPE), NotLoginException.INVALID_TOKEN, token);
|
||||
};
|
||||
|
||||
// 校验 Token 有效期
|
||||
Long effTime = payloads.getLong(EFF, 0L);
|
||||
if(effTime != NEVER_EXPIRE) {
|
||||
if(effTime == null || effTime < System.currentTimeMillis()) {
|
||||
throw NotLoginException.newInstance(payloads.getStr(LOGIN_TYPE), NotLoginException.TOKEN_TIMEOUT, token);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回
|
||||
return jwt;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 jwt 数据载荷 (校验签名和密码)
|
||||
* @param token token值
|
||||
* @param keyt 秘钥
|
||||
* @return 载荷
|
||||
*/
|
||||
public static JSONObject getPayloads(String token, String keyt) {
|
||||
return parseToken(token, keyt).getPayloads();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 jwt 数据载荷 (不校验签名和密码)
|
||||
* @param token token值
|
||||
* @param keyt 秘钥
|
||||
* @return 载荷
|
||||
*/
|
||||
public static JSONObject getPayloadsNotCheck(String token, String keyt) {
|
||||
try {
|
||||
JWT jwt = JWT.of(token);
|
||||
JSONObject payloads = jwt.getPayloads();
|
||||
return payloads;
|
||||
} catch (JWTException e) {
|
||||
return new JSONObject();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 jwt 代表的账号id
|
||||
* @param token Token值
|
||||
* @param keyt 秘钥
|
||||
* @return 值
|
||||
*/
|
||||
public static Object getLoginId(String token, String keyt) {
|
||||
return getPayloads(token, keyt).get(LOGIN_ID);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 jwt 代表的账号id (未登录时返回null)
|
||||
* @param token Token值
|
||||
* @param keyt 秘钥
|
||||
* @return 值
|
||||
*/
|
||||
public static Object getLoginIdOrNull(String token, String keyt) {
|
||||
try {
|
||||
return getPayloads(token, keyt).get(LOGIN_ID);
|
||||
} catch (NotLoginException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 jwt 剩余有效期
|
||||
* @param token JwtToken值
|
||||
* @param keyt 秘钥
|
||||
* @return 值
|
||||
*/
|
||||
public static long getTimeout(String token, String keyt) {
|
||||
|
||||
// 如果token为null
|
||||
if(token == null) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 取出数据
|
||||
JWT jwt = null;
|
||||
try {
|
||||
jwt = JWT.of(token);
|
||||
} catch (JWTException e) {
|
||||
// 解析失败
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
JSONObject payloads = jwt.getPayloads();
|
||||
|
||||
// 如果签名无效
|
||||
boolean verify = jwt.setKey(keyt.getBytes()).verify();
|
||||
if(verify == false) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
};
|
||||
|
||||
// 如果被设置为:永不过期
|
||||
Long effTime = payloads.get(EFF, Long.class);
|
||||
if(effTime == NEVER_EXPIRE) {
|
||||
return NEVER_EXPIRE;
|
||||
}
|
||||
// 如果已经超时
|
||||
if(effTime == null || effTime < System.currentTimeMillis()) {
|
||||
return SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
}
|
||||
|
||||
// 计算timeout (转化为以秒为单位的有效时间)
|
||||
return (effTime - System.currentTimeMillis()) / 1000;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,188 @@
|
||||
package cn.dev33.satoken.jwt;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.stp.SaLoginModel;
|
||||
import cn.dev33.satoken.stp.SaTokenInfo;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token 整合 jwt -- stateless 无状态
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class StpLogicJwtForStateless extends StpLogic {
|
||||
|
||||
/**
|
||||
* 异常描述
|
||||
*/
|
||||
public static final String ERROR_MESSAGE = "This API is disabled";
|
||||
|
||||
/**
|
||||
* 初始化StpLogic, 并指定账号类型
|
||||
* @param loginType 账号体系标识
|
||||
*/
|
||||
public StpLogicJwtForStateless() {
|
||||
super(StpUtil.TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化StpLogic, 并指定账号类型
|
||||
* @param loginType 账号体系标识
|
||||
*/
|
||||
public StpLogicJwtForStateless(String loginType) {
|
||||
super(loginType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取jwt秘钥
|
||||
* @return /
|
||||
*/
|
||||
public String jwtSecretKey() {
|
||||
return getConfig().getJwtSecretKey();
|
||||
}
|
||||
|
||||
//
|
||||
// ------ 重写方法
|
||||
//
|
||||
|
||||
// ------------------- 获取token 相关 -------------------
|
||||
|
||||
/**
|
||||
* 创建一个TokenValue
|
||||
*/
|
||||
@Override
|
||||
public String createTokenValue(Object loginId) {
|
||||
return SaJwtUtil.createToken(loginId, jwtSecretKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前会话的Token信息
|
||||
* @return token信息
|
||||
*/
|
||||
@Override
|
||||
public SaTokenInfo getTokenInfo() {
|
||||
SaTokenInfo info = new SaTokenInfo();
|
||||
info.tokenName = getTokenName();
|
||||
info.tokenValue = getTokenValue();
|
||||
info.isLogin = isLogin();
|
||||
info.loginId = getLoginIdDefaultNull();
|
||||
info.loginType = getLoginType();
|
||||
info.tokenTimeout = getTokenTimeout();
|
||||
info.sessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
info.tokenSessionTimeout = SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
info.tokenActivityTimeout = SaTokenDao.NOT_VALUE_EXPIRE;
|
||||
info.loginDevice = getLoginDevice();
|
||||
return info;
|
||||
}
|
||||
|
||||
// ------------------- 登录相关操作 -------------------
|
||||
|
||||
/**
|
||||
* 会话登录,并指定所有登录参数Model
|
||||
*/
|
||||
@Override
|
||||
public void login(Object id, SaLoginModel loginModel) {
|
||||
|
||||
SaTokenException.throwByNull(id, "账号id不能为空");
|
||||
|
||||
// ------ 1、初始化 loginModel
|
||||
loginModel.build(getConfig());
|
||||
|
||||
// ------ 2、生成一个token
|
||||
String tokenValue = SaJwtUtil.createToken(
|
||||
loginType,
|
||||
id,
|
||||
loginModel.getDeviceOrDefalut(),
|
||||
loginModel.getTimeout(),
|
||||
jwtSecretKey()
|
||||
);
|
||||
|
||||
// 3、在当前会话写入tokenValue
|
||||
setTokenValue(tokenValue, loginModel.getCookieTimeout());
|
||||
|
||||
// $$ 通知监听器,账号xxx 登录成功
|
||||
SaManager.getSaTokenListener().doLogin(loginType, id, loginModel);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定Token对应的账号id (不做任何特殊处理)
|
||||
*/
|
||||
@Override
|
||||
public String getLoginIdNotHandle(String tokenValue) {
|
||||
// 先验证 loginType,如果不符,相当于null
|
||||
String loginType = SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.LOGIN_TYPE);
|
||||
if(getLoginType().equals(loginType) == false) {
|
||||
return null;
|
||||
}
|
||||
// 获取 loginId
|
||||
try {
|
||||
Object loginId = SaJwtUtil.getLoginId(tokenValue, jwtSecretKey());
|
||||
return String.valueOf(loginId);
|
||||
} catch (NotLoginException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 会话注销
|
||||
*/
|
||||
@Override
|
||||
public void logout() {
|
||||
// stateless模式下清除Cookie即可
|
||||
|
||||
// 如果打开了cookie模式,把cookie清除掉
|
||||
if(getConfig().getIsReadCookie() == true){
|
||||
SaManager.getSaTokenContext().getResponse().deleteCookie(getTokenName());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ------------------- 过期时间相关 -------------------
|
||||
|
||||
/**
|
||||
* 获取当前登录者的 token 剩余有效时间 (单位: 秒)
|
||||
*/
|
||||
@Override
|
||||
public long getTokenTimeout() {
|
||||
return SaJwtUtil.getTimeout(getTokenValue(), jwtSecretKey());
|
||||
}
|
||||
|
||||
|
||||
// ------------------- id 反查 token 相关操作 -------------------
|
||||
|
||||
/**
|
||||
* 返回当前会话的登录设备
|
||||
* @return 当前令牌的登录设备
|
||||
*/
|
||||
@Override
|
||||
public String getLoginDevice() {
|
||||
// 如果没有token,直接返回 null
|
||||
String tokenValue = getTokenValue();
|
||||
if(tokenValue == null) {
|
||||
return null;
|
||||
}
|
||||
// 如果还未登录,直接返回 null
|
||||
if(!isLogin()) {
|
||||
return null;
|
||||
}
|
||||
// 获取
|
||||
return SaJwtUtil.getPayloadsNotCheck(tokenValue, jwtSecretKey()).getStr(SaJwtUtil.DEVICE);
|
||||
}
|
||||
|
||||
|
||||
// ------------------- Bean对象代理 -------------------
|
||||
|
||||
/**
|
||||
* 返回持久化对象
|
||||
*/
|
||||
@Override
|
||||
public SaTokenDao getSaTokenDao() {
|
||||
throw new SaTokenException(ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
package cn.dev33.satoken.jwt;
|
||||
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* Sa-Token 整合 jwt -- Token风格
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class StpLogicJwtForTokenStyle extends StpLogic {
|
||||
|
||||
/**
|
||||
* 初始化StpLogic, 并指定账号类型
|
||||
* @param loginType 账号体系标识
|
||||
*/
|
||||
public StpLogicJwtForTokenStyle() {
|
||||
super(StpUtil.TYPE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化StpLogic, 并指定账号类型
|
||||
* @param loginType 账号体系标识
|
||||
*/
|
||||
public StpLogicJwtForTokenStyle(String loginType) {
|
||||
super(loginType);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取jwt秘钥
|
||||
* @return /
|
||||
*/
|
||||
public String jwtSecretKey() {
|
||||
return getConfig().getJwtSecretKey();
|
||||
}
|
||||
|
||||
// ------ 重写方法
|
||||
|
||||
/**
|
||||
* 创建一个TokenValue
|
||||
* @param loginId loginId
|
||||
* @return 生成的tokenValue
|
||||
*/
|
||||
@Override
|
||||
public String createTokenValue(Object loginId) {
|
||||
return SaJwtUtil.createToken(loginId, jwtSecretKey());
|
||||
}
|
||||
|
||||
}
|
@ -14,6 +14,7 @@ import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.session.TokenSign;
|
||||
import cn.dev33.satoken.stp.StpLogic;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
@ -160,5 +161,18 @@ public class ManyLoginTest {
|
||||
Assert.assertNull(StpUtil.getSessionByLoginId(10001, false));
|
||||
Assert.assertNull(dao.getSession("satoken:login:session:" + 10001));
|
||||
}
|
||||
|
||||
|
||||
// 测试:多账号模式,在一个账号体系里登录成功,在另一个账号体系不会校验通过
|
||||
@Test
|
||||
public void login7() {
|
||||
SaManager.setConfig(new SaTokenConfig());
|
||||
|
||||
StpUtil.login(10001);
|
||||
String token1 = StpUtil.getTokenValue();
|
||||
|
||||
StpLogic stp = new StpLogic("user");
|
||||
|
||||
Assert.assertNotNull(StpUtil.getLoginIdByToken(token1));
|
||||
Assert.assertNull(stp.getLoginIdByToken(token1));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user