1
0
mirror of synced 2026-05-22 22:53:16 +00:00

Compare commits

...

38 Commits

Author SHA1 Message Date
click33 6b80afe806 v1.28.0 更新. 2021-11-06 00:14:41 +08:00
click33 d3113ed6c9 v1.28.0 更新 2021-11-06 00:08:07 +08:00
click33 8ad2e3db96 新增常见问题整理 2021-11-05 18:55:14 +08:00
click33 2b18084cbc 文档新增:Sa-Token插件开发指南 2021-11-05 17:25:07 +08:00
click33 c830ed77ca 文档 名称解释 2021-11-02 18:00:04 +08:00
click33 652e6172af 新增 Dubbo 集成插件 2021-11-01 09:52:57 +08:00
click33 a1bab9e747 修复文档部分错误 2021-10-30 00:54:01 +08:00
click33 060ed055d0 Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-10-30 00:37:23 +08:00
click33 068f838165 新增二级Context模式 2021-10-30 00:37:04 +08:00
省长 eb1ec5676c !78 修改Oauth2 凭证式缺少校验scope 问题
Merge pull request !78 from 茉莉/dev
2021-10-27 03:10:00 +00:00
moli 1bc0f51845 修改Oauth2 凭证式鉴权缺少校验scope 问题 2021-10-27 10:58:12 +08:00
省长 daff33df13 !77 update sa-token-doc/doc/use/jur-auth.md.
Merge pull request !77 from thrgo/N/A
2021-10-27 01:37:44 +00:00
潘业鑫 41231a87d1 update sa-token-doc/doc/use/jur-auth.md.
可参考代码:[码云:StpInterfaceImpl.java],链接地址错误
2021-10-26 12:44:41 +00:00
省长 9e45312924 update sa-token-doc/doc/sso/sso-h5.md. 2021-10-26 05:46:57 +00:00
省长 a8a702fe13 !76 sa-token-solon-plugin: 升级到 solon 1.5.50
Merge pull request !76 from 林西东/dev
2021-10-25 17:31:43 +00:00
noear 50624d0f47 sa-token-solon-plugin: 升级到 solon 1.5.50 2021-10-25 20:31:17 +08:00
省长 4807429bf1 !75 update sa-token-doc/doc/up/remember-me.md.
Merge pull request !75 from Admin/N/A
2021-10-25 02:20:17 +00:00
Admin 2bddb0df89 update sa-token-doc/doc/up/remember-me.md.
文档错误修改
2021-10-25 02:13:25 +00:00
click33 3929b16dfb Merge pull request #177 from ejlchina/patch-1
添加 友情链接
2021-10-22 23:06:10 +08:00
周旭 2e7c0ac590 添加 友情链接 2021-10-22 23:05:16 +08:00
click33 a38029e060 优化文档推荐公众号列表、开源大事记 2021-10-21 10:04:25 +08:00
click33 c4160e4fd3 优化文档 2021-10-21 02:43:27 +08:00
click33 4003de98c6 优化readme 2021-10-21 02:22:32 +08:00
click33 fb017d9718 优化文档 2021-10-21 02:05:10 +08:00
click33 657623f910 项目目录介绍 2021-10-21 01:29:03 +08:00
click33 7369977241 jwt插件文档 2021-10-21 01:09:31 +08:00
click33 bb5c378f48 jwt集成新增mix模式 2021-10-20 22:49:54 +08:00
click33 f9ec6e6487 v1.27.1 beta. 细节优化 2021-10-19 20:43:13 +08:00
click33 82591e397f Merge branch 'dev' of https://gitee.com/dromara/sa-token into dev 2021-10-19 20:37:36 +08:00
省长 73d4fe5071 !73 处理session持久化到redis时,LocalDateTime等java8的新时间类序列化失败的问题
Merge pull request !73 from 阿超/dev
2021-10-19 12:35:51 +00:00
省长 a3677c4afc !74 update sa-token-doc/doc/up/many-account.md.
Merge pull request !74 from AppleOfGray/N/A
2021-10-19 12:23:23 +00:00
AppleOfGray 9bffe60112 update sa-token-doc/doc/up/many-account.md.
给出不同体系不同临时过期时间的解决方案
2021-10-19 11:37:23 +00:00
VampireAchao 99dffba4eb 一行代码处理session持久化到redis时,LocalDateTime等java8的新时间类序列化失败的问题 2021-10-19 12:20:45 +08:00
click33 634a3be1aa jwt整合模块添加单元测试 2021-10-19 05:01:13 +08:00
click33 7740ab0a3f v1.27.1 beta. 2021-10-18 22:05:56 +08:00
click33 6d26761fd5 v1.27.1 新增jwt集成插件 2021-10-18 22:05:26 +08:00
省长 4a91553a77 update sa-token-doc/doc/use/jur-auth.md.
修复 GlobalException 代码示例路径问题
2021-10-13 14:59:10 +00:00
click33 30dad24302 优化readme 2021-10-12 22:35:59 +08:00
135 changed files with 3709 additions and 735 deletions
+27 -18
View File
@@ -1,7 +1,7 @@
<p align="center">
<img alt="logo" src="https://gitee.com/dromara/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.27.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.28.0</h1>
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg"></a>
@@ -24,6 +24,8 @@
- 开源不易,点个 star 鼓励一下吧!
- QQ交流群:1群 1002350610 (已满)、**2群 614714762 [点击加入](https://jq.qq.com/?_wv=1027&k=b759RZrL)**
## Sa-Token 介绍
**Sa-Token** 是一个轻量级 Java 权限认证框架,主要解决:**`登录认证`**、**`权限认证`**、**`Session会话`**、**`单点登录`**、**`OAuth2.0`**、**`微服务网关鉴权`**
@@ -66,21 +68,21 @@ StpUtil.kickout(10001);
在 Sa-Token 中,绝大多数功能都可以 **一行代码** 完成:
``` java
StpUtil.login(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.kickout(10001); // 将账号为10001的会话踢下线
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.login(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.kickout(10001); // 将账号为10001的会话踢下线
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.kickout(10001, "PC"); // 指定账号指定设备标识踢下线 (不同端不受影响)
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.kickout(10001, "PC"); // 指定账号指定设备标识踢下线 (不同端不受影响)
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
```
即使不运行测试,相信您也能意会到绝大多数 API 的用法。
@@ -125,9 +127,9 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso1) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso2-server) |
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso3-server) |
| 前端同域 + 后端同 Redis | 模式一 | 共享Cookie同步会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
| 前端不同域 + 后端 不同Redis | 模式三 | Http请求获取会话 | [文档](http://sa-token.dev33.cn/doc/index.html#/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com`、`c2.domain.com`、`c3.domain.com`
@@ -190,6 +192,8 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)
## 友情链接
- **[ OkHttps ]**[ 一个轻量级http通信框架,API设计无比优雅,支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
- **[ Bean Searcher ]**[ 轻量级关系数据库条件检索引擎,使一行代码实现复杂列表检索成为可能!](https://github.com/ejlchina/bean-searcher)
- **[ 小诺快速开发平台 ]**[ 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
- **[ Jpom ]**[ 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件](https://gitee.com/dromara/Jpom)
@@ -197,6 +201,11 @@ Sa-OAuth2 模块基于 [RFC-6749 标准](https://tools.ietf.org/html/rfc6749)
- **[ TLog ]**[ 一个轻量级的分布式日志标记追踪神器](https://gitee.com/dromara/TLog)
## 贡献者名单
感谢每一个为 Sa-Token 贡献代码的小伙伴
[![Giteye chart](https://chart.giteye.net/gitee/dromara/sa-token/CGZ7GT8E.png)](https://giteye.net/chart/CGZ7GT8E)
## 交流群
QQ交流群:1群:1002350610 (已满) 、
+17
View File
@@ -57,6 +57,23 @@ cd sa-token-demo-sso3-client
call mvn clean
cd ..
cd sa-token-demo-jwt
call mvn clean
cd ..
cd sa-token-demo-jwt
call mvn clean
cd ..
cd sa-token-demo-dubbo-provider
call mvn clean
cd ..
cd sa-token-demo-dubbo-consumer
call mvn clean
cd ..
+2 -2
View File
@@ -8,7 +8,7 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<packaging>pom</packaging>
<version>1.27.0</version>
<version>1.28.0</version>
<!-- 项目介绍 -->
<name>sa-token</name>
@@ -36,7 +36,7 @@
<!-- 一些属性 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
<jdk.version>1.8</jdk.version>
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.27.0</version>
<version>1.28.0</version>
</parent>
<packaging>jar</packaging>
@@ -9,6 +9,7 @@ import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.config.SaTokenConfigFactory;
import cn.dev33.satoken.context.SaTokenContext;
import cn.dev33.satoken.context.SaTokenContextDefaultImpl;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.dao.SaTokenDaoDefaultImpl;
import cn.dev33.satoken.exception.SaTokenException;
@@ -18,12 +19,12 @@ import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.temp.SaTempInterface;
import cn.dev33.satoken.temp.SaTempDefaultImpl;
import cn.dev33.satoken.temp.SaTempInterface;
import cn.dev33.satoken.util.SaFoxUtil;
/**
* 管理 Sa-Token 所有接口对象
* 管理 Sa-Token 所有全局组件
* @author kong
*
*/
@@ -111,21 +112,49 @@ public class SaManager {
}
/**
* 上下文 Bean
* 上下文Context Bean
*/
private volatile static SaTokenContext saTokenContext;
public static void setSaTokenContext(SaTokenContext saTokenContext) {
SaManager.saTokenContext = saTokenContext;
}
public static SaTokenContext getSaTokenContext() {
if (saTokenContext == null) {
synchronized (SaManager.class) {
if (saTokenContext == null) {
setSaTokenContext(new SaTokenContextDefaultImpl());
}
return saTokenContext;
}
/**
* 二级Context
*/
private volatile static SaTokenSecondContext saTokenSecondContext;
public static SaTokenSecondContext getSaTokenSecondContext() {
return saTokenSecondContext;
}
public static void setSaTokenSecondContext(SaTokenSecondContext saTokenSecondContext) {
SaManager.saTokenSecondContext = saTokenSecondContext;
}
/**
* 获取一个可用的SaTokenContext
* @return /
*/
public static SaTokenContext getSaTokenContextOrSecond() {
// s1. 一级Context可用时返回一级Context
if(saTokenContext != null) {
if(saTokenSecondContext == null || saTokenContext.isValid()) {
// 因为 isValid 是一个耗时操作,所以此处假定:二级Context为null的情况下无需验证一级Context有效性
// 这样可以提升6倍左右的上下文获取速度
return saTokenContext;
}
}
return saTokenContext;
// s2. 一级Context不可用时判断二级Context是否可用
if(saTokenSecondContext != null && saTokenSecondContext.isValid()) {
return saTokenSecondContext;
}
// s3. 都不行,就返回默认的 Context
return SaTokenContextDefaultImpl.defaultContext;
}
/**
@@ -45,6 +45,8 @@ public @interface SaCheckPermission {
* 例2 orRole = {"admin", "manager", "staff"},具有三个角色其一即可 <br>
* 例3 orRole = {"admin, manager, staff"},必须三个角色同时具备
* </p>
*
* @return /
*/
String[] orRole() default {};
@@ -63,7 +63,7 @@ public class SaTokenConfig implements Serializable {
private Boolean isLog = false;
/**
* jwt秘钥 (只有集成 sa-token-temp-jwt 模块时此参数才会生效)
* jwt秘钥 (只有集成 jwt 模块时此参数才会生效)
*/
private String jwtSecretKey;
@@ -80,6 +80,8 @@ public class SaTokenConfig implements Serializable {
/** 配置当前项目的网络访问地址 */
private String currDomain;
/** 是否校验Id-Token(部分rpc插件有效) */
private Boolean checkIdToken = false;
/**
* Cookie配置对象
@@ -337,14 +339,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) {
@@ -399,6 +401,22 @@ public class SaTokenConfig implements Serializable {
this.currDomain = currDomain;
return this;
}
/**
* @return 是否校验Id-Token(部分rpc插件有效)
*/
public Boolean getCheckIdToken() {
return checkIdToken;
}
/**
* @param checkIdToken 是否校验Id-Token(部分rpc插件有效)
* @return 对象自身
*/
public SaTokenConfig setCheckIdToken(Boolean checkIdToken) {
this.checkIdToken = checkIdToken;
return this;
}
/**
* @return SSO单点登录配置对象
@@ -454,6 +472,7 @@ public class SaTokenConfig implements Serializable {
+ ", idTokenTimeout=" + idTokenTimeout
+ ", basic=" + basic
+ ", currDomain=" + currDomain
+ ", checkIdToken=" + checkIdToken
+ ", sso=" + sso
+ ", cookie=" + cookie
+ "]";
@@ -11,6 +11,15 @@ import cn.dev33.satoken.context.model.SaStorage;
*
*/
public class SaHolder {
/**
* 获取当前请求的 SaTokenContext
*
* @return see note
*/
public static SaTokenContext getContext() {
return SaManager.getSaTokenContextOrSecond();
}
/**
* 获取当前请求的 [Request] 对象
@@ -18,7 +27,7 @@ public class SaHolder {
* @return see note
*/
public static SaRequest getRequest() {
return SaManager.getSaTokenContext().getRequest();
return SaManager.getSaTokenContextOrSecond().getRequest();
}
/**
@@ -27,7 +36,7 @@ public class SaHolder {
* @return see note
*/
public static SaResponse getResponse() {
return SaManager.getSaTokenContext().getResponse();
return SaManager.getSaTokenContextOrSecond().getResponse();
}
/**
@@ -36,7 +45,7 @@ public class SaHolder {
* @return see note
*/
public static SaStorage getStorage() {
return SaManager.getSaTokenContext().getStorage();
return SaManager.getSaTokenContextOrSecond().getStorage();
}
}
@@ -41,4 +41,12 @@ public interface SaTokenContext {
*/
public boolean matchPath(String pattern, String path);
/**
* 此上下文是否有效
* @return /
*/
public default boolean isValid() {
return false;
}
}
@@ -10,13 +10,18 @@ import cn.dev33.satoken.exception.SaTokenException;
*
* <p>
* 一般情况下框架会为你自动注入合适的上下文处理器,如果代码断点走到了此默认实现类,
* 说明你引入的依赖有问题或者错误的调用了Sa-Token的API, 请在[在线开发文档 > 附录 > 常见问题排查] 中按照提示进行排查
* 说明你引入的依赖有问题或者错误的调用了Sa-Token的API, 请在[在线开发文档 附录 常见问题排查] 中按照提示进行排查
* </p>
*
* @author kong
*
*/
public class SaTokenContextDefaultImpl implements SaTokenContext {
/**
* 默认的上下文处理器对象
*/
public static SaTokenContext defaultContext = new SaTokenContextDefaultImpl();
/**
* 默认的错误提示语
@@ -56,5 +61,4 @@ public class SaTokenContextDefaultImpl implements SaTokenContext {
}
}
@@ -37,4 +37,9 @@ public class SaTokenContextForThreadLocal implements SaTokenContext {
return false;
}
@Override
public boolean isValid() {
return SaTokenContextForThreadLocalStorage.getBox() != null;
}
}
@@ -88,6 +88,7 @@ public class SaCookie {
/**
* @param name 名称
* @return 对象自身
*/
public SaCookie setName(String name) {
this.name = name;
@@ -0,0 +1,15 @@
package cn.dev33.satoken.context.second;
import cn.dev33.satoken.context.SaTokenContext;
/**
* Sa-Token 二级Context - 基础接口
*
* <p> (利用继承机制实现区别 [一级Context] 与 [二级Context] 的目的)
*
* @author kong
*
*/
public interface SaTokenSecondContext extends SaTokenContext {
}
@@ -0,0 +1,18 @@
package cn.dev33.satoken.context.second;
/**
* Sa-Token 二级Context - 创建器
*
* @author kong
*
*/
@FunctionalInterface
public interface SaTokenSecondContextCreator {
/**
* 创建一个二级 Context
* @return /
*/
public SaTokenSecondContext create();
}
@@ -0,0 +1,31 @@
package cn.dev33.satoken.exception;
/**
* 一个异常:代表 API 已被禁用
* @author kong
*/
public class ApiDisabledException extends SaTokenException {
/**
* 序列化版本号
*/
private static final long serialVersionUID = 6806129545290130133L;
/** 异常提示语 */
public static final String BE_MESSAGE = "This API is disabled";
/**
* 一个异常:代表 API 已被禁用
*/
public ApiDisabledException() {
super(BE_MESSAGE);
}
/**
* 一个异常:代表 API 已被禁用
* @param message 异常描述
*/
public ApiDisabledException(String message) {
super(message);
}
}
@@ -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);
}
@@ -26,7 +26,7 @@ public class SaRouter {
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
return SaManager.getSaTokenContext().matchPath(pattern, path);
return SaManager.getSaTokenContextOrSecond().matchPath(pattern, path);
}
/**
@@ -253,6 +253,7 @@ public class SaRouter {
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param fun 要执行的校验方法
* @return /
*/
public static SaRouterStaff match(String pattern, SaFunction fun) {
return new SaRouterStaff().match(pattern, fun);
@@ -262,6 +263,7 @@ public class SaRouter {
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param fun 要执行的校验方法
* @return /
*/
public static SaRouterStaff match(String pattern, SaParamFunction<SaRouterStaff> fun) {
return new SaRouterStaff().match(pattern, fun);
@@ -272,6 +274,7 @@ public class SaRouter {
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param fun 要执行的方法
* @return /
*/
public static SaRouterStaff match(String pattern, String excludePattern, SaFunction fun) {
return new SaRouterStaff().match(pattern, excludePattern, fun);
@@ -282,6 +285,7 @@ public class SaRouter {
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param fun 要执行的方法
* @return /
*/
public static SaRouterStaff match(String pattern, String excludePattern, SaParamFunction<SaRouterStaff> fun) {
return new SaRouterStaff().match(pattern, excludePattern, fun);
@@ -250,6 +250,7 @@ public class SaRouterStaff {
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param fun 要执行的校验方法
* @return /
*/
public SaRouterStaff match(String pattern, SaFunction fun) {
return this.match(pattern).check(fun);
@@ -259,6 +260,7 @@ public class SaRouterStaff {
* 路由匹配,如果匹配成功则执行认证函数
* @param pattern 路由匹配符
* @param fun 要执行的校验方法
* @return /
*/
public SaRouterStaff match(String pattern, SaParamFunction<SaRouterStaff> fun) {
return this.match(pattern).check(fun);
@@ -269,6 +271,7 @@ public class SaRouterStaff {
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param fun 要执行的方法
* @return /
*/
public SaRouterStaff match(String pattern, String excludePattern, SaFunction fun) {
return this.match(pattern).notMatch(excludePattern).check(fun);
@@ -279,6 +282,7 @@ public class SaRouterStaff {
* @param pattern 路由匹配符
* @param excludePattern 要排除的路由匹配符
* @param fun 要执行的方法
* @return /
*/
public SaRouterStaff match(String pattern, String excludePattern, SaParamFunction<SaRouterStaff> fun) {
return this.match(pattern).notMatch(excludePattern).check(fun);
@@ -27,7 +27,7 @@ public class SaRouterUtil {
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
return SaManager.getSaTokenContext().matchPath(pattern, path);
return SaManager.getSaTokenContextOrSecond().matchPath(pattern, path);
}
/**
@@ -93,7 +93,7 @@ public class SaLoginModel {
/**
* @return 获取device参数,如果为null,则返回默认值
*/
public String getDeviceOrDefalut() {
public String getDeviceOrDefault() {
if(device == null) {
return SaTokenConsts.DEFAULT_LOGIN_DEVICE;
}
@@ -142,4 +142,13 @@ public class SaLoginModel {
return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout + "]";
}
/**
* 更换为 getDeviceOrDefault()
* @return /
*/
@Deprecated
public String getDeviceOrDefalut() {
return getDeviceOrDefault();
}
}
@@ -84,37 +84,62 @@ public class StpLogic {
}
/**
* 创建一个TokenValue
* @param loginId loginId
* 创建一个TokenValue
* @param loginId loginId
* @param device 设备标识
* @param timeout 过期时间
* @return 生成的tokenValue
*/
public String createTokenValue(Object loginId) {
public String createTokenValue(Object loginId, String device, long timeout) {
return SaStrategy.me.createToken.apply(loginId, loginType);
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
*/
public void setTokenValue(String tokenValue){
setTokenValue(tokenValue, (int)SaManager.getConfig().getTimeout());
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
* @param cookieTimeout Cookie存活时间(秒)
*/
public void setTokenValue(String tokenValue, int cookieTimeout){
SaTokenConfig config = getConfig();
if(SaFoxUtil.isEmpty(tokenValue)) {
return;
}
// 1. 将token保存到[存储器]里
setTokenValueToStorage(tokenValue);
// 2. 将 Token 保存到 [Cookie] 里
if (getConfig().getIsReadCookie()) {
setTokenValueToCookie(tokenValue, cookieTimeout);
}
}
/**
* 将 Token 保存到 [Storage] 里
* @param tokenValue token值
*/
public void setTokenValueToStorage(String tokenValue){
// 1. 将token保存到[存储器]里
SaStorage storage = SaHolder.getStorage();
// 如果打开了token前缀模式,则拼接上前缀一起写入
String tokenPrefix = config.getTokenPrefix();
// 2. 如果打开了 Token 前缀模式,则拼接上前缀
String tokenPrefix = getConfig().getTokenPrefix();
if(SaFoxUtil.isEmpty(tokenPrefix) == false) {
storage.set(splicingKeyJustCreatedSave(), tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT + tokenValue);
} else {
storage.set(splicingKeyJustCreatedSave(), tokenValue);
}
// 2. 将 Token 保存到 [Cookie] 里
if (config.getIsReadCookie()) {
setTokenValueToCookie(tokenValue, cookieTimeout);
}
// 3. 写入 (无前缀)
storage.set(SaTokenConsts.JUST_CREATED_NOT_PREFIX, tokenValue);
}
/**
@@ -142,6 +167,30 @@ public class StpLogic {
* @return 当前tokenValue
*/
public String getTokenValue(){
// 1. 获取
String tokenValue = getTokenValueNotCut();
// 2. 如果打开了前缀模式,则裁剪掉
String tokenPrefix = getConfig().getTokenPrefix();
if(SaFoxUtil.isEmpty(tokenPrefix) == false) {
// 如果token并没有按照指定的前缀开头,则视为未提供token
if(SaFoxUtil.isEmpty(tokenValue) || tokenValue.startsWith(tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT) == false) {
tokenValue = null;
} else {
// 则裁剪掉前缀
tokenValue = tokenValue.substring(tokenPrefix.length() + SaTokenConsts.TOKEN_CONNECTOR_CHAT.length());
}
}
// 3. 返回
return tokenValue;
}
/**
* 获取当前TokenValue (不裁剪前缀)
* @return /
*/
public String getTokenValueNotCut(){
// 0. 获取相应对象
SaStorage storage = SaHolder.getStorage();
SaRequest request = SaHolder.getRequest();
@@ -166,19 +215,7 @@ public class StpLogic {
tokenValue = request.getCookieValue(keyTokenName);
}
// 5. 如果打开了前缀模式
String tokenPrefix = getConfig().getTokenPrefix();
if(SaFoxUtil.isEmpty(tokenPrefix) == false) {
// 如果token并没有按照指定的前缀开头,则视为未提供token
if(SaFoxUtil.isEmpty(tokenValue) || tokenValue.startsWith(tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT) == false) {
tokenValue = null;
} else {
// 则裁剪掉前缀
tokenValue = tokenValue.substring(tokenPrefix.length() + SaTokenConsts.TOKEN_CONNECTOR_CHAT.length());
}
}
// 6. 返回
// 5. 返回
return tokenValue;
}
@@ -255,8 +292,8 @@ public class StpLogic {
// --- 如果允许并发登录
if(config.getIsConcurrent()) {
// 如果配置为共享token, 则尝试从Session签名记录里取出token
if(config.getIsShare()) {
tokenValue = getTokenValueByLoginId(id, loginModel.getDevice());
if(getConfigOfIsShare()) {
tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
}
} else {
// --- 如果不允许并发登录,则将这个账号的历史登录标记为:被顶下线
@@ -264,7 +301,7 @@ public class StpLogic {
}
// 如果至此,仍未成功创建tokenValue, 则开始生成一个
if(tokenValue == null) {
tokenValue = createTokenValue(id);
tokenValue = createTokenValue(id, loginModel.getDeviceOrDefault(), loginModel.getTimeout());
}
// ------ 3. 获取 User-Session , 续期
@@ -272,7 +309,7 @@ public class StpLogic {
session.updateMinTimeout(loginModel.getTimeout());
// 在 User-Session 上记录token签名
session.addTokenSign(tokenValue, loginModel.getDeviceOrDefalut());
session.addTokenSign(tokenValue, loginModel.getDeviceOrDefault());
// ------ 4. 持久化其它数据
// token -> id 映射关系
@@ -299,10 +336,16 @@ public class StpLogic {
if(SaFoxUtil.isEmpty(tokenValue)) {
return;
}
// 如果打开了Cookie模式,第一步,先把cookie清除掉
// 从当前 [storage存储器] 里删除
SaHolder.getStorage().delete(splicingKeyJustCreatedSave());
// 如果打开了Cookie模式,则把cookie清除掉
if(getConfig().getIsReadCookie()){
SaHolder.getResponse().deleteCookie(getTokenName());
}
// 清除这个token的相关信息
logoutByTokenValue(tokenValue);
}
@@ -504,19 +547,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 +666,7 @@ public class StpLogic {
* @return 账号id
*/
public String getLoginIdNotHandle(String tokenValue) {
return SaManager.getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
return getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
}
// ---- 其它操作
@@ -640,7 +683,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 +692,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 +701,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 +715,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;
}
@@ -761,7 +804,7 @@ public class StpLogic {
String tokenValue = getTokenValue();
if(tokenValue == null || Objects.equals(tokenValue, "")) {
// 随机一个token送给Ta
tokenValue = createTokenValue(null);
tokenValue = createTokenValue(null, null, getConfig().getTimeout());
// 写入 [最后操作时间]
setLastActivityToNow(tokenValue);
// 在当前会话写入这个tokenValue
@@ -786,10 +829,10 @@ public class StpLogic {
* @param tokenValue token值
*/
public void deleteTokenSession(String tokenValue) {
SaManager.getSaTokenDao().delete(splicingKeyTokenSession(tokenValue));
getSaTokenDao().delete(splicingKeyTokenSession(tokenValue));
}
// ------------------- [临时期] 验证相关 -------------------
// ------------------- [临时有效期] 验证相关 -------------------
/**
* 写入指定token的 [最后操作时间] 为当前时间戳
@@ -801,7 +844,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 +857,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 +885,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 +909,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 +929,7 @@ public class StpLogic {
* @return token剩余有效时间
*/
public long getTokenTimeout() {
return SaManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue()));
return getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue()));
}
/**
@@ -895,7 +938,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 +955,7 @@ public class StpLogic {
* @return token剩余有效时间
*/
public long getSessionTimeoutByLoginId(Object loginId) {
return SaManager.getSaTokenDao().getSessionTimeout(splicingKeySession(loginId));
return getSaTokenDao().getSessionTimeout(splicingKeySession(loginId));
}
/**
@@ -929,7 +972,7 @@ public class StpLogic {
* @return token剩余有效时间
*/
public long getTokenSessionTimeoutByTokenValue(String tokenValue) {
return SaManager.getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue));
return getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue));
}
/**
@@ -957,7 +1000,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 +1342,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 +1353,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 +1364,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 +1437,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 +1449,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 +1458,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 +1466,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);
@@ -1591,7 +1634,8 @@ public class StpLogic {
* @return key
*/
public String splicingKeyJustCreatedSave() {
return SaTokenConsts.JUST_CREATED_SAVE_KEY + loginType;
// return SaTokenConsts.JUST_CREATED_SAVE_KEY + loginType;
return SaTokenConsts.JUST_CREATED;
}
/**
@@ -1607,19 +1651,35 @@ public class StpLogic {
// ------------------- Bean对象代理 -------------------
/**
* 返回配置对象
* @return 配置对象
* 返回全局配置对象
* @return /
*/
public SaTokenConfig getConfig() {
// 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利
return SaManager.getConfig();
}
/**
* 返回全局配置对象的isShare属性
* @return /
*/
public boolean getConfigOfIsShare() {
return getConfig().getIsShare();
}
/**
* 返回持久化对象
* @return /
*/
public SaTokenDao getSaTokenDao() {
return SaManager.getSaTokenDao();
}
/**
* 判断:集合中是否包含指定元素(模糊匹配)
* @param list 集合
* @param element 元素
* @return
* @return /
*/
public boolean hasElement(List<String> list, String element) {
return SaStrategy.me.hasElement.apply(list, element);
@@ -1628,7 +1688,7 @@ public class StpLogic {
// ------------------- 历史API,兼容旧版本 -------------------
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout(id) ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
@@ -1639,7 +1699,7 @@ public class StpLogic {
}
/**
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout() ,使用方式保持不变 </h1>
* <h1> 本函数设计已过时,未来版本可能移除此函数,请及时更换为 StpUtil.kickout(id) ,使用方式保持不变 </h1>
*
* 会话注销,根据账号id and 设备标识 (踢人下线)
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2 </p>
@@ -51,6 +51,14 @@ public class StpUtil {
return stpLogic.getTokenName();
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
*/
public static void setTokenValue(String tokenValue){
stpLogic.setTokenValue(tokenValue);
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
@@ -68,6 +76,14 @@ public class StpUtil {
return stpLogic.getTokenValue();
}
/**
* 获取当前TokenValue (不裁剪前缀)
* @return /
*/
public static String getTokenValueNotCut(){
return stpLogic.getTokenValueNotCut();
}
/**
* 获取当前会话的Token信息
* @return token信息
@@ -340,7 +356,7 @@ public class StpUtil {
}
// =================== [临时期] 验证相关 ===================
// =================== [临时有效期] 验证相关 ===================
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
@@ -15,17 +15,16 @@ import cn.dev33.satoken.session.SaSession;
* Sa-Token 策略对象
* <p>
* 此类统一定义框架内的一些关键性逻辑算法,方便开发者进行按需重写,例:
* </p>
* <pre>
// SaStrategy全局单例,所有方法都用以下形式重写
SaStrategy.me.setCreateToken((loginId, loginType) -> {
SaStrategy.me.setCreateToken((loginId, loginType) - {
// 自定义Token生成的算法
return "xxxx";
});
* </pre>
* </p>
*
* @author kong
* @param <T>
*
*/
@SuppressWarnings("deprecation")
@@ -13,7 +13,7 @@ public class SaTokenConsts {
/**
* Sa-Token 当前版本号
*/
public static final String VERSION_NO = "v1.27.0";
public static final String VERSION_NO = "v1.28.0";
/**
* Sa-Token 开源地址
@@ -30,7 +30,12 @@ public class SaTokenConsts {
/**
* 常量key标记: 如果token为本次请求新创建的,则以此字符串为key存储在当前request中
*/
public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_";
public static final String JUST_CREATED = "JUST_CREATED_";
/**
* 常量key标记: 如果token为本次请求新创建的,则以此字符串为key存储在当前request中(不拼接前缀,纯Token
*/
public static final String JUST_CREATED_NOT_PREFIX = "JUST_CREATED_NOT_PREFIX_";
/**
* 常量key标记: 如果本次请求已经验证过[无操作过期], 则以此值存储在当前request中
@@ -97,5 +102,14 @@ public class SaTokenConsts {
* 切面、拦截器、过滤器等各种组件的注册优先级顺序
*/
public static final int ASSEMBLY_ORDER = -100;
// =================== 废弃 ===================
/**
* 请更换为 JUST_CREATED
*/
@Deprecated
public static final String JUST_CREATED_SAVE_KEY = JUST_CREATED;
}
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -0,0 +1,13 @@
target/
.project
.classpath
.settings
/.idea/
node_modules/
bin/
.settings/
unpackage/
/.apt_generated/
/.apt_generated_tests/
@@ -0,0 +1,76 @@
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pj</groupId>
<artifactId>sa-token-demo-dubbo-consumer</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.11</version>
</dependency>
<!-- Dubbo 注册到 Nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.11</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Sa-Token 整合 Dubbo -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-context-dubbo</artifactId>
<version>${sa-token-version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,22 @@
package com.pj;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Dubbo 服务消费端
*
* @author kong
*
*/
@EnableDubbo
@SpringBootApplication
public class ConsumerApplication {
public static void main(String[] args) {
SpringApplication.run(ConsumerApplication.class, args);
System.out.println("ConsumerApplication 启动成功");
}
}
@@ -0,0 +1,16 @@
package com.pj.more;
public interface DemoService {
/**
* 登录
* @param loginId 账号id
*/
void doLogin(Object loginId);
/**
* 判断是否登录,打印状态
*/
void isLogin(String str);
}
@@ -0,0 +1,60 @@
package com.pj.more;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.StpUtil;
@RestController
public class TestController {
@DubboReference
private DemoService demoService;
// Consumer端登录,状态传播到Provider端
@RequestMapping("test")
public String test() {
demoService.isLogin("----------- 登录前 ");
StpUtil.login(10001);
demoService.isLogin("----------- 登录后 ");
return "ok";
}
// Provider端登录,状态回传到Consumer端
@RequestMapping("test2")
public String test2() {
System.out.println("----------- 登录前 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
demoService.doLogin(10002);
System.out.println("----------- 登录后 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
return "ok";
}
// Consumer端登录,状态在Consumer端保持
@RequestMapping("test3")
public String test3() {
System.out.println("----------- 登录前 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
StpUtil.login(10003);
demoService.isLogin("----------- Provider状态");
System.out.println("----------- 登录后 ");
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
return "ok";
}
}
@@ -0,0 +1,24 @@
server:
# 端口号
port: 8081
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
dubbo:
application:
# 服务名称
name: dubbo-consumer-demo
registry:
# 注册中心地址
address: nacos://127.0.0.1:8001
@@ -0,0 +1,13 @@
target/
.project
.classpath
.settings
/.idea/
node_modules/
bin/
.settings/
unpackage/
/.apt_generated/
/.apt_generated_tests/
@@ -0,0 +1,76 @@
<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/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.pj</groupId>
<artifactId>sa-token-demo-dubbo-provider</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- SpringBoot -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
</parent>
<!-- 指定一些属性 -->
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
<!-- SpringBoot Web模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Sa-Token -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>${sa-token-version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- Dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.11</version>
</dependency>
<!-- Dubbo 注册到 Nacos -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-registry-nacos</artifactId>
<version>2.7.11</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>1.4.1</version>
</dependency>
<!-- Sa-Token 整合 Dubbo -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-context-dubbo</artifactId>
<version>${sa-token-version}</version>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,22 @@
package com.pj;
import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* Dubbo 服务提供端
*
* @author kong
*
*/
@EnableDubbo
@SpringBootApplication
public class ProviderApplication {
public static void main(String[] args) {
SpringApplication.run(ProviderApplication.class, args);
System.out.println("ProviderApplication 启动成功");
}
}
@@ -0,0 +1,16 @@
package com.pj.more;
public interface DemoService {
/**
* 登录
* @param loginId 账号id
*/
void doLogin(Object loginId);
/**
* 判断是否登录,打印状态
*/
void isLogin(String str);
}
@@ -0,0 +1,23 @@
package com.pj.more;
import org.apache.dubbo.config.annotation.DubboService;
import cn.dev33.satoken.stp.StpUtil;
@DubboService()
public class DemoServiceImpl implements DemoService {
@Override
public void doLogin(Object loginId) {
StpUtil.login(loginId);
}
@Override
public void isLogin(String str) {
System.out.println(str);
System.out.println("Token值:" + StpUtil.getTokenValue());
System.out.println("是否登录:" + StpUtil.isLogin());
}
}
@@ -0,0 +1,38 @@
server:
# 端口号
port: 8080
spring:
# redis配置
redis:
# Redis数据库索引(默认为0
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码(默认为空)
password:
# 连接超时时间
timeout: 10s
# Dubbo
dubbo:
# 服务名
application:
name: dubbo-provider-demo
# 扫描包
scan:
base-packages: com.pj
# 注册中心地址
registry:
address: nacos://127.0.0.1:8001
# 协议
protocol:
name: dubbo
port: 12345
+15 -34
View File
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -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>
<!-- 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> -->
<!-- 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,13 @@
<optional>true</optional>
</dependency>
<!-- test -->
<dependency>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
<!-- <version>2.3.1</version> -->
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</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.StpLogicJwtForStyle;
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 StpLogicJwtForStyle();
}
}
@@ -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:
@@ -0,0 +1,256 @@
package com.pj.test;
import java.util.List;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.exception.DisableLoginException;
import cn.dev33.satoken.jwt.SaJwtUtil;
import cn.dev33.satoken.jwt.StpLogicJwtForMix;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
/**
* Sa-Token 整合 jwtmix 模式 测试
*
* @author kong
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartUpApplication.class)
public class JwtForMixTest {
// 持久化Bean
@Autowired(required = false)
SaTokenDao dao = SaManager.getSaTokenDao();
// 开始
@BeforeClass
public static void beforeClass() {
System.out.println("\n\n------------------------ JwtForMixTest star ...");
StpUtil.setStpLogic(new StpLogicJwtForMix());
}
// 结束
@AfterClass
public static void afterClass() {
System.out.println("\n\n------------------------ JwtForMixTest end ... \n");
}
// 测试:登录
@Test
public void doLogin() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
// API 验证
Assert.assertTrue(StpUtil.isLogin());
Assert.assertNotNull(token); // token不为null
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assert.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备
// token 验证
JWT jwt = JWT.of(token);
JSONObject payloads = jwt.getPayloads();
Assert.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_ID), "10001"); // 账号
Assert.assertEquals(payloads.getStr(SaJwtUtil.DEVICE), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备
Assert.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_TYPE), StpUtil.TYPE); // 账号类型
// db数据 验证
// token不存在
Assert.assertNull(dao.get("satoken:login:token:" + token));
// Session 存在
SaSession session = dao.getSession("satoken:login:session:" + 10001);
Assert.assertNotNull(session);
Assert.assertEquals(session.getId(), "satoken:login:session:" + 10001);
Assert.assertTrue(session.getTokenSignList().size() >= 1);
}
// 测试:注销
@Test
public void logout() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
Assert.assertEquals(JWT.of(token).getPayloads().getStr("loginId"), "10001");
// 注销
StpUtil.logout();
// token 应该被清除
Assert.assertNull(StpUtil.getTokenValue());
Assert.assertFalse(StpUtil.isLogin());
}
// 测试:Session会话
@Test
public void testSession() {
StpUtil.login(10001);
// API 应该可以获取 Session
Assert.assertNotNull(StpUtil.getSession(false));
// db中应该存在 Session
SaSession session = dao.getSession("satoken:login:session:" + 10001);
Assert.assertNotNull(session);
// 存取值
session.set("name", "zhang");
session.set("age", "18");
Assert.assertEquals(session.get("name"), "zhang");
Assert.assertEquals(session.getInt("age"), 18);
Assert.assertEquals((int)session.getModel("age", int.class), 18);
Assert.assertEquals((int)session.get("age", 20), 18);
Assert.assertEquals((int)session.get("name2", 20), 20);
Assert.assertEquals((int)session.get("name2", () -> 30), 30);
session.clear();
Assert.assertEquals(session.get("name"), null);
}
// 测试:权限认证
@Test
public void testCheckPermission() {
StpUtil.login(10001);
// 权限认证
Assert.assertTrue(StpUtil.hasPermission("user-add"));
Assert.assertTrue(StpUtil.hasPermission("user-list"));
Assert.assertTrue(StpUtil.hasPermission("user"));
Assert.assertTrue(StpUtil.hasPermission("art-add"));
Assert.assertFalse(StpUtil.hasPermission("get-user"));
// and
Assert.assertTrue(StpUtil.hasPermissionAnd("art-add", "art-get"));
Assert.assertFalse(StpUtil.hasPermissionAnd("art-add", "comment-add"));
// or
Assert.assertTrue(StpUtil.hasPermissionOr("art-add", "comment-add"));
Assert.assertFalse(StpUtil.hasPermissionOr("comment-add", "comment-delete"));
}
// 测试:角色认证
@Test
public void testCheckRole() {
StpUtil.login(10001);
// 角色认证
Assert.assertTrue(StpUtil.hasRole("admin"));
Assert.assertFalse(StpUtil.hasRole("teacher"));
// and
Assert.assertTrue(StpUtil.hasRoleAnd("admin", "super-admin"));
Assert.assertFalse(StpUtil.hasRoleAnd("admin", "ceo"));
// or
Assert.assertTrue(StpUtil.hasRoleOr("admin", "ceo"));
Assert.assertFalse(StpUtil.hasRoleOr("ceo", "cto"));
}
// 测试:根据token强制注销
@Test(expected = ApiDisabledException.class)
public void testLogoutByToken() {
// 先登录上
StpUtil.login(10001);
Assert.assertTrue(StpUtil.isLogin());
String token = StpUtil.getTokenValue();
// 根据token注销
StpUtil.logoutByTokenValue(token);
}
// 测试:根据账号id强制注销
@Test(expected = ApiDisabledException.class)
public void testLogoutByLoginId() {
// 先登录上
StpUtil.login(10001);
Assert.assertTrue(StpUtil.isLogin());
// 根据账号id注销
StpUtil.logout(10001);
}
// 测试Token-Session
@Test
public void testTokenSession() {
// 先登录上
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
// 刚开始不存在
Assert.assertNull(StpUtil.stpLogic.getTokenSession(false));
SaSession session = dao.getSession("satoken:login:token-session:" + token);
Assert.assertNull(session);
// 调用一次就存在了
StpUtil.getTokenSession();
Assert.assertNotNull(StpUtil.stpLogic.getTokenSession(false));
SaSession session2 = dao.getSession("satoken:login:token-session:" + token);
Assert.assertNotNull(session2);
}
// 测试:账号封禁
@Test(expected = DisableLoginException.class)
public void testDisable() {
// 封号
StpUtil.disable(10007, 200);
Assert.assertTrue(StpUtil.isDisable(10007));
Assert.assertEquals(dao.get("satoken:login:disable:" + 10007), DisableLoginException.BE_VALUE);
// 解封
StpUtil.untieDisable(10007);
Assert.assertFalse(StpUtil.isDisable(10007));
Assert.assertEquals(dao.get("satoken:login:disable:" + 10007), null);
// 封号后登陆 (会抛出 DisableLoginException 异常)
StpUtil.disable(10007, 200);
StpUtil.login(10007);
}
// 测试:身份切换
@Test
public void testSwitch() {
// 登录
StpUtil.login(10001);
Assert.assertFalse(StpUtil.isSwitch());
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001);
// 开始身份切换
StpUtil.switchTo(10044);
Assert.assertTrue(StpUtil.isSwitch());
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10044);
// 结束切换
StpUtil.endSwitch();
Assert.assertFalse(StpUtil.isSwitch());
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001);
}
// 测试:会话管理
@Test(expected = ApiDisabledException.class)
public void testSearchTokenValue() {
// 登录
StpUtil.login(10001);
StpUtil.login(10002);
StpUtil.login(10003);
StpUtil.login(10004);
StpUtil.login(10005);
// 查询
List<String> list = StpUtil.searchTokenValue("", 0, 10);
Assert.assertTrue(list.size() >= 5);
}
}
@@ -0,0 +1,166 @@
package com.pj.test;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.exception.ApiDisabledException;
import cn.dev33.satoken.jwt.SaJwtUtil;
import cn.dev33.satoken.jwt.StpLogicJwtForStateless;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
/**
* Sa-Token 整合 jwtstateless 模式 测试
*
* @author kong
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartUpApplication.class)
public class JwtForStatelessTest {
// 持久化Bean
@Autowired(required = false)
SaTokenDao dao = SaManager.getSaTokenDao();
// 开始
@BeforeClass
public static void beforeClass() {
System.out.println("\n\n------------------------ JwtForStatelessTest star ...");
StpUtil.setStpLogic(new StpLogicJwtForStateless());
}
// 结束
@AfterClass
public static void afterClass() {
System.out.println("\n\n------------------------ JwtForStatelessTest end ... \n");
}
// 测试:登录
@Test
public void doLogin() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
// API 验证
Assert.assertTrue(StpUtil.isLogin());
Assert.assertNotNull(token); // token不为null
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assert.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备
// token 验证
JWT jwt = JWT.of(token);
JSONObject payloads = jwt.getPayloads();
Assert.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_ID), "10001"); // 账号
Assert.assertEquals(payloads.getStr(SaJwtUtil.DEVICE), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备
Assert.assertEquals(payloads.getStr(SaJwtUtil.LOGIN_TYPE), StpUtil.TYPE); // 账号类型
// 时间
Assert.assertTrue(StpUtil.getTokenTimeout() <= SaManager.getConfig().getTimeout());
Assert.assertTrue(StpUtil.getTokenTimeout() > SaManager.getConfig().getTimeout() - 10000);
try {
// 尝试获取Session会抛出异常
StpUtil.getSession();
Assert.assertTrue(false);
} catch (Exception e) {
}
}
// 测试:注销
@Test
public void logout() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
Assert.assertEquals(JWT.of(token).getPayloads().getStr("loginId"), "10001");
// 注销
StpUtil.logout();
// token 应该被清除
Assert.assertNull(StpUtil.getTokenValue());
Assert.assertFalse(StpUtil.isLogin());
}
// 测试:Session会话
@Test(expected = ApiDisabledException.class)
public void testSession() {
StpUtil.login(10001);
// 会抛异常
StpUtil.getSession();
}
// 测试:权限认证
@Test
public void testCheckPermission() {
StpUtil.login(10001);
// 权限认证
Assert.assertTrue(StpUtil.hasPermission("user-add"));
Assert.assertTrue(StpUtil.hasPermission("user-list"));
Assert.assertTrue(StpUtil.hasPermission("user"));
Assert.assertTrue(StpUtil.hasPermission("art-add"));
Assert.assertFalse(StpUtil.hasPermission("get-user"));
// and
Assert.assertTrue(StpUtil.hasPermissionAnd("art-add", "art-get"));
Assert.assertFalse(StpUtil.hasPermissionAnd("art-add", "comment-add"));
// or
Assert.assertTrue(StpUtil.hasPermissionOr("art-add", "comment-add"));
Assert.assertFalse(StpUtil.hasPermissionOr("comment-add", "comment-delete"));
}
// 测试:角色认证
@Test
public void testCheckRole() {
StpUtil.login(10001);
// 角色认证
Assert.assertTrue(StpUtil.hasRole("admin"));
Assert.assertFalse(StpUtil.hasRole("teacher"));
// and
Assert.assertTrue(StpUtil.hasRoleAnd("admin", "super-admin"));
Assert.assertFalse(StpUtil.hasRoleAnd("admin", "ceo"));
// or
Assert.assertTrue(StpUtil.hasRoleOr("admin", "ceo"));
Assert.assertFalse(StpUtil.hasRoleOr("ceo", "cto"));
}
// 测试:根据token强制注销
@Test(expected = ApiDisabledException.class)
public void testLogoutByToken() {
// 先登录上
StpUtil.login(10001);
Assert.assertTrue(StpUtil.isLogin());
String token = StpUtil.getTokenValue();
// 根据token注销
StpUtil.logoutByTokenValue(token);
}
// 测试:根据账号id强制注销
@Test(expected = ApiDisabledException.class)
public void testLogoutByLoginId() {
// 先登录上
StpUtil.login(10001);
Assert.assertTrue(StpUtil.isLogin());
// 根据账号id注销
StpUtil.logout(10001);
}
}
@@ -0,0 +1,75 @@
package com.pj.test;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.jwt.StpLogicJwtForStyle;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.hutool.json.JSONObject;
import cn.hutool.jwt.JWT;
/**
* Sa-Token 整合 jwtStyle 模式 测试
*
* @author kong
*
*/
@RunWith(SpringRunner.class)
@SpringBootTest(classes = StartUpApplication.class)
public class JwtForStyleTest {
// 持久化Bean
static SaTokenDao dao;
// 开始
@BeforeClass
public static void beforeClass() {
System.out.println("\n\n------------------------ JwtForStyleTest star ...");
dao = SaManager.getSaTokenDao();
StpUtil.setStpLogic(new StpLogicJwtForStyle());
}
// 结束
@AfterClass
public static void afterClass() {
System.out.println("\n\n------------------------ JwtForStyleTest end ... \n");
}
// 测试:登录
@Test
public void doLogin() {
// 登录
StpUtil.login(10001);
String token = StpUtil.getTokenValue();
// API 验证
Assert.assertTrue(StpUtil.isLogin());
Assert.assertNotNull(token); // token不为null
Assert.assertEquals(StpUtil.getLoginIdAsLong(), 10001); // loginId=10001
Assert.assertEquals(StpUtil.getLoginDevice(), SaTokenConsts.DEFAULT_LOGIN_DEVICE); // 登录设备
// token 验证
JWT jwt = JWT.of(token);
JSONObject payloads = jwt.getPayloads();
Assert.assertEquals(payloads.getStr("loginId"), "10001");
// db数据 验证
// token存在
Assert.assertEquals(dao.get("satoken:login:token:" + token), "10001");
// Session 存在
SaSession session = dao.getSession("satoken:login:session:" + 10001);
Assert.assertNotNull(session);
Assert.assertEquals(session.getId(), "satoken:login:session:" + 10001);
Assert.assertTrue(session.getTokenSignList().size() >= 1);
}
}
@@ -0,0 +1,16 @@
package com.pj.test;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
* @author Auster
*
*/
@SpringBootApplication
public class StartUpApplication {
public static void main(String[] args) {
SpringApplication.run(StartUpApplication.class, args);
}
}
@@ -0,0 +1,35 @@
package com.pj.test.satoken;
import java.util.Arrays;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.stp.StpInterface;
/**
* 自定义权限验证接口扩展
*
* @author Auster
*
*/
@Component
public class StpInterfaceImpl implements StpInterface {
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
return Arrays.asList("user*", "art-add", "art-delete", "art-update", "art-get");
}
/**
* 返回一个账号所拥有的角色标识集合
*/
@Override
public List<String> getRoleList(Object loginId, String loginType) {
return Arrays.asList("admin", "super-admin");
}
}
@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义sa-token版本号 -->
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -17,7 +17,7 @@
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
<!-- 定义sa-token版本号 -->
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -15,7 +15,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
+2 -2
View File
@@ -9,7 +9,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -18,7 +18,7 @@
<dependency>
<groupId>org.noear</groupId>
<artifactId>solon-web</artifactId>
<version>1.5.27</version>
<version>1.5.50</version>
</dependency>
<!-- Sa-Token 权限认证, 在线文档:http://sa-token.dev33.cn/ -->
@@ -17,7 +17,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -14,8 +14,8 @@ import cn.dev33.satoken.SaManager;
public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
}
}
@@ -2,8 +2,6 @@ package com.pj.satoken.at;
import java.util.List;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
@@ -13,10 +11,9 @@ import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.stp.StpUtil;
/**
* Sa-Token 权限认证工具类
* Sa-Token 权限认证工具类 (User版)
* @author kong
*/
@Component
public class StpUserUtil {
/**
@@ -58,6 +55,14 @@ public class StpUserUtil {
return stpLogic.getTokenName();
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
*/
public static void setTokenValue(String tokenValue){
stpLogic.setTokenValue(tokenValue);
}
/**
* 在当前会话写入当前TokenValue
* @param tokenValue token值
@@ -75,6 +80,14 @@ public class StpUserUtil {
return stpLogic.getTokenValue();
}
/**
* 获取当前TokenValue (不裁剪前缀)
* @return /
*/
public static String getTokenValueNotCut(){
return stpLogic.getTokenValueNotCut();
}
/**
* 获取当前会话的Token信息
* @return token信息
@@ -347,7 +360,7 @@ public class StpUserUtil {
}
// =================== [临时期] 验证相关 ===================
// =================== [临时有效期] 验证相关 ===================
/**
* 检查当前token 是否已经[临时过期],如果已经过期则抛出异常
@@ -404,8 +417,34 @@ public class StpUserUtil {
// =================== 角色验证操作 ===================
/**
* 获取:当前账号的角色集合
* @return /
*/
public static List<String> getRoleList() {
return stpLogic.getRoleList();
}
/**
* 获取:指定账号的角色集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getRoleList(Object loginId) {
return stpLogic.getRoleList(loginId);
}
/**
* 判断:指定账号id是否含有角色标识, 返回true或false
* 判断:当前账号是否拥有指定角色, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
}
/**
* 判断:指定账号是否含有指定角色标识, 返回true或false
* @param loginId 账号id
* @param role 角色标识
* @return 是否含有指定角色标识
@@ -414,15 +453,6 @@ public class StpUserUtil {
return stpLogic.hasRole(loginId, role);
}
/**
* 判断:当前账号是否含有指定角色标识, 返回true或false
* @param role 角色标识
* @return 是否含有指定角色标识
*/
public static boolean hasRole(String role) {
return stpLogic.hasRole(role);
}
/**
* 判断:当前账号是否含有指定角色标识 [指定多个,必须全部验证通过]
* @param roleArray 角色标识数组
@@ -465,26 +495,24 @@ public class StpUserUtil {
stpLogic.checkRoleOr(roleArray);
}
// --
/**
* 返回当前账号所拥有的角色标识集合
* @return /
*/
public static List<String> getRoleList() {
return stpLogic.getRoleList();
}
// =================== 权限验证操作 ===================
/**
* 判断:指定账号id是否含有指定权限, 返回true或false
* @param loginId 账号id
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(Object loginId, String permission) {
return stpLogic.hasPermission(loginId, permission);
/**
* 获取:当前账号的权限码集合
* @return /
*/
public static List<String> getPermissionList() {
return stpLogic.getPermissionList();
}
/**
* 获取:指定账号的权限码集合
* @param loginId 指定账号id
* @return /
*/
public static List<String> getPermissionList(Object loginId) {
return stpLogic.getPermissionList(loginId);
}
/**
@@ -496,6 +524,16 @@ public class StpUserUtil {
return stpLogic.hasPermission(permission);
}
/**
* 判断:指定账号id是否含有指定权限, 返回true或false
* @param loginId 账号id
* @param permission 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(Object loginId, String permission) {
return stpLogic.hasPermission(loginId, permission);
}
/**
* 判断:当前账号是否含有指定权限, [指定多个,必须全部具有]
* @param permissionArray 权限码数组
@@ -538,15 +576,6 @@ public class StpUserUtil {
stpLogic.checkPermissionOr(permissionArray);
}
// --
/**
* 返回当前账号所拥有的权限码集合
* @return /
*/
public static List<String> getPermissionList() {
return stpLogic.getPermissionList();
}
// =================== id 反查token 相关操作 ===================
@@ -819,6 +848,7 @@ public class StpUserUtil {
* <p> 当对方再次访问系统时,会抛出NotLoginException异常,场景值=-2
* @param loginId 账号id
*/
@Deprecated
public static void logoutByLoginId(Object loginId) {
stpLogic.kickout(loginId);
}
@@ -831,6 +861,7 @@ public class StpUserUtil {
* @param loginId 账号id
* @param device 设备标识 (填null代表所有注销设备)
*/
@Deprecated
public static void logoutByLoginId(Object loginId, String device) {
stpLogic.kickout(loginId, device);
}
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
+1 -1
View File
@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.27.0</sa-token-version>
<sa-token-version>1.28.0</sa-token-version>
</properties>
<dependencies>
+19 -15
View File
@@ -1,7 +1,7 @@
<p align="center">
<img alt="logo" src="https://gitee.com/dromara/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.27.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.28.0</h1>
<h5 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h5>
<p align="center">
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg"></a>
@@ -64,21 +64,21 @@ StpUtil.kickout(10001);
在 Sa-Token 中,绝大多数功能都可以 **一行代码** 完成:
``` java
StpUtil.login(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.kickout(10001); // 将账号为10001的会话踢下线
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.login(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.kickout(10001); // 将账号为10001的会话踢下线
StpUtil.hasRole("super-admin"); // 查询当前账号是否含有指定角色标识, 返回true或false
StpUtil.hasPermission("user:add"); // 查询当前账号是否含有指定权限, 返回true或false
StpUtil.getSession(); // 获取当前账号id的Session
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.getTokenValueByLoginId(10001); // 获取账号id为10001的token令牌值
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.kickout(10001, "PC"); // 指定账号指定设备标识踢下线 (不同端不受影响)
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
StpUtil.login(10001, "PC"); // 指定设备标识登录,常用于“同端互斥登录”
StpUtil.kickout(10001, "PC"); // 指定账号指定设备标识踢下线 (不同端不受影响)
StpUtil.openSafe(120); // 在当前会话开启二级认证,有效期为120秒
StpUtil.checkSafe(); // 校验当前会话是否处于二级认证有效期内,校验失败会抛出异常
StpUtil.switchTo(10044); // 将当前会话身份临时切换为其它账号
```
即使不运行测试,相信您也能意会到绝大多数 API 的用法。
@@ -157,6 +157,10 @@ StpUtil.switchTo(10044); // 将当前会话身份临时切换
- **[ TLog ]**[ 一个轻量级的分布式日志标记追踪神器](https://gitee.com/dromara/TLog)
## 贡献者名单
感谢每一个为 Sa-Token 贡献代码的小伙伴
[![Giteye chart](https://chart.giteye.net/gitee/dromara/sa-token/CGZ7GT8E.png)](https://giteye.net/chart/CGZ7GT8E)
## 交流群
QQ交流群:1群:1002350610 (已满) 、
+9 -5
View File
@@ -59,20 +59,25 @@
- **插件**
- [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)
- [和 Thymeleaf 集成](/plugin/thymeleaf-extend)
- [和 jwt 集成](/plugin/jwt-extend)
- [和 Dubbo 集成](/plugin/dubbo-extend)
- [Sa-Token 插件开发指南](/fun/plugin-dev)
- **其它**
- [更新日志](/more/update-log)
- [友情链接](/more/link)
- [推荐公众号](/more/tj-gzh)
- [赞助 Sa-Token](/more/sa-token-donate)
- **附录**
- [常用类、方法](/more/common-action)
- [常见问题排查](/more/common-questions)
- [常见问题排查](/more/common-questions)
- [框架名词解释](/more/noun-intro)
- [Sa-Token功能结构图](/fun/auth-flow)
- [未登录场景值详解](/fun/not-login-scene)
- [Token有效期详解](/fun/token-timeout)
@@ -85,11 +90,10 @@
- [自定义 SaTokenContext 指南](/fun/sa-token-context)
- [框架源码所有技术栈](/fun/tech-stack)
- [为Sa-Token贡献代码](/fun/git-pr)
- [Sa-Token开源大事记](/fun/timeline)
- [Sa-Token框架掌握度--在线考试](/fun/sa-token-test)
<!-- - [Web开发常见漏洞防护](/fun/web-loophole) -->
<!-- - [Sa-Token大事记](/fun/timeline) -->
<br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/><br/>
<br/><br/><br/><br/><br/><br/><br/>
<p style="text-align: center;">----- 到底线了 -----</p>
+32 -16
View File
@@ -3,27 +3,43 @@
---
参考示例
参考如下
``` java
/**
* 返回一个账号所拥有的权限码集合
* 自定义权限验证接口扩展
*/
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
@Component
public class StpInterfaceImpl implements StpInterface {
// 返回一个账号所拥有的权限码集合
@Override
public List<String> getPermissionList(Object loginId, String loginType) {
// 1. 声明权限码集合
List<String> permissionList = new ArrayList<>();
// 2. 遍历角色列表,查询拥有的权限码
for (String roleId : getRoleList(loginId, loginType)) {
SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);
List<String> list = roleSession.get("Permission_List", () -> {
return ...; // 从数据库查询这个角色所拥有的权限列表
});
permissionList.addAll(list);
}
// 3. 返回权限码集合
return permissionList;
}
// 1. 获取这个账号所属角色id
long roleId = StpUtil.getSessionByLoginId(loginId).get("Role_Id", () -> {
return ...; // 从数据库查询这个账号所属的角色id
});
// 返回一个账号所拥有的角色标识集合
@Override
public List<String> getRoleList(Object loginId, String loginType) {
SaSession session = StpUtil.getSessionByLoginId(loginId);
return session.get("Role_List", () -> {
return ...; // 从数据库查询这个账号id拥有的角色列表
});
}
// 2. 获取这个角色id拥有的权限列表
SaSession roleSession = SaSessionCustomUtil.getSessionById("role-" + roleId);
List<String> list = roleSession.get("Permission_List", () -> {
return ...; // 从数据库查询这个角色id拥有的权限列表
});
// 3. 返回
return list;
}
```
+132
View File
@@ -0,0 +1,132 @@
# Sa-Token 插件开发指南
---
插件,从字面意思理解就是可拔插的组件,作用是在不改变 Sa-Token 现有架构的情况下,替换或扩展一部分底层代码逻辑。
为 Sa-Token 开发插件非常简单,以下是几种可用的方法:
- 自定义全局策略。
- 更改全局组件实现。
- 实现自定义SaTokenContext。
- 其它自由扩展。
下面依次介绍这几种方式。
### 1、自定义全局策略
Sa-Token 将框架的一些关键逻辑抽象出一个统一的概念 —— 策略,并统一定义在 `SaStrategy` 中,源码参考:[SaStrategy](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/strategy/SaStrategy.java) 。
SaStrategy 的每一个函数都可以单独重写,以 “自定义Token生成策略” 这一需求为例:
``` java
// 重写 Token 生成策略
SaStrategy.me.createToken = (loginId, loginType) -> {
return SaFoxUtil.getRandomString(60); // 随机60位长度字符串
};
```
就像变量的重新赋值一样,我们只需重新指定一个新的策略函数,即可自定义 Token 生成的逻辑。
### 2、更改全局组件实现
Sa-Token 大部分全局组件都定义在 SaManager 之上([SaManager](https://gitee.com/dromara/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/SaManager.java)),
我们只需要更改组件的实现类即可。以 临时令牌认证 模块举例
##### 1、先自定义一个实现类
``` java
/**
* 临时认证模块 自定义实现
*/
public class MySaTemp implements SaTempInterface {
@Override
public String createToken(Object value, long timeout) {
System.out.println("------- 自定义一些逻辑 createToken ");
return SaTempInterface.super.createToken(value, timeout);
}
@Override
public Object parseToken(String token) {
System.out.println("------- 自定义一些逻辑 parseToken ");
return SaTempInterface.super.parseToken(token);
}
}
```
##### 2、将自定义实现类绑定在 SaManager 上
``` java
// 注入
SaManager.setSaTemp(new MySaTemp());
```
以上是手动注入方式,如果你是 Spring 的 IOC 环境,则直接在 MySaTemp 实现类加上 @Component 注解即可。
##### 3、开始测试:
``` java
// 根据 value 创建一个 token
String token = SaTempUtil.createToken("10014", 120);
System.out.println("生成的Token为:" + token);
Object value = SaTempUtil.parseToken(token);
System.out.println("将Token解析后的值为:" + value);
```
观察控制台输出,检验自定义实现类是否注入成功:
![dev-plugin-print](https://oss.dev33.cn/sa-token/doc/dev-plugin-print.png)
### 3、实现自定义SaTokenContext
SaTokenContext 是对接不同框架的上下文接口,注入流程和第二步类似,篇幅限制,可参考:[自定义 SaTokenContext 指南](/fun/sa-token-context)
### 4、其它自由扩展
这种方式就无需注入什么全局组件替换内部实现了,你可以在 Sa-Token 的基础之上封装任何代码,进行功能扩展。
### 5、练练手
熟悉了插件开发流程,下面的 [ 待开发插件列表 ] 或许可以给你提供一个练手的方向。
##### SaTokenContext 实现:
| 插件 | 功能 | 状态 |
| :-------- | :-------- | :-------- |
| sa-token-solon-starter | Sa-Token 与 Solon 的整合 | <font color="green" >已完成</font> |
| sa-token-jfinal-starter | Sa-Token 与 JFinal 的整合 | 待开发 |
| sa-token-hasor-starter | Sa-Token 与 Hasor 的整合 | 待开发 |
##### 标签方言:
| 插件 | 功能 | 状态 |
| :-------- | :-------- | :-------- |
| sa-token-dialect-thymeleaf | Sa-Token 与 thymeleaf 的整合 | <font color="green" >已完成</font> |
| sa-token-dialect-freemarker | Sa-Token 与 freemarker 的整合 | 待开发 |
| sa-token-dialect-jsp | Sa-Token 与 jsp 的整合 | 待开发 |
| sa-token-dialect-velocity | Sa-Token 与 velocity 的整合 | 待开发 |
| sa-token-dialect-beetl | Sa-Token 与 beetl 的整合 | 待开发 |
##### 持久层扩展:
| 插件 | 功能 | 状态 |
| :-------- | :-------- | :-------- |
| sa-token-dao-redis | Sa-Token 与 Redis 的整合 | <font color="green" >已完成</font> |
| sa-token-dao-memcached | Sa-Token 与 memcached 的整合 | 待开发 |
##### 其它:
任何你认为有价值的功能代码,都可以扩展为插件。
### 6、发布代码
插件开发完毕之后,你可以将其pr到官方仓库,或:
上传到 gitee/github 作为独立项目维护,并发布到 Maven 中央仓库,参考这篇:[https://juejin.cn/post/6844904104834105358](https://juejin.cn/post/6844904104834105358)
+19 -10
View File
@@ -1,14 +1,23 @@
# Sa-Token 大事记
# Sa-Token 开源大事记
- **2020-02-04** 在GitHub提交第一个版本,正式开源
- **2020-09-14** GitHub star数量破100
- **2020-10-26** Gitee star数量破100
- **2021-03-01** 被[HelloGitHub]59期收录推荐
- **2021-03-26** GitHub star数量破1k
- **2021-03-30** 受TLog作者邀请,Sa-Token加入dromara社区
- **2021-03-30** 被Gitee官方列为推荐项目
- **2021-03-31** Gitee star数量破1K
- **2021-04-09** GitHub star数量破2K
- **2020-02-04** 在 GitHub 提交第一个版本,正式开源
- **2020-09-14** GitHub star 数量破 100
- **2020-10-26** Gitee star 数量破 100
- **2021-03-01** 被 [HelloGitHub]59 期收录推荐
- **2021-03-26** GitHub star 数量破 1k
- **2021-03-30** 受 TLog 作者邀请,Sa-Token 加入 dromara 社区
- **2021-03-30** 被 Gitee 列为推荐项目
- **2021-03-31** Gitee star 数量破1K
- **2021-04-09** GitHub star 数量破2K
- **2021-05-09** Gitee star 数量破2K
- **2021-05-17** GitHub star 数量破3K
- **2021-06-17** Sa-Token star 数量 (3529) 超过 Shiro (3506)
- **2021-07-15** GitHub star 数量破4K
- **2021-07-26** GitHub star 数量破5K
- **2021-07-29** Gitee star 数量破3K
- **2021-09-07** GitHub star 数量破6K
- **2021-09-24** Sa-Token star 数量 (6280) 超过 SpringSecurity (6247)
- **2021-10-19** Gitee star数量破4K
+5 -5
View File
@@ -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">
@@ -22,6 +22,7 @@
<nav>
<select onchange="location.href=this.value">
<option value="http://sa-token.dev33.cn/doc/index.html">最新版</option>
<option value="http://sa-token.dev33.cn/v/v1.27.0/doc/index.html">v1.27.0</option>
<option value="http://sa-token.dev33.cn/v/v1.26.0/doc/index.html">v1.26.0</option>
<option value="http://sa-token.dev33.cn/v/v1.25.0/doc/index.html">v1.25.0</option>
<option value="http://sa-token.dev33.cn/v/v1.24.0/doc/index.html">v1.24.0</option>
@@ -48,10 +49,9 @@
<option value="http://sa-token.dev33.cn/v/v1.4.0/doc/index.html">v1.4.0</option>
</select>
<a href="../index.html">首页</a>
<!-- <a href="index.html">文档</a> -->
<a href="http://sa-app.dev33.cn/wall.html?name=sa-token" target="_blank">需求墙</a>
<!-- <a href="#/more/tj-gzh">推荐公众号</a> -->
<!-- <a href="http://sa-app.dev33.cn/wall.html?name=sa-token" target="_blank">需求墙</a> -->
<a href="#/more/link">生态</a>
<a class="p-none" href="#/more/sa-token-donate">赞助</a>
<a href="#/more/update-log">更新日志</a>
</nav>
<div class="main-box">
@@ -62,7 +62,7 @@
</div>
<script>
var saTokenTopVersion = '1.27.0'; // Sa-Token最新版本
var saTokenTopVersion = '1.28.0'; // Sa-Token最新版本
var name = '<img style="width: 60px; height: 60px; vertical-align: middle;" src="logo.png" alt="logo" /> ';
name += '<b style="font-size: 28px; vertical-align: middle;">Sa-Token</b> <sub>v' + saTokenTopVersion + '</sub>'
window.$docsify = {
+7 -2
View File
@@ -16,8 +16,8 @@ body{font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu
/* @media screen and (min-width: 1700px) {
.main-box .markdown-section{max-width: 70%;}
} */
/* 手机端不显示广告 */
@media (max-width: 576px) {.wwads-cn{display:none!important}}
/* 手机端不显示广告,和一些其它东西 */
@media (max-width: 576px) {.wwads-cn,.p-none{display:none!important}}
/* 调整一下左侧树的字体样式 */
@@ -111,3 +111,8 @@ body{font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu
/* 行级代码样式 */
blockquote code {font-weight: 400;}
.markdown-section strong code{font-family: "宋体";}
/* 赞助列表 */
.zanzhu-pre+table tr td:nth-child(2){
color: red;
}
+1 -1
View File
@@ -83,7 +83,7 @@ public class SaTokenConfigure {
.addExclude("/favicon.ico")
// 鉴权方法:每次访问进入
.setAuth(obj -> {
// 登录验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
// 登录验 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
SaRouter.match("/**", "/user/doLogin", r -> StpUtil.checkLogin());
// 权限认证 -- 不同模块, 校验不同权限
+1 -1
View File
@@ -12,7 +12,7 @@ SaManager.getStpInterface(); // 获取权限认证对象
SaManager.getSaTokenAction(); // 获取框架行为对象
SaManager.getSaTokenContext(); // 获取上下文处理对象
SaManager.getSaTokenListener(); // 获取侦听器对象
SaManager.getSaTemp(); // 获取临时令牌证模块对象
SaManager.getSaTemp(); // 获取临时令牌证模块对象
SaManager.getStpLogic("type"); // 获取指定账号类型的StpLogic对象
```
+17 -6
View File
@@ -52,11 +52,13 @@
### 一个User对象存进Session后,再取出来时报错:无法从User类型转换成User类型?
群员亲测,当你打开热部署模式后,先存进去的对象,热刷新后再取出,会报错,关闭热刷新即可解决
群员亲测,当你打开热部署模式后,先存进去的对象,热刷新后再取出,会报错,关闭热刷新即可解决
### Springboot环境下采用自定义拦截器排除了某个路径仍然被拦截了?
可能是404了,SpringBoot环境下如果访问接口404后,会被转发到`/error`,然后被再次拦截,如果是其它原因,欢迎加群反馈
可能是404了,SpringBoot环境下如果访问接口404后,会被转发到`/error`,然后被再次拦截
如果不是404,可以先打印一下访问的路由,因为后端拦截的未必是你前端访问的这个path,先获取到具体path再仔细分析。
### 我配置了 active-timeout 值,但是当我每次续签时 Redis 中的 ttl 并没有更新,是不是 bug 了?
@@ -64,15 +66,24 @@
每次验签时用:当前时间 - 时间戳 > active-timeout,来判断这个 Token 是否已经超时
### 集成 jwt 后为什么在 getSession 时提示 jwt has not session ?
jwt 的招牌便是无须借助服务端完成会话管理,如果集成`jwt`后再使用`Session`功能,那将又回到了传统`Session`模式,属于自断招牌,此种技术组合没有意义,
因此jwt集成模式不提供`Session`功能,如果需要`Session`功能,就不要集成`jwt`
### 集成 Redis 后,明明 Redis 中有值,却还是提示无效Token?
根据以往的处理经验,发生这种情况 90% 的概率是因为你找错了Redis,即:代码连接的Redis和你用管理工具看到的Redis并不是同一个。
你可能会问:我看配置文件明明是同一个啊?
我的回答是:别光看配置文件,不一定准确,在启动时直接执行 `SaManager.getSaTokenDao().set("name", "value", 100000);`
随便写入一个值,看看能不能根据你的预期写进这个Redis,如果能的才能证明 Redis 连接没问题,再进行下一步排查。
### 整合 Redis 时先选择了默认jdk序列化,后又改成 jackson 序列化,程序开始报错,SerializationException
两者的序列化算法不一致导致的反序列化失败,如果要更改序列化方式,则需要先将 Redis 中历史数据清除,再做更新
### 集成 jwt 后为什么在 getSession 时提示 jwt has not session ?
jwt 的招牌便是无须借助服务端完成会话管理,如果集成`jwt`后再使用`Session`功能,那将又回到了传统`Session`模式,属于自断招牌,此种技术组合没有意义,
因此jwt集成模式不提供`Session`功能,如果需要`Session`功能,就不要集成`jwt`
### 我加了 Sa-Token 的全局过滤器,浏览器报错跨域了怎么办?
参考:[https://blog.csdn.net/shengzhang_/article/details/119928794](https://blog.csdn.net/shengzhang_/article/details/119928794)
@@ -115,7 +126,7 @@ jwt 的招牌便是无须借助服务端完成会话管理,如果集成`jwt`
### SaRouter.match 有多个路径需要排除怎么办?
可以点进去源码看一下,`SaRouter.match`方法有多个重载,可以放一个集合, 例如:<br>
`SaRouter.match(Arrays.asList("/**"), Arrays.asList("/login", "/reg"), () -> StpUtil.checkLogin());`
`SaRouter.match(/**).notMatch("/login", "/reg").check(r -> StpUtil.checkLogin());`
### 为什么StpUtil.login() 不能直接写入一个User对象?
+63
View File
@@ -0,0 +1,63 @@
# Sa-Token 名词解释
Sa-Token 无意发明任何晦涩概念提升逼格,但在处理 issues 、Q群解答时还是发现不少同学因为一些基本概念理解偏差导致代码出错,
所以整理本篇针对一些比较容易混淆的地方加以解释说明。
也希望各位同学在提交 issues、Q群提问之前充分阅读本篇文章,保证不要因为基本概念理解偏差,增加不必要的沟通成本。
---
#### 几种 Token
- token:指通过 `StpUtil.login()` 登录产生的身份令牌,用来维护用户登录状态,也称:satoken、会话Token。
- temp-token:指通过 `SaTempUtil.createToken()` 临时验证模块产生的Token,也称:临时Token。
- Access-Token:在 OAuth2 模块产生的身份令牌,也称:访问令牌、资源令牌。
- Refresh-Token:在 OAuth2 模块产生的刷新令牌,也称:刷新令牌。
- Id-Token:在 SaIdUtil 模块生成的Token令牌,用于提供子服务外网隔离功能。
#### 两种过期时间:
- timeout:会话 Token 的长久有效期。
- activity-timeout:会话的临时有效期。
两者的差别详见:[Token有效期详解](/fun/token-timeout)
#### 三种Session
- User-Session:框架为每个账号分配的 Session 对象,也称:账号Session。
- Token-Session:框架为每个 Token 分配的 Session 对象,也称:令牌Session。
- Custom-Session:以一个特定的值作为SessionId,来分配的 Session 对象,也称:自定义Session。
三者差别详见:[Session模型详解](/fun/session-model)
#### 账号标识:
- loginId:账号id,用来区分不同账号,通过 `StpUtil.login(id)` 来指定。
- device:登录设备端,例如:`PC``APP`,通过 `StpUtil.login(id, device)` 来指定。
- loginType:账号类型,用来区分不同体系的账号,如同一系统的 `User账号``Admin账号`,详见:[多账号认证](/up/many-account)
#### 几种登录策略:
- 单地登录:指同一时间只能在一个地方登录,新登录会挤掉旧登录,也可以叫:单端登录。
- 多地登录:指同一时间可以在不同地方登录,新登录会和旧登录共存,也可以叫:多端登录。
- 同端互斥登录:在同一类型设备上只允许单地点登录,在不同类型设备上允许同时在线,参考腾讯QQ的登录模式:手机和电脑可以同时在线,但不能两个手机同时在线。
- 单点登录:在进入多个系统时,只需要登录一次即可。解决用户在不同系统间频繁登录的问题。
- 同端多登录:指在一个终端可以同时登录多个账号。
- 记住我模式:指在一个设备终端登录成功,该设备重启之后依然保持登录状态。
#### 几种注销策略:
- 单端注销:只在调用登录的一端注销。
- 全端注销:一端注销,全端下线。
- 单点注销:与单点登录对应,一个系统注销,所有系统一起下线。
#### 几种鉴权方式:
- 代码鉴权:在代码里直接调用 `StpUtil.checkXxx` 相关 API 进行鉴权。
- 注解鉴权:在方法或类上添加 `@SaCheckXxx` 注解进行鉴权。
- 路由拦截鉴权:在全局过滤器或拦截里通过:`SaRouter.match()` 拦截路由进行鉴权。
+46
View File
@@ -0,0 +1,46 @@
# 赞助 Sa-Token
---
Sa-Token 采用 Apache-2.0 开源协议,**承诺框架本身与官网文档永久免费开放**,
但是框架的日常更新与社区运营需要付出大量的精力,靠爱发电难以长久,如果 Sa-Token 帮助到了你,你可以友情支持一下 Sa-Token。
### 友情赞助
你可以在项目 [Gitee](https://gitee.com/dromara/sa-token) 主页进行捐赠
![gitee-zanzhu2.png](https://oss.dev33.cn/sa-token/doc/gitee-zanzhu2.png)
#### 已捐赠列表:
<p class="zanzhu-pre"></p>
| 赞助人 | 赞助金额 | 留言 | 时间 |
| :-------- | :-------- | :-------- | :-------- |
| 孔孔的空空 | ¥ 100 | 感谢您的开源项目! | 2021-11-02 |
| songfazhun | ¥ 10 | 感谢您的开源项目! | 2021-10-28 |
| ithorns | ¥ 10 | 感谢您的开源项目! | 2021-10-25 |
| xiaoyan | ¥ 200 | 节日快乐 | 2021-10-24 |
| apifox001 | ¥ 200 | 开源不易,Apifoxapifox.cn)和你们一起加油! | 2021-10-15 |
| 永夜 | ¥ 20 | 感谢您的开源项目! | 2021-09-18 |
| 苏永晓 | ¥ 10 | 感谢您的开源项目! | 2021-09-01 |
| xiaoyan | ¥ 200 | 好的作者理应被认可 | 2021-08-24 |
| xiaoyan | ¥ 50 | be better | 2021-07-31 |
| 孔孔的空空 | ¥ 500 | 感谢您的开源项目! | 2021-07-30 |
| Wizzer | ¥ 20 | 感谢您的开源项目! | 2021-05-22 |
| 二范先生 | ¥ 20 | 省长加油啊 喝杯茶 | 2021-03-16 |
| EchoSlammaJamma | ¥ 20 | 感谢您的开源项目! | 2021-03-16 |
| xue1992wz | ¥ 20 | 感谢您的开源项目! | 2021-03-16 |
| whcrow | ¥ 20 | 军师加油! | 2021-03-16 |
| njx33 | ¥ 10 | 感谢您的开源项目! | 2020-12-17 |
| zhangjiaxiaozhuo | ¥ 10 | 感谢您的开源项目! | 2020-12-15 |
| 知知 | ¥ 10 | 感谢您的开源项目! | 2020-12-15 |
| 省长 | ¥ 10 | java中最好用的权限认证框架! | 2020-12-15 |
感谢每一位小伙伴的热心支持!
+34 -3
View File
@@ -7,6 +7,40 @@
<table class="gzh-table" style="text-align: center;">
<tr>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzU2MTI4MjI0MQ==&mid=2247508620&idx=1&sn=500448fce310a6012aa58616c304dec2&send_time="/>
<b>Java笔记虾</b>
<span>专注于Java技术栈,推送 Spring全家桶,Dubbo等相关技术知识</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzAxODcyNjEzNQ==&mid=2247541818&idx=2&sn=2b0e7190ed40fa07196de26be51bb432&send_time="/>
<b>程序猿DD</b>
<span>一线技术工作者的学习、生活与见闻</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzU5Mjc5NTIzMA==&mid=2247485007&idx=1&sn=d3bc0cc74a4efbc88b52cc5812c6573a&send_time="/>
<b>TJ君</b>
<span>一个励志推荐10000款开源项目与免费工具的程序猿</span>
</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=Mzg2OTA0Njk0OA==&mid=2247511204&idx=2&sn=e1c689f459474fcc1b8ee2ca20a52d3a&send_time="/>
<b>JavaGuide</b>
<span>专注Java后端学习和大厂面试的公众号!</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzI3NzE0NjcwMg==&mid=2650166553&idx=3&sn=c8457e76c3fea17235fb7370cd2279d8&send_time="/>
<b>Hollis</b>
<span>《Java工程师成神之路》系列作者</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzU1Nzg4NjgyMw==&mid=2247493156&idx=1&sn=571db231d274a90ae9a47ac9c4f0a034&send_time="/>
<b>macrozheng</b>
<span>专注Java技术分享,作者Github开源项目mall40K+Star</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzU2OTMyMTAxNA==&mid=2247496684&idx=2&sn=be1520743589ca43c129fde828af16ef&send_time="/>
<b>终码一生</b>
@@ -17,9 +51,6 @@
<b>CodeSheep</b>
<span>一只爱技术的程序羊,想把分享变成一种习惯!</span>
</td>
<td>-</td>
<td>-</td>
<td>-</td>
</tr>
<tr>
<td>
+14
View File
@@ -1,6 +1,20 @@
# 更新日志
### 2021-11-5 @v1.28.0
- 新增:新增 `sa-token-jwt` 插件,用于与jwt的整合 **[重要]**
- 新增:新增 `sa-token-context-dubbo` 插件,用于与 Dubbo 的整合 **[重要]**
- 文档:文档新增章节:Sa-Token 插件开发指南 **[重要]**
- 文档:文档新增章节:名称解释
- 优化:抽离 `getSaTokenDao()` 方法,方便重写
- 新增:单元测试新增多账号模式数据不互通测试
- 优化:优化在线文档,修复部分错误之处
- 优化:优化未登录异常抛出提示,标注无效的Token值
- 修复:修复单词拼写错误 `getDeviceOrDefault`
- 优化:优化 jwt 集成示例
- 文档:新增常见问题总结
### 2021-10-11 @v1.27.0
- 升级:增强 SaRouter 链式匹配能力 **[重要]**
- 新增:新增插件 Thymeleaf 标签方言 **[重要]**
+150
View File
@@ -0,0 +1,150 @@
# 和 Dubbo 集成
本插件的作用是让 Sa-Token 和 Dubbo 做一个整合。
---
### 先说说要解决的问题
在 Dubbo 的整个调用链中,代码被分为 Consumer 端和 Provider 端,为方便理解我们可以称其为 `[调用端]``[被调用端]`
RPC 模式的调用,可以让我们像调用本地方法一样完成服务通信,然而这种便利下却隐藏着两个问题:
- 上下文环境的丢失。
- 上下文参数的丢失。
这种问题作用在 Sa-Token 框架上就是,在 [ 被调用端 ] 调用 Sa-Token 相关API会抛出异常:**`无效上下文`**。
所以本插件的目的也就是解决上述两个问题:
- 在 [ 被调用端 ] 提供以 Dubbo 为基础的上下文环境
- 在 RPC 调用时将 Token 传递至 [ 被调用端 ],同时在调用结束时将 Token 回传至 [ 调用端 ]。
### 引入插件
在项目已经引入 Dubbo 的基础上,继续添加依赖(Consumer 端和 Provider 端都需要引入):
``` xml
<!-- Sa-Token 整合 Dubbo -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-context-dubbo</artifactId>
<version>${sa.top.version}</version>
</dependency>
```
然后我们就可以愉快的做到以下事情:
1. 在 [ 被调用端 ] 安全的调用 Sa-Token 相关 API。
2. 在 [ 调用端 ] 登录的会话,其登录状态可以自动传递到 [ 被调用端 ] 。
3. 在 [ 被调用端 ] 登录的会话,其登录状态也会自动回传到 [ 调用端 ] 。
但是我们仍具有以下限制:
1. [ 调用端 ] 与 [ 被调用端 ] 的 `SaStorage` 数据无法互通。
2. [ 被调用端 ] 执行的 `SaResponse.setHeader()`、`setStatus()` 等代码无效。
应该合理避开以上 API 的使用。
### RPC调用鉴权
在之前的 [Id-Token](/micro/id-token) 章节,我们演示了基于 Feign 的 RPC 调用鉴权,下面我们演示一下在 Dubbo 中如何集成 Id-Token 模块。
其实思路和 Feign 模式一致,在 [ 调用端 ] 追加 Id-Token 参数,在 [ 被调用端 ] 校验这个 Id-Token 参数:
- 校验通过:调用成功。
- 校验不通过:通过失败,抛出异常。
我们有两种方式完成整合。
##### 方式一、使用配置
直接在 `application.yml` 配置即可:
``` yml
sa-token:
# 打开 RPC 调用鉴权
check-id-token: true
```
##### 方式二、自建 Dubbo 过滤器校验
1、在 [ 调用端 ] 的 `\resources\META-INF\dubbo\` 目录新建 `org.apache.dubbo.rpc.Filter` 文件
``` html
dubboConsumerFilter=com.pj.DubboConsumerFilter
```
新建 `DubboConsumerFilter.java` 过滤器
``` java
package com.pj;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import cn.dev33.satoken.id.SaIdUtil;
/**
* Sa-Token 整合 Dubbo Consumer端过滤器
*/
@Activate(group = {CommonConstants.CONSUMER}, order = -10000)
public class DubboConsumerFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 追加 Id-Token 参数
RpcContext.getContext().setAttachment(SaIdUtil.ID_TOKEN, SaIdUtil.getToken());
// 开始调用
return invoker.invoke(invocation);
}
}
```
2、在 [ 被调用端 ] 的 `\resources\META-INF\dubbo\` 目录新建 `org.apache.dubbo.rpc.Filter` 文件
``` html
dubboProviderFilter=com.pj.DubboProviderFilter
```
新建 `DubboProviderFilter.java` 过滤器
``` java
package com.pj;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.*;
import cn.dev33.satoken.id.SaIdUtil;
/**
* Sa-Token 整合 Dubbo Provider端过滤器
*/
@Activate(group = {CommonConstants.PROVIDER}, order = -10000)
public class DubboProviderFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 取出 Id-Token 进行校验
String idToken = invocation.getAttachment(SaIdUtil.ID_TOKEN);
SaIdUtil.checkToken(idToken);
// 开始调用
return invoker.invoke(invocation);
}
}
```
然后我们就可以进行安全的 RPC 调用了,不带有 Id-Token 参数的调用都会抛出异常,无法调用成功。
+142
View File
@@ -0,0 +1,142 @@
# 和 jwt 集成
本插件的作用是让 Sa-Token 和 jwt 做一个整合。
---
### 1、引入依赖
首先在项目已经引入 Sa-Token 的基础上,继续添加:
``` xml
<!-- Sa-Token 整合 jwt -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-jwt</artifactId>
<version>${sa.top.version}</version>
</dependency>
```
### 2、配置秘钥
在 `application.yml` 配置文件中配置 jwt 生成秘钥:
``` yml
sa-token:
# jwt秘钥
jwt-secret-key: asdasdasifhueuiwyurfewbfjsdafjk
```
注:为了安全起见请不要直接复制官网示例这个字符串(随便按几个字符就好了)
### 3、注入jwt实现
根据不同的整合规则,插件提供了三种不同的模式,你需要 **选择其中一种** 注入到你的项目中
<!------------------------------ tabs:start ------------------------------>
<!-- tab: Style 模式 -->
Style 模式:Token 风格替换
``` java
@Configuration
public class SaTokenConfigure {
// Sa-Token 整合 jwt (Style模式)
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForStyle();
}
}
```
<!-- tab: Mix 模式 -->
Mix 模式:混入部分逻辑
``` java
@Configuration
public class SaTokenConfigure {
// Sa-Token 整合 jwt (Style模式)
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForMix();
}
}
```
<!-- tab: Stateless模式 -->
Stateless 模式:服务器完全无状态
``` java
@Configuration
public class SaTokenConfigure {
// Sa-Token 整合 jwt (Style模式)
@Bean
public StpLogic getStpLogicJwt() {
return new StpLogicJwtForStateless();
}
}
```
<!---------------------------- tabs:end ------------------------------>
### 4、开始使用
然后我们就可以像之前一样使用 Sa-Token 了
``` java
/**
* 登录测试
*/
@RestController
@RequestMapping("/acc/")
public class LoginController {
// 测试登录
@RequestMapping("login")
public SaResult login() {
StpUtil.login(10001);
return SaResult.ok("登录成功");
}
// 查询登录状态
@RequestMapping("isLogin")
public SaResult isLogin() {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 测试注销
@RequestMapping("logout")
public SaResult logout() {
StpUtil.logout();
return SaResult.ok();
}
}
```
访问上述接口,观察Token生成的样式
``` java
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpbklkIjoiMTAwMDEiLCJybiI6IjZYYzgySzBHVWV3Uk5NTTl1dFdjbnpFZFZHTVNYd3JOIn0.F_7fbHsFsDZmckHlGDaBuwDotZwAjZ0HB14DRujQfOQ
```
### 5、不同模式策略对比
注入不同模式会让框架具有不同的行为策略,以下是三种模式的差异点(为方便叙述,以下比较以同时引入 jwt 与 Redis 作为前提):
| 功能点 | Style 模式 | Mix 模式 | Stateless 模式 |
| :-------- | :-------- | :-------- | :-------- |
| Token风格 | jwt风格 | jwt风格 | jwt风格 |
| 登录数据存储 | Redis中 | Token中 | Token中 |
| Session存储 | Redis中 | Redis中 | 无Session |
| 注销下线 | 前后端双清数据 | 前后端双清数据 | 前端清除数据 |
| 踢人下线API | 支持 | 不支持 | 不支持 |
| 登录认证 | 支持 | 支持 | 支持 |
| 角色认证 | 支持 | 支持 | 支持 |
| 权限认证 | 支持 | 支持 | 支持 |
| timeout 有效期 | 支持 | 支持 | 支持 |
| activity-timeout 有效期 | 支持 | 支持 | 不支持 |
| id反查Token | 支持 | 支持 | 不支持 |
| 会话管理 | 支持 | 部分支持 | 不支持 |
| 注解鉴权 | 支持 | 支持 | 支持 |
| 账号封禁 | 支持 | 支持 | 不支持 |
| 身份切换 | 支持 | 支持 | 支持 |
| 二级认证 | 支持 | 支持 | 支持 |
| 模式总结 | Token风格替换 | jwt 与 Redis 逻辑混合 | 完全舍弃Redis,只用jwt |
+9
View File
@@ -0,0 +1,9 @@
# Sa-Token 插件开发指南
本插件的作用是让 Sa-Token 和 Dubbo 做一个整合。
---
+3 -3
View File
@@ -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保持不变
+3 -3
View File
@@ -19,9 +19,9 @@
| 系统架构 | 采用模式 | 简介 | 文档链接 |
| :-------- | :-------- | :-------- | :-------- |
| 前端同域 + 后端同 Redis | 模式一 | 共享 Cookie 同步会话 | [文档](/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso1) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso2-server) |
| 前端不同域 + 后端不同 Redis | 模式三 | Http请求获取会话 | [文档](/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/dev/sa-token-demo/sa-token-demo-sso3-server) |
| 前端同域 + 后端同 Redis | 模式一 | 共享 Cookie 同步会话 | [文档](/sso/sso-type1)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso1-client) |
| 前端不同域 + 后端同 Redis | 模式二 | URL重定向传播会话 | [文档](/sso/sso-type2)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso2-client) |
| 前端不同域 + 后端不同 Redis | 模式三 | Http请求获取会话 | [文档](/sso/sso-type3)、[示例](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-sso3-client) |
1. 前端同域:就是指多个系统可以部署在同一个主域名之下,比如:`c1.domain.com``c2.domain.com``c3.domain.com`
+1 -1
View File
@@ -30,7 +30,7 @@ public class H5Controller {
// 根据ticket进行登录
@RequestMapping("/doLoginByTicket")
public SaResult doLoginByTicket(String ticket) {
Object loginId = SaSsoHandle.checkTicket(ticket);
Object loginId = SaSsoHandle.checkTicket(ticket, "/doLoginByTicket");
if(loginId != null) {
StpUtil.login(loginId);
return SaResult.data(StpUtil.getTokenValue());
+1 -1
View File
@@ -198,7 +198,7 @@ public class SaSsoClientApplication {
至此,测试完毕!
可以看出,除了在`Client1`端我们需要手动登录一次之外,在`Client2端`和`Client3端`都是可以无需证,直接登录成功的。
可以看出,除了在`Client1`端我们需要手动登录一次之外,在`Client2端`和`Client3端`都是可以无需再次认证,直接登录成功的。
我们可以通过 F12控制台 Netword跟踪整个过程
+8
View File
@@ -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、无刷单点注销
+2
View File
@@ -102,6 +102,7 @@ implementation 'cn.dev33:sa-token-core:${sa.top.version}'
├── sa-token-alone-redis // [插件] Sa-Token 独立Redis插件,实现[权限缓存与业务缓存分离]
├── sa-token-oauth2 // [插件] Sa-Token 实现 OAuth2.0 模块
├── sa-token-dialect-thymeleaf // [插件] Sa-Token 标签方言(Thymeleaf版)
├── sa-token-jwt // [插件] Sa-Token 整合 jwt 登录认证
├── sa-token-demo // [示例] Sa-Token 示例合集
├── sa-token-demo-springboot // [示例] Sa-Token 整合 SpringBoot
├── sa-token-demo-webflux // [示例] Sa-Token 整合 WebFlux
@@ -110,6 +111,7 @@ implementation 'cn.dev33:sa-token-core:${sa.top.version}'
├── sa-token-demo-quick-login // [示例] Sa-Token 集成 quick-login 模块
├── sa-token-demo-alone-redis // [示例] Sa-Token 集成 alone-redis 模块
├── sa-token-demo-thymeleaf // [示例] Sa-Token 集成 Thymeleaf 标签方言
├── sa-token-demo-jwt // [示例] Sa-Token 集成 jwt 登录认证
├── sa-token-demo-sso-server // [示例] Sa-Token 集成 SSO单点登录-Server认证中心
├── sa-token-demo-sso1-client // [示例] Sa-Token 集成 SSO单点登录-模式一 应用端
├── sa-token-demo-sso2-client // [示例] Sa-Token 集成 SSO单点登录-模式二 应用端
+1 -1
View File
@@ -42,7 +42,7 @@ public class SaTokenConfigure {
.setAuth(obj -> {
System.out.println("---------- 进入Sa-Token全局认证 -----------");
// 登录证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
// 登录证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
SaRouter.match("/**", "/user/doLogin", () -> StpUtil.checkLogin());
// 更多拦截处理方式,请参考“路由拦截式鉴权”章节
+32 -4
View File
@@ -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` 比如:
@@ -156,3 +156,31 @@ public class StpUserUtil {
再次调用 `StpUserUtil.login(10001)` 进行登录授权时,token的名称将不再是 `satoken`,而是我们重写后的 `satoken-user`
### 7、不同体系不同 SaTokenConfig 配置
如果自定义的 StpUserUtil 需要使用不同 SaTokenConfig 对象, 也很简单,参考示例如下:
``` java
public class StpUserUtil {
// 使用匿名子类 重写`stpLogic对象`的一些方法
public static StpLogic stpLogic = new StpLogic("user") {
// 首先自定义一个 Config 对象
SaTokenConfig config = new SaTokenConfig()
.setTokenName("satoken")
.setTimeout(2592000)
// ... 其它set
;
// 然后重写 stpLogic 配置获取方法
@Override
public SaTokenConfig getConfig() {
return config;
}
};
// ...
}
```
+1 -1
View File
@@ -1,7 +1,7 @@
# [记住我] 模式
---
如图所示,一般网站的登录界面都会有一个 **`[记住我]`** 按钮,当你勾选它后,即你关闭浏览器再次打开网站,也依然会处于登录状态,无须重复验证密码
如图所示,一般网站的登录界面都会有一个 **`[记住我]`** 按钮,当你勾选它后,即使你关闭浏览器再次打开网站,也依然会处于登录状态,无须重复验证密码
![../static/login-view.png](https://oss.dev33.cn/sa-token/doc/login-view.png)
+2 -1
View File
@@ -94,7 +94,8 @@ PS:两者的区别在于:**`模式1会覆盖yml中的配置,模式2会与y
| jwtSecretKey | String | null | jwt秘钥 (只有集成 `sa-token-temp-jwt` 模块时此参数才会生效) |
| idTokenTimeout | long | 86400 | Id-Token的有效期 (单位: 秒) |
| basic | String | "" | Http Basic 认证的账号和密码 [参考:Http Basic 认证](/up/basic-auth) |
| currDomain | null | "" | 配置当前项目的网络访问地址 |
| currDomain | Boolean | false | 是否校验Id-Token(部分rpc插件有效) |
| checkIdToken | false | false | 配置当前项目的网络访问地址 |
| sso | Object | new SaSsoConfig() | SSO 单点登录相关配置 |
| cookie | Object | new SaCookieConfig() | Cookie配置对象 |
+4 -3
View File
@@ -67,8 +67,6 @@ public class StpInterfaceImpl implements StpInterface {
可参考代码:[码云:StpInterfaceImpl.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo/sa-token-demo-springboot/src/main/java/com/pj/satoken/StpInterfaceImpl.java)
<!-- todo: 缓存逻辑 -->
### 权限认证
@@ -114,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)
### 权限通配符
@@ -145,6 +144,8 @@ StpUtil.hasPermission("index.html"); // false
思路:如此精确的范围控制只依赖后端已经难以完成,此时需要前端进行一定的逻辑判断
如果是前后端一体项目,可以参考:[Thymeleaf 标签方言](/plugin/thymeleaf-extend),如果是前后端分离项目,则:
1. 在登录时,把当前账号拥有的所有权限码一次性返回给前端
2. 前端将权限码集合保存在`localStorage`或其它全局状态管理对象中
3. 在需要权限控制的按钮上,使用js进行逻辑判断,例如在`vue`框架中我们可以使用如下写法:
+6
View File
@@ -96,6 +96,12 @@ public class LoginController {
return SaResult.ok("是否登录:" + StpUtil.isLogin());
}
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
@RequestMapping("tokenInfo")
public SaResult tokenInfo() {
return SaResult.data(StpUtil.getTokenInfo());
}
// 测试注销 ---- http://localhost:8081/acc/logout
@RequestMapping("logout")
public SaResult logout() {
+1 -1
View File
@@ -2,7 +2,7 @@
---
假设我们有如下需求:
> 项目中所有接口均需要登录证,只有'登录接口'本身对外开放
> 项目中所有接口均需要登录证,只有'登录接口'本身对外开放
我们怎么实现呢?给每个接口加上鉴权注解?手写全局拦截器?似乎都不是非常方便。<br/>
在这个需求中我们真正需要的是一种基于路由拦截的鉴权模式, 那么在Sa-Token怎么实现路由拦截鉴权呢?
+45 -5
View File
@@ -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">
@@ -46,7 +46,7 @@
<div class="content-box">
<div class="fenge"></div>
<!-- <img class="title-logo" src="./doc/logo.png" onclick="alert('别点我, 快去点star')"> -->
<h1>Sa-Token<small>v1.27.0</small></h1>
<h1>Sa-Token<small>v1.28.0</small></h1>
<div class="sub-title">一个轻量级 java 权限认证框架,让鉴权变得简单、优雅!</div>
<div class="btn-box">
<a href="https://github.com/dromara/sa-token" target="_blank">GitHub</a>
@@ -257,6 +257,9 @@
<a href="http://www.dzlanke.cn/" target="_blank" title="德州蓝客网络科技">
<img src="https://oss.dev33.cn/sa-token/com/dezhoulanke.png">
</a>
<a href="http://www.turingoal.com" target="_blank" title="图灵谷(北京)科技有限公司">
<img src="https://oss.dev33.cn/sa-token/com/tulinggu.png">
</a>
</div>
<div style="height: 10px; clear: both;"></div>
@@ -308,7 +311,7 @@
<img src="https://oss.dev33.cn/sa-token/link/maxkey.png" msg="业界领先的身份管理和认证产品">
</a>
<a href="http://forest.dtflyx.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" msg="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端">
<img src="https://oss.dev33.cn/sa-token/link/forest-logo.png" msg="Forest能够帮助您使用更简单的方式编写Java的HTTP客户端" nf>
</a>
<a href="https://jpom.io/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/jpom.png" msg="一款简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件">
@@ -316,8 +319,9 @@
<a href="https://su.usthe.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/sureness.png" msg="面向 REST API 的高性能认证鉴权框架">
</a>
<a href="https://searcher.ejlchina.com/" target="_blank">
<img src="https://oss.dev33.cn/sa-token/link/bean-searcher.png" msg="轻量级关系数据库条件检索引擎,使一行代码实现复杂列表检索成为可能!">
</a>
</div>
<div style="height: 10px; clear: both;"></div>
</div>
@@ -438,6 +442,42 @@
content: content
});
})
// set 一下 img 的宽度
function setImgWidth(img) {
// 如果已经有了宽度参数,则不追加
if(img.src.indexOf('?') > -1) {
return;
}
// console.log(img.getAttribute('nf'));
if(img.getAttribute('nf') != null) {
return;
}
img.src = img.src + "?x-oss-process=image/resize,m_lfit,w_" + (img.width) + ",limit_0/auto-orient,0";
}
// set 一遍 img 的尺寸,防止失真
function f5ImgSize() {
$('.com-box-f .com-box a img').each(function() {
// console.log(this.src, this.width);
// 未加载完毕时,则等待其load之后再设置
if(this.complete == false) {
this.onload = function() {
setImgWidth(this);
}
return;
}
// 追加宽度参数
setImgWidth(this);
})
}
if(window.innerWidth > 1800) {
if(navigator.userAgent.indexOf('WebKit') > -1) {
f5ImgSize();
}
}
</script>
</body>
+3 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.27.0</version>
<version>1.28.0</version>
</parent>
<packaging>pom</packaging>
@@ -25,6 +25,8 @@
<module>sa-token-quick-login</module>
<module>sa-token-spring-aop</module>
<module>sa-token-temp-jwt</module>
<module>sa-token-jwt</module>
<module>sa-token-context-dubbo</module>
</modules>
<dependencies>
+1 -1
View File
@@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-plugin</artifactId>
<version>1.27.0</version>
<version>1.28.0</version>
</parent>
<packaging>jar</packaging>
@@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/
@@ -0,0 +1,41 @@
<?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.28.0</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-context-dubbo</name>
<artifactId>sa-token-context-dubbo</artifactId>
<description>sa-token-context-dubbo</description>
<dependencies>
<!-- sa-token-core -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>${sa-token-version}</version>
</dependency>
<!-- dubbo -->
<dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo</artifactId>
<version>2.7.11</version>
</dependency>
<!-- <dependency>
<groupId>org.apache.dubbo</groupId>
<artifactId>dubbo-spring-boot-starter</artifactId>
<version>2.7.11</version>
</dependency> -->
</dependencies>
</project>
@@ -0,0 +1,19 @@
package cn.dev33.satoken.context.dubbo;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.context.second.SaTokenSecondContextCreator;
/**
* Sa-Token 二级Context - 创建器 [Dubbo版]
*
* @author kong
*
*/
public class SaTokenSecondContextCreatorForDubbo implements SaTokenSecondContextCreator {
@Override
public SaTokenSecondContext create() {
return new SaTokenSecondContextForDubbo();
}
}
@@ -0,0 +1,47 @@
package cn.dev33.satoken.context.dubbo;
import org.apache.dubbo.rpc.RpcContext;
import cn.dev33.satoken.context.dubbo.model.SaRequestForDubbo;
import cn.dev33.satoken.context.dubbo.model.SaResponseForDubbo;
import cn.dev33.satoken.context.dubbo.model.SaStorageForDubbo;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.context.second.SaTokenSecondContext;
import cn.dev33.satoken.exception.ApiDisabledException;
/**
* Sa-Token 上下文 [Dubbo版本]
*
* @author kong
*
*/
public class SaTokenSecondContextForDubbo implements SaTokenSecondContext {
@Override
public SaRequest getRequest() {
return new SaRequestForDubbo(RpcContext.getContext());
}
@Override
public SaResponse getResponse() {
return new SaResponseForDubbo(RpcContext.getContext());
}
@Override
public SaStorage getStorage() {
return new SaStorageForDubbo(RpcContext.getContext());
}
@Override
public boolean matchPath(String pattern, String path) {
throw new ApiDisabledException();
}
@Override
public boolean isValid() {
return RpcContext.getContext() != null;
}
}
@@ -0,0 +1,48 @@
package cn.dev33.satoken.context.dubbo.filter;
import org.apache.dubbo.common.constants.CommonConstants;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.Filter;
import org.apache.dubbo.rpc.Invocation;
import org.apache.dubbo.rpc.Invoker;
import org.apache.dubbo.rpc.Result;
import org.apache.dubbo.rpc.RpcContext;
import org.apache.dubbo.rpc.RpcException;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.id.SaIdUtil;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaTokenConsts;
/**
*
* Sa-Token 整合 Dubbo Consumer端过滤器
*
* @author kong
*
*/
@Activate(group = {CommonConstants.CONSUMER}, order = -10000)
public class SaTokenDubboConsumerFilter implements Filter {
@Override
public Result invoke(Invoker<?> invoker, Invocation invocation) throws RpcException {
// 追加 Id-Token 参数
if(SaManager.getConfig().getCheckIdToken()) {
RpcContext.getContext().setAttachment(SaIdUtil.ID_TOKEN, SaIdUtil.getToken());
}
// 1. 调用前,向下传递会话Token
RpcContext.getContext().setAttachment(SaTokenConsts.JUST_CREATED, StpUtil.getTokenValueNotCut());
// 2. 开始调用
Result invoke = invoker.invoke(invocation);
// 3. 调用后,解析回传的Token值
StpUtil.setTokenValue(invoke.getAttachment(SaTokenConsts.JUST_CREATED_NOT_PREFIX));
// note
return invoke;
}
}

Some files were not shown because too many files have changed in this diff Show More