1
0
mirror of https://gitee.com/dromara/sa-token.git synced 2025-04-05 17:37:53 +08:00

v1.8.0更新

This commit is contained in:
shengzhang 2021-01-02 04:00:49 +08:00
parent 5ec35cce28
commit 2c6e656834
45 changed files with 1120 additions and 910 deletions

1
.gitignore vendored
View File

@ -8,5 +8,6 @@ unpackage/
.project
.factorypath
/.factorypath
.idea/

View File

@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://gitee.com/sz6/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150" style="margin-bottom: 10px;">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.7.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.8.0</h1>
<h4 align="center">一个JavaWeb轻量级权限认证框架功能全面上手简单</h4>
<h4 align="center">
<a href="https://gitee.com/sz6/sa-token/stargazers"><img src="https://gitee.com/sz6/sa-token/badge/star.svg"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.7.0-2B9939"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.8.0-2B9939"></a>
<a href="https://github.com/click33/sa-token/stargazers"><img src="https://img.shields.io/github/stars/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/watchers"><img src="https://img.shields.io/github/watchers/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/network/members"><img src="https://img.shields.io/github/forks/click33/sa-token"></a>
@ -28,21 +28,18 @@
## ⭐ sa-token是什么
- **sa-token是一个JavaWeb轻量级权限认证框架其API调用非常简单有多简单呢以登录验证为例你只需要**
**sa-token是一个JavaWeb轻量级权限认证框架其API调用非常简单有多简单呢以登录验证为例你只需要**
``` java
// 在登录时写入当前会话的账号id
StpUtil.setLoginId(10001);
StpUtil.setLoginId(10001);
// 然后在任意需要校验登录处调用以下API --- 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常
StpUtil.checkLogin();
```
- **然后在任意需要验证登录权限的地方:**
``` java
// 检测是否登录 --- 如果当前会话未登录,下面这句代码会抛出 `NotLoginException`异常
StpUtil.checkLogin();
```
- **没有复杂的封装!不要任何的配置!先写入,后鉴权!只需这两行简单的调用,即可轻松完成系统登录鉴权!**
**没有复杂的封装!不要任何的配置!只需这两行简单的调用,即可轻松完成系统登录鉴权!**
## 🔥 框架设计思想
@ -51,6 +48,24 @@ StpUtil.checkLogin();
- 功能强大:能涵盖的功能全部涵盖,不让你用个框架还要自己给框架打各种补丁
**如果上面的示例能够证明`sa-token`的简单那么以下API则可以证明`sa-token`的强大**
``` java
StpUtil.setLoginId(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.logoutByLoginId(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令牌值
```
**sa-token的API众多请恕此处无法为您逐一展示更多示例请戳官方在线文档**
## 💦️️ 涵盖功能
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 拦截违规调用,不同角色不同授权
@ -59,7 +74,7 @@ StpUtil.checkLogin();
- **模拟他人账号** —— 实时操作任意用户状态数据
- **持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件
- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
- **无cookie模式** —— APP、小程序等前后台分离场景
- **无Cookie模式** —— APP、小程序等前后台分离场景
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
- **花式token生成** —— 内置六种token风格还可自定义token生成策略
- **自动续签** —— 提供两种token过期策略灵活搭配使用还可自动续签

View File

@ -8,7 +8,7 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<packaging>pom</packaging>
<version>1.7.0</version>
<version>1.8.0</version>
<!-- 项目介绍 -->
<name>sa-token</name>
@ -21,6 +21,7 @@
<module>sa-token-core</module>
<module>sa-token-spring-boot-starter</module>
<module>sa-token-dao-redis</module>
<module>sa-token-dao-redis-jackson</module>
</modules>
<!-- 开源协议 apache 2.0 -->

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</parent>
<packaging>jar</packaging>

View File

@ -16,7 +16,10 @@ public class SaTokenConfig {
/** token临时有效期 (指定时间内无操作就视为token过期) 单位/秒, 默认-1 代表不限制 (例如可以设置为1800代表30分钟内无操作就过期) */
private long activityTimeout = -1;
/** 在多人登录同一账号时,是否共享会话 (为true时共用一个为false时新登录挤掉旧登录) */
/** 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) */
private Boolean allowConcurrentLogin = true;
/** 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) */
private Boolean isShare = true;
/** 是否尝试从请求体里读取token */
@ -34,14 +37,13 @@ public class SaTokenConfig {
/** 默认dao层实现类中每次清理过期数据间隔的时间 (单位: 秒) 默认值30秒设置为-1代表不启动定时清理 */
private int dataRefreshPeriod = 30;
/** 获取token专属session时是否必须登录 */
/** 获取token专属session时是否必须登录 (如果配置为true会在每次获取token专属session时校验是否登录) */
private Boolean tokenSessionCheckLogin = true;
/** 是否在初始化配置时打印版本字符画 */
private Boolean isV = true;
/**
* @return tokenName
*/
@ -69,7 +71,7 @@ public class SaTokenConfig {
public void setTimeout(long timeout) {
this.timeout = timeout;
}
/**
* @return activityTimeout
*/
@ -83,7 +85,21 @@ public class SaTokenConfig {
public void setActivityTimeout(long activityTimeout) {
this.activityTimeout = activityTimeout;
}
/**
* @return allowConcurrentLogin
*/
public Boolean getAllowConcurrentLogin() {
return allowConcurrentLogin;
}
/**
* @param allowConcurrentLogin 要设置的 allowConcurrentLogin
*/
public void setAllowConcurrentLogin(Boolean allowConcurrentLogin) {
this.allowConcurrentLogin = allowConcurrentLogin;
}
/**
* @return isShare
*/
@ -99,19 +115,19 @@ public class SaTokenConfig {
}
/**
* @return isReadCookie
* @return isReadBody
*/
public Boolean getIsReadCookie() {
return isReadCookie;
public Boolean getIsReadBody() {
return isReadBody;
}
/**
* @param isReadCookie 要设置的 isReadCookie
* @param isReadBody 要设置的 isReadBody
*/
public void setIsReadCookie(Boolean isReadCookie) {
this.isReadCookie = isReadCookie;
public void setIsReadBody(Boolean isReadBody) {
this.isReadBody = isReadBody;
}
/**
* @return isReadHead
*/
@ -127,19 +143,19 @@ public class SaTokenConfig {
}
/**
* @return isReadBody
* @return isReadCookie
*/
public Boolean getIsReadBody() {
return isReadBody;
public Boolean getIsReadCookie() {
return isReadCookie;
}
/**
* @param isReadBody 要设置的 isReadBody
* @param isReadCookie 要设置的 isReadCookie
*/
public void setIsReadBody(Boolean isReadBody) {
this.isReadBody = isReadBody;
public void setIsReadCookie(Boolean isReadCookie) {
this.isReadCookie = isReadCookie;
}
/**
* @return tokenStyle
*/
@ -153,20 +169,6 @@ public class SaTokenConfig {
public void setTokenStyle(String tokenStyle) {
this.tokenStyle = tokenStyle;
}
/**
* @return isV
*/
public Boolean getIsV() {
return isV;
}
/**
* @param isV 要设置的 isV
*/
public void setIsV(Boolean isV) {
this.isV = isV;
}
/**
* @return dataRefreshPeriod
@ -196,20 +198,31 @@ public class SaTokenConfig {
this.tokenSessionCheckLogin = tokenSessionCheckLogin;
}
/**
* @return isV
*/
public Boolean getIsV() {
return isV;
}
/**
* 将对象转为String字符串
* @param isV 要设置的 isV
*/
public void setIsV(Boolean isV) {
this.isV = isV;
}
@Override
public String toString() {
return "SaTokenConfig [tokenName=" + tokenName + ", timeout=" + timeout + ", activityTimeout=" + activityTimeout
+ ", isShare=" + isShare + ", isReadBody=" + isReadBody + ", isReadHead=" + isReadHead
+ ", isReadCookie=" + isReadCookie + ", tokenStyle=" + tokenStyle + ", dataRefreshPeriod="
+ dataRefreshPeriod + ", tokenSessionCheckLogin=" + tokenSessionCheckLogin + ", isV=" + isV + "]";
+ ", allowConcurrentLogin=" + allowConcurrentLogin + ", isShare=" + isShare + ", isReadBody="
+ isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle="
+ tokenStyle + ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin="
+ tokenSessionCheckLogin + ", isV=" + isV + "]";
}

View File

@ -52,6 +52,13 @@ public interface SaTokenDao {
*/
public long getTimeout(String key);
/**
* 修改指定key的剩余存活时间 (单位: )
* @param key 指定key
* @param timeout 过期时间
*/
public void updateTimeout(String key, long timeout);
/**
* 根据指定key的Session如果没有则返回空
@ -80,12 +87,19 @@ public interface SaTokenDao {
public void deleteSession(String sessionId);
/**
* 获取指定SaSession的剩余存活时间 (单位: )
* 获取指定SaSession的剩余存活时间 (单位: )
* @param sessionId 指定SaSession
* @return 这个SaSession的剩余存活时间 (单位: )
*/
public long getSessionTimeout(String sessionId);
/**
* 修改指定SaSession的剩余存活时间 (单位: )
* @param sessionId sessionId
* @param timeout 过期时间
*/
public void updateSessionTimeout(String sessionId, long timeout);

View File

@ -70,6 +70,11 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
return getKeyTimeout(key);
}
@Override
public void updateTimeout(String key, long timeout) {
expireMap.put(key, System.currentTimeMillis() + timeout * 1000);
}
// ------------------------ Session 读写操作
@ -104,8 +109,13 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
return getKeyTimeout(sessionId);
}
@Override
public void updateSessionTimeout(String sessionId, long timeout) {
expireMap.put(sessionId, System.currentTimeMillis() + timeout * 1000);
}
// ------------------------ Session 读写操作
// ------------------------ 过期时间相关操作
/**
* 如果指定key已经过期则立即清除它
@ -193,6 +203,11 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
public void endRefreshTimer() {
this.refreshTimer.cancel();
}

View File

@ -1,9 +1,11 @@
package cn.dev33.satoken.session;
import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaTokenManager;
@ -17,21 +19,19 @@ public class SaSession implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 会话id
*/
/** 此会话的id */
private String id;
/**
* 当前会话创建时间
*/
/** 此会话的创建时间 */
private long createTime;
/** 此会话的所有数据 */
private Map<String, Object> dataMap = new ConcurrentHashMap<String, Object>();
/**
* 当前会话键值对
* 构建一个 session对象
*/
private Map<String, Object> dataMap;
public SaSession() {}
/**
* 构建一个 session对象
@ -39,13 +39,12 @@ public class SaSession implements Serializable {
*/
public SaSession(String id) {
this.id = id;
this.createTime = System.currentTimeMillis();
this.dataMap = new HashMap<String, Object>();
this.createTime = System.currentTimeMillis();
}
/**
* 获取会话id
* @return id
* 获取会话id
* @return 此会话的id
*/
public String getId() {
return id;
@ -58,6 +57,67 @@ public class SaSession implements Serializable {
public long getCreateTime() {
return createTime;
}
// ----------------------- tokenSign相关
/**
* 本session绑定的token签名列表
*/
private List<TokenSign> tokenSignList = new Vector<TokenSign>();
/**
* 返回token签名列表
* @return token签名列表
*/
public List<TokenSign> getTokenSignList() {
return new Vector<>(tokenSignList);
}
/**
* 查找一个token签名
* @param tokenValue token值
* @return 查找到的tokenSign
*/
public TokenSign getTokenSign(String tokenValue) {
for (TokenSign tokenSign : getTokenSignList()) {
if(tokenSign.getValue().equals(tokenValue)){
return tokenSign;
}
}
return null;
}
/**
* 添加一个token签名
* @param tokenSign token签名
*/
public void addTokenSign(TokenSign tokenSign) {
// 如果已经存在于列表中则无需再次添加
for (TokenSign tokenSign2 : getTokenSignList()) {
if(tokenSign2.getValue().equals(tokenSign.getValue())){
return;
}
}
// 添加并更新
tokenSignList.add(tokenSign);
update();
}
/**
* 移除一个token签名
* @param tokenValue token名称
*/
public void removeTokenSign(String tokenValue) {
TokenSign tokenSign = getTokenSign(tokenValue);
if(tokenSignList.remove(tokenSign)) {
update();
}
}
// ----------------------- 存取值
/**
* 写入一个值
@ -92,7 +152,6 @@ public class SaSession implements Serializable {
return defaultValue;
}
/**
* 移除一个值
* @param key 要移除的值的名字
@ -123,7 +182,7 @@ public class SaSession implements Serializable {
* 返回当前session会话所有key
* @return 所有值的key列表
*/
public Set<String> getAttributeKeys() {
public Set<String> attributeKeys() {
return dataMap.keySet();
}
@ -135,6 +194,10 @@ public class SaSession implements Serializable {
return dataMap;
}
// ----------------------- 一些操作
/**
* 将这个session从持久库更新一下
*/
@ -142,12 +205,17 @@ public class SaSession implements Serializable {
SaTokenManager.getSaTokenDao().updateSession(this);
}
// /** 注销会话(注销后此session会话将不再存储服务器上) */
// public void logout() {
// SaTokenManager.getDao().delSaSession(this.id);
// }
/** 注销会话(注销后此session会话将不再存储服务器上) */
public void logout() {
SaTokenManager.getSaTokenDao().deleteSession(this.id);
}
/** 如果这个token的tokenSign数量为零则直接注销会话 */
public void logoutByTokenSignCountToZero() {
if(tokenSignList.size() == 0) {
logout();
}
}
}

View File

@ -14,7 +14,11 @@ public class SaSessionCustomUtil {
*/
public static String sessionKey = "custom";
/** 组织一下key */
/**
* 组织一下自定义session的id
* @param sessionId 会话id
* @return sessionId
*/
public static String getSessionKey(String sessionId) {
return SaTokenManager.getConfig().getTokenName() + ":" + sessionKey + ":session:" + sessionId;
}

View File

@ -0,0 +1,68 @@
package cn.dev33.satoken.session;
import java.io.Serializable;
/**
* 挂在到SaSession上的token签名
* @author kong
*
*/
public class TokenSign implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1406115065849845073L;
/**
* token值
*/
private String value;
/**
* 所在设备标识
*/
private String device;
/** 构建一个 */
public TokenSign() {}
/**
* 构建一个
* @param value token值
* @param device 所在设备标识
*/
public TokenSign(String value, String device) {
this.value = value;
this.device = device;
}
/**
* @return value
*/
public String getValue() {
return value;
}
/**
* @return device
*/
public String getDevice() {
return device;
}
@Override
public String toString() {
return "TokenSign [value=" + value + ", device=" + device + "]";
}
}

View File

@ -162,6 +162,7 @@ public class SaTokenInfo {
/**
* @param tokenSessionTimeout 要设置的 tokenSessionTimeout
* @return 对象自身
*/
public SaTokenInfo setTokenSessionTimeout(long tokenSessionTimeout) {
this.tokenSessionTimeout = tokenSessionTimeout;

View File

@ -1,5 +1,7 @@
package cn.dev33.satoken.stp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
@ -13,6 +15,8 @@ import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.TokenSign;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
@ -52,10 +56,11 @@ public class StpLogic {
* @param loginId loginId
* @return 生成的tokenValue
*/
public String randomTokenValue(Object loginId) {
return SaTokenManager.getSaTokenAction().createToken(loginId, loginKey);
public String createTokenValue(Object loginId) {
// 去除掉所有逗号
return SaTokenManager.getSaTokenAction().createToken(loginId, loginKey).replaceAll(",", "");
}
/**
* 获取当前tokenValue
* @return 当前tokenValue
@ -68,8 +73,8 @@ public class StpLogic {
String tokenValue = null;
// 1. 尝试从request里读取
if(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY) != null) {
tokenValue = String.valueOf(request.getAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY));
if(request.getAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY) != null) {
tokenValue = String.valueOf(request.getAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY));
}
// 2. 尝试从请求体里面读取
if(tokenValue == null && config.getIsReadBody() == true){
@ -91,15 +96,6 @@ public class StpLogic {
return tokenValue;
}
/**
* 获取指定loginId的tokenValue
* @param loginId 账号id
* @return token值
*/
public String getTokenValueByLoginId(Object loginId) {
return SaTokenManager.getSaTokenDao().getValue(getKeyLoginId(loginId));
}
/**
* 获取当前StpLogin的loginKey
* @return 当前StpLogin的loginKey
@ -135,36 +131,64 @@ public class StpLogic {
*/
public void setLoginId(Object loginId) {
// 1获取相应对象
// ------ 0如果当前会话已经登录上了此LoginId则立即返回
Object loggedId = getLoginIdDefaultNull();
if(loggedId != null && loggedId.toString().equals(loginId.toString())) {
return;
}
// ------ 1获取相应对象
HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest();
SaTokenConfig config = getConfig();
SaTokenDao dao = SaTokenManager.getSaTokenDao();
// 2获取tokenValue
String tokenValue = getTokenValueByLoginId(loginId); // 获取旧tokenValue
if(tokenValue == null){ // 为null则创建一个新的
tokenValue = randomTokenValue(loginId);
// ------ 2生成一个token
String tokenValue = null;
// --- 如果允许并发登录
if(config.getAllowConcurrentLogin() == true) {
// 如果配置为共享token, 则尝试从session签名记录里取出token
if(config.getIsShare() == true) {
tokenValue = getTokenValueByLoginId(loginId);
}
} else {
// 不为null, 并且配置不共享会话将原来的会话标记为[被顶替]
if(config.getIsShare() == false){
dao.updateValue(getKeyTokenValue(tokenValue), NotLoginException.BE_REPLACED);
clearLastActivity(tokenValue); // 同时清理掉[最后操作时间]
tokenValue = randomTokenValue(loginId); // 再重新生成一个token
// --- 如果不允许并发登录
// 如果此时[id-session]不为null说明此账号在其他地正在登录现在需要先把其它地的token标记为被顶下线
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
List<String> tokenValueList = getTokenValueListByLoginId(loginId);
for (String token : tokenValueList) {
dao.updateValue(getKeyTokenValue(token), NotLoginException.BE_REPLACED); // 1. 将此token 标记为已顶替
clearLastActivity(token); // 2. 清理掉[token-最后操作时间]
session.removeTokenSign(token); // 3. 清理账号session上的token签名
}
}
}
// 3持久化
// 如果至此仍未成功创建tokenValue
if(tokenValue == null) {
tokenValue = createTokenValue(loginId);
}
// ------ 3. 获取[id-session] (如果还没有创建session, 则新建, 如果已经创建则续期)
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
session = getSessionByLoginId(loginId);
} else {
dao.updateSessionTimeout(session.getId(), config.getTimeout());
}
// 在session上记录token签名
session.addTokenSign(new TokenSign(tokenValue, SaTokenConsts.DEFAULT_LOGIN_DEVICE));
// ------ 4. 持久化其它数据
dao.setValue(getKeyTokenValue(tokenValue), String.valueOf(loginId), config.getTimeout()); // token -> uid
dao.setValue(getKeyLoginId(loginId), tokenValue, config.getTimeout()); // uid -> token
request.setAttribute(SaTokenInsideUtil.JUST_CREATED_SAVE_KEY, tokenValue); // 保存到本次request里
setLastActivityToNow(tokenValue); // 写入 [最后操作时间]
if(config.getIsReadCookie() == true){
SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue, "/", (int)config.getTimeout()); // cookie注入
request.setAttribute(SaTokenConsts.JUST_CREATED_SAVE_KEY, tokenValue); // 将token保存到本次request里
setLastActivityToNow(tokenValue); // 写入 [最后操作时间]
if(config.getIsReadCookie() == true){ // cookie注入
SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue, "/", (int)config.getTimeout());
}
}
/**
* 当前会话注销登录
* 当前会话注销登录
*/
public void logout() {
// 如果连token都没有那么无需执行任何操作
@ -176,57 +200,63 @@ public class StpLogic {
if(getConfig().getIsReadCookie() == true){
SaTokenManager.getSaTokenCookie().delCookie(SaTokenManager.getSaTokenServlet().getRequest(), SaTokenManager.getSaTokenServlet().getResponse(), getTokenName());
}
// 尝试从db中获取loginId值
String loginId = SaTokenManager.getSaTokenDao().getValue(getKeyTokenValue(tokenValue));
// 如果根本查不到loginId那么也无需执行任何操作
if(loginId == null) {
return;
}
// 如果已过期或被顶替或被挤下线那么只删除此token即可
if(loginId.equals(NotLoginException.TOKEN_TIMEOUT) || loginId.equals(NotLoginException.BE_REPLACED) || loginId.equals(NotLoginException.KICK_OUT)) {
return;
}
// 至此已经是一个正常的loginId开始三清
logoutByLoginId(loginId);
logoutByTokenValue(tokenValue);
}
/**
* 指定loginId的会话注销登录正常注销下线
* 指定token的会话注销登录
* @param tokenValue 指定token
*/
public void logoutByTokenValue(String tokenValue) {
// 1. 清理掉[token-最后操作时间]
clearLastActivity(tokenValue);
// 2. 尝试清除token-id键值对 (先从db中获取loginId值如果根本查不到loginId那么无需继续操作 )
String loginId = SaTokenManager.getSaTokenDao().getValue(getKeyTokenValue(tokenValue));
if(loginId == null || NotLoginException.ABNORMAL_LIST.contains(loginId)) {
return;
}
SaTokenManager.getSaTokenDao().deleteKey(getKeyTokenValue(tokenValue));
// 2. 尝试清理账号session上的token签名 (如果为null或已被标记为异常, 那么无需继续执行 )
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
session.removeTokenSign(tokenValue);
// 3. 尝试注销session
session.logoutByTokenSignCountToZero();
}
/**
* 指定loginId的会话注销登录踢人下线
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-2
* @param loginId 账号id
*/
public void logoutByLoginId(Object loginId) {
// 获取相应tokenValue
String tokenValue = getTokenValueByLoginId(loginId);
if(tokenValue == null) {
// 先获取这个账号的[id-session], 如果为null则不执行任何操作
SaSession session = getSessionByLoginId(loginId);
if(session == null) {
return;
}
// 清除相关数据
SaTokenManager.getSaTokenDao().deleteKey(getKeyTokenValue(tokenValue)); // 清除token-id键值对
SaTokenManager.getSaTokenDao().deleteKey(getKeyLoginId(loginId)); // 清除id-token键值对
SaTokenManager.getSaTokenDao().deleteSession(getKeySession(loginId)); // 清除其session
clearLastActivity(tokenValue); // 同时清理掉 [最后操作时间]
// 循环token签名列表开始删除相关信息
List<TokenSign> tokenSignList = session.getTokenSignList();
for (TokenSign tokenSign : tokenSignList) {
// 1. 获取token
String tokenValue = tokenSign.getValue();
// 2. 清理掉[token-最后操作时间]
clearLastActivity(tokenValue);
// 3. 标记已被踢下线
SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记已被踢下线
// 4. 清理账号session上的token签名
session.removeTokenSign(tokenValue);
}
// 尝试注销session
session.logoutByTokenSignCountToZero();
}
/**
* 指定loginId的会话注销登录踢人下线
* @param loginId 账号id
*/
public void kickoutByLoginId(Object loginId) {
// 获取相应tokenValue
String tokenValue = getTokenValueByLoginId(loginId);
if(tokenValue == null) {
return;
}
// 清除相关数据
SaTokenManager.getSaTokenDao().updateValue(getKeyTokenValue(tokenValue), NotLoginException.KICK_OUT); // 标记已被踢下线
SaTokenManager.getSaTokenDao().deleteKey(getKeyLoginId(loginId)); // 清除id-token键值对
SaTokenManager.getSaTokenDao().deleteSession(getKeySession(loginId)); // 清除其session
clearLastActivity(tokenValue); // 同时清理掉 [最后操作时间]
}
// 查询相关
@ -454,7 +484,6 @@ public class StpLogic {
/**
* 获取当前token的专属-session如果session尚未创建isCreate代表是否新建并返回
* <p> 只有当前会话属于登录状态才可调用
* @param isCreate 是否新建
* @return session会话
*/
@ -474,8 +503,7 @@ public class StpLogic {
}
/**
* 获取当前token的专属-session如果session尚未创建则新建并返回
* <p> 只有当前会话属于登录状态才可调用
* 获取当前token的专属-session如果session尚未创建则新建并返回
* @return session会话
*/
public SaSession getTokenSession() {
@ -510,7 +538,7 @@ public class StpLogic {
// 删除[最后操作时间]
SaTokenManager.getSaTokenDao().deleteKey(getKeyLastActivityTime(tokenValue));
// 清除标记
SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY);
SaTokenManager.getSaTokenServlet().getRequest().removeAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY);
}
/**
@ -524,7 +552,7 @@ public class StpLogic {
}
// 如果本次请求已经有了[检查标记], 则立即返回
HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest();
if(request.getAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) {
if(request.getAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) {
return;
}
// ------------ 验证是否已经 [临时过期]
@ -541,7 +569,7 @@ public class StpLogic {
// --- 至此验证已通过
// 打上[检查标记]标记一下当前请求已经通过验证避免一次请求多次验证造成不必要的性能消耗
request.setAttribute(SaTokenInsideUtil.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true);
request.setAttribute(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY, true);
}
/**
@ -795,7 +823,43 @@ public class StpLogic {
throw new NotPermissionException(permissionArray[0], this.loginKey); // 没有权限抛出异常
}
}
// =================== id 反查token 相关操作 ===================
/**
* 获取指定loginId的tokenValue
* <p> 在配置为允许并发登录时此方法只会返回队列的最后一个token
* 如果你需要返回此账号id的所有token请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @return token值
*/
public String getTokenValueByLoginId(Object loginId) {
List<String> tokenValueList = getTokenValueListByLoginId(loginId);
return tokenValueList.size() == 0 ? null : tokenValueList.get(tokenValueList.size() - 1);
}
/**
* 获取指定loginId的tokenValue
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
public List<String> getTokenValueListByLoginId(Object loginId) {
// 如果session为null的话直接返回空集合
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return Arrays.asList();
}
// 遍历解析
List<TokenSign> tokenSignList = session.getTokenSignList();
List<String> tokenValueList = new ArrayList<>();
for (TokenSign tokenSign : tokenSignList) {
tokenValueList.add(tokenSign.getValue());
}
return tokenValueList;
}
// =================== 返回相应key ===================
@ -807,21 +871,13 @@ public class StpLogic {
return getConfig().getTokenName();
}
/**
* 获取key tokenValue 持久化
* 获取key tokenValue 持久化 token-id
* @param tokenValue token值
* @return key
*/
public String getKeyTokenValue(String tokenValue) {
return getConfig().getTokenName() + ":" + loginKey + ":token:" + tokenValue;
}
/**
* 获取key id 持久化
* @param loginId 账号id
* @return key
*/
public String getKeyLoginId(Object loginId) {
return getConfig().getTokenName() + ":" + loginKey + ":id:" + loginId;
}
/**
* 获取key session 持久化
* @param loginId 账号id
@ -846,9 +902,13 @@ public class StpLogic {
public String getKeyLastActivityTime(String tokenValue) {
return getConfig().getTokenName() + ":" + loginKey + ":last-activity:" + tokenValue;
}
// =================== Bean对象代理 ===================
/**
* 返回配置对象
* @return 配置对象
*/
public SaTokenConfig getConfig() {
// 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利
@ -856,4 +916,8 @@ public class StpLogic {
}
}

View File

@ -1,5 +1,7 @@
package cn.dev33.satoken.stp;
import java.util.List;
import cn.dev33.satoken.session.SaSession;
/**
@ -32,15 +34,6 @@ public class StpUtil {
return stpLogic.getTokenValue();
}
/**
* 获取指定loginId的tokenValue
* @param loginId 账号id
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
/**
* 获取当前StpLogin的loginKey
* @return 当前StpLogin的loginKey
@ -76,20 +69,22 @@ public class StpUtil {
}
/**
* 指定loginId的会话注销登录正常注销下线
* 指定token的会话注销登录
* @param tokenValue 指定token
*/
public static void logoutByTokenValue(String tokenValue) {
stpLogic.logoutByTokenValue(tokenValue);
}
/**
* 指定loginId的会话注销登录踢人下线
* <p> 当对方再次访问系统时会抛出NotLoginException异常场景值=-2
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
stpLogic.logoutByLoginId(loginId);
}
/**
* 指定loginId的会话注销登录踢人下线
* @param loginId 账号id
*/
public static void kickoutByLoginId(Object loginId) {
stpLogic.kickoutByLoginId(loginId);
}
// 查询相关
@ -181,10 +176,9 @@ public class StpUtil {
}
/**
* 获取指定loginId的session, 如果session尚未创建isCreate=是否新建并返回
* @param loginId 账号id
* @param isCreate 是否新建
* @return SaSession
* 获取指定loginId的session如果session尚未创建则新建并返回
* @param loginId 账号id
* @return session会话
*/
public static SaSession getSessionByLoginId(Object loginId) {
return stpLogic.getSessionByLoginId(loginId);
@ -221,7 +215,6 @@ public class StpUtil {
/**
* 获取当前token的专属-session如果session尚未创建则新建并返回
* <p> 只有当前会话属于登录状态才可调用
* @return session会话
*/
public static SaSession getTokenSession() {
@ -376,4 +369,27 @@ public class StpUtil {
}
// =================== id 反查token 相关操作 ===================
/**
* 获取指定loginId的tokenValue
* <p> 在配置为允许并发登录时此方法只会返回队列的最后一个token
* 如果你需要返回此账号id的所有token请调用 getTokenValueListByLoginId
* @param loginId 账号id
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
/**
* 获取指定loginId的tokenValue
* @param loginId 账号id
* @return 此loginId的所有相关token
*/
public static List<String> getTokenValueListByLoginId(Object loginId) {
return stpLogic.getTokenValueListByLoginId(loginId);
}
}

View File

@ -0,0 +1,45 @@
package cn.dev33.satoken.util;
/**
* 定义sa-token的所有常量
* @author kong
*
*/
public class SaTokenConsts {
/**
* sa-token 版本号
*/
public static final String VERSION_NO = "v1.8.0";
/**
* sa-token 开源地址
*/
public static final String GITHUB_URL = "https://github.com/click33/sa-token";
/**
* 如果token为本次请求新创建的则以此字符串为key存储在当前request中 JUST_CREATED_SAVE_KEY
*/
public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_";
/**
* 如果本次请求已经验证过[无操作过期], 则以此值存储在当前request中 TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY
*/
public static final String TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY = "TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY_";
/**
* 在登录时默认使用的设备名称
*/
public static final String DEFAULT_LOGIN_DEVICE = "default-device";
// /**
// * 在用一个字符串存储多个token时所使用的分隔符
// */
// public static final String MULTIPLE_TOKEN_SEPARATOR = ",";
}

View File

@ -10,16 +10,6 @@ import java.util.Random;
public class SaTokenInsideUtil {
/**
* sa-token 版本号
*/
public static final String VERSION_NO = "v1.7.0";
/**
* sa-token 开源地址
*/
public static final String GITHUB_URL = "https://github.com/click33/sa-token";
/**
* 打印 sa-token 版本字符画
*/
@ -28,20 +18,11 @@ public class SaTokenInsideUtil {
"____ ____ ___ ____ _ _ ____ _ _ \r\n" +
"[__ |__| __ | | | |_/ |___ |\\ | \r\n" +
"___] | | | |__| | \\_ |___ | \\| \r\n" +
"sa-token" + VERSION_NO + " \r\n" +
"GitHub" + GITHUB_URL; // + "\r\n";
"sa-token" + SaTokenConsts.VERSION_NO + " \r\n" +
"GitHub" + SaTokenConsts.GITHUB_URL; // + "\r\n";
System.out.println(str);
}
/**
* 如果token为本次请求新创建的则以此字符串为key存储在当前request中 JUST_CREATED_SAVE_KEY
*/
public static final String JUST_CREATED_SAVE_KEY = "JUST_CREATED_SAVE_KEY_";
/**
* 如果本次请求已经验证过[无操作过期], 则以此值存储在当前request中 TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY
*/
public static final String TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY = "TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY_";
/**
* 生成指定长度的随机字符串
@ -61,6 +42,7 @@ public class SaTokenInsideUtil {
/**
* 以当前时间戳和随机int数字拼接一个随机字符串
* @return 随机字符串
*/
public static String getMarking28() {
return System.currentTimeMillis() + "" + new Random().nextInt(Integer.MAX_VALUE);

12
sa-token-dao-redis-jackson/.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
target/
node_modules/
bin/
.settings/
unpackage/
.classpath
.project
.factorypath
.idea/

View File

@ -0,0 +1,35 @@
<?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-parent</artifactId>
<version>1.8.0</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-dao-redis-jackson</name>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<description>sa-token integrate redis (to jackson)</description>
<dependencies>
<!-- sa-token-spring-boot-starter -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.8.0</version>
</dependency>
<!-- RedisTemplate 相关操作API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.7.RELEASE</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,185 @@
package cn.dev33.satoken.dao;
import java.lang.reflect.Field;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token持久层的实现类, 基于redis (to jackson)
*/
@Component
public class SaTokenDaoRedisJackson implements SaTokenDao {
/**
* ObjectMapper对象 (以public作用于暴露出此对象方便开发者二次更改配置)
*/
public ObjectMapper objectMapper;
/**
* string专用
*/
@Autowired
public StringRedisTemplate stringRedisTemplate;
/**
* SaSession专用
*/
public RedisTemplate<String, SaSession> sessionRedisTemplate;
@Autowired
public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) {
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
GenericJackson2JsonRedisSerializer valueSerializer = new GenericJackson2JsonRedisSerializer();
// 通过反射获取Mapper对象, 配置[忽略未知字段], 增强兼容性
try {
Field field = GenericJackson2JsonRedisSerializer.class.getDeclaredField("mapper");
field.setAccessible(true);
ObjectMapper objectMapper = (ObjectMapper) field.get(valueSerializer);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
this.objectMapper = objectMapper;
} catch (Exception e) {
System.err.println(e.getMessage());
}
// 构建RedisTemplate
RedisTemplate<String, SaSession> template = new RedisTemplate<String, SaSession>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
if(this.sessionRedisTemplate == null) {
this.sessionRedisTemplate = template;
}
}
/**
* 根据key获取value如果没有则返回空
*/
@Override
public String getValue(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
/**
* 写入指定key-value键值对并设定过期时间(单位)
*/
@Override
public void setValue(String key, String value, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
stringRedisTemplate.opsForValue().set(key, value);
} else {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
}
/**
* 修改指定key-value键值对 (过期时间取原来的值)
*/
@Override
public void updateValue(String key, String value) {
long expire = getTimeout(key);
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键
return;
}
this.setValue(key, value, expire);
}
/**
* 删除一个指定的key
*/
@Override
public void deleteKey(String key) {
stringRedisTemplate.delete(key);
}
/**
* 根据key获取value如果没有则返回空
*/
@Override
public long getTimeout(String key) {
return stringRedisTemplate.getExpire(key);
}
/**
* 修改指定key的剩余存活时间 (单位: )
*/
@Override
public void updateTimeout(String key, long timeout) {
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 根据指定key的Session如果没有则返回空
*/
@Override
public SaSession getSession(String sessionId) {
return sessionRedisTemplate.opsForValue().get(sessionId);
}
/**
* 将指定Session持久化
*/
@Override
public void saveSession(SaSession session, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
sessionRedisTemplate.opsForValue().set(session.getId(), session);
} else {
sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS);
}
}
/**
* 更新指定session
*/
@Override
public void updateSession(SaSession session) {
long expire = getSessionTimeout(session.getId());
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键
return;
}
this.saveSession(session, expire);
}
/**
* 删除一个指定的session
*/
@Override
public void deleteSession(String sessionId) {
sessionRedisTemplate.delete(sessionId);
}
/**
* 获取指定SaSession的剩余存活时间 (单位: )
*/
@Override
public long getSessionTimeout(String sessionId) {
return sessionRedisTemplate.getExpire(sessionId);
}
/**
* 修改指定SaSession的剩余存活时间 (单位: )
*/
@Override
public void updateSessionTimeout(String sessionId, long timeout) {
sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS);
}
}

View File

@ -0,0 +1 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.dev33.satoken.dao.SaTokenDaoRedisJackson

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</parent>
<packaging>jar</packaging>
@ -20,14 +20,14 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency>
<!-- RedisTemplate 相关操作API -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.3.7.RELEASE</version>
</dependency>
<!-- spring-boot-starter-redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>RELEASE</version>
</dependency>
</dependencies>

View File

@ -3,6 +3,7 @@ package cn.dev33.satoken.dao;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
@ -21,18 +22,28 @@ public class SaTokenDaoRedis implements SaTokenDao {
* string专用
*/
@Autowired
StringRedisTemplate stringRedisTemplate;
public StringRedisTemplate stringRedisTemplate;
/**
* SaSession专用
*/
RedisTemplate<String, SaSession> redisTemplate;
public RedisTemplate<String, SaSession> sessionRedisTemplate;
@Autowired
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setRedisTemplate(RedisTemplate redisTemplate) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
this.redisTemplate = redisTemplate;
public void setSessionRedisTemplate(RedisConnectionFactory connectionFactory) {
// 指定相应的序列化方案
StringRedisSerializer keySerializer = new StringRedisSerializer();
JdkSerializationRedisSerializer valueSerializer = new JdkSerializationRedisSerializer();
// 构建RedisTemplate
RedisTemplate<String, SaSession> template = new RedisTemplate<String, SaSession>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(keySerializer);
template.setHashKeySerializer(keySerializer);
template.setValueSerializer(valueSerializer);
template.setHashValueSerializer(valueSerializer);
template.afterPropertiesSet();
if(this.sessionRedisTemplate == null) {
this.sessionRedisTemplate = template;
}
}
@ -58,7 +69,7 @@ public class SaTokenDaoRedis implements SaTokenDao {
}
/**
* 根据key获取value如果没有则返回空
* 修改指定key-value键值对 (过期时间取原来的值)
*/
@Override
public void updateValue(String key, String value) {
@ -70,7 +81,7 @@ public class SaTokenDaoRedis implements SaTokenDao {
}
/**
* 根据key获取value如果没有则返回空
* 删除一个指定的key
*/
@Override
public void deleteKey(String key) {
@ -84,6 +95,15 @@ public class SaTokenDaoRedis implements SaTokenDao {
public long getTimeout(String key) {
return stringRedisTemplate.getExpire(key);
}
/**
* 修改指定key的剩余存活时间 (单位: )
*/
@Override
public void updateTimeout(String key, long timeout) {
stringRedisTemplate.expire(key, timeout, TimeUnit.SECONDS);
}
/**
@ -91,7 +111,7 @@ public class SaTokenDaoRedis implements SaTokenDao {
*/
@Override
public SaSession getSession(String sessionId) {
return redisTemplate.opsForValue().get(sessionId);
return sessionRedisTemplate.opsForValue().get(sessionId);
}
/**
@ -101,9 +121,9 @@ public class SaTokenDaoRedis implements SaTokenDao {
public void saveSession(SaSession session, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
redisTemplate.opsForValue().set(session.getId(), session);
sessionRedisTemplate.opsForValue().set(session.getId(), session);
} else {
redisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS);
sessionRedisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS);
}
}
@ -124,7 +144,7 @@ public class SaTokenDaoRedis implements SaTokenDao {
*/
@Override
public void deleteSession(String sessionId) {
redisTemplate.delete(sessionId);
sessionRedisTemplate.delete(sessionId);
}
/**
@ -132,7 +152,15 @@ public class SaTokenDaoRedis implements SaTokenDao {
*/
@Override
public long getSessionTimeout(String sessionId) {
return redisTemplate.getExpire(sessionId);
return sessionRedisTemplate.getExpire(sessionId);
}
/**
* 修改指定SaSession的剩余存活时间 (单位: )
*/
@Override
public void updateSessionTimeout(String sessionId, long timeout) {
sessionRedisTemplate.expire(sessionId, timeout, TimeUnit.SECONDS);
}
}

View File

@ -29,14 +29,27 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency>
<!-- sa-token 整合 redis -->
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency> -->
<!-- sa-token整合redis (使用jackson序列化方式) -->
<!-- <dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.8.0</version>
</dependency> -->
<!-- 提供redis连接池 -->
<!-- <dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency> -->
<!-- @ConfigurationProperties -->

View File

@ -7,7 +7,7 @@ import cn.dev33.satoken.SaTokenManager;
@SpringBootApplication
public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功sa-token配置如下" + SaTokenManager.getConfig());

View File

@ -20,12 +20,10 @@ public class MySaTokenConfig implements WebMvcConfigurer {
SaTokenConfig config = new SaTokenConfig();
config.setTokenName("satoken"); // token名称 (同时也是cookie名称)
config.setTimeout(30 * 24 * 60 * 60); // token有效期单位s 默认30天
config.setIsShare(true); // 在多人登录同一账号时是否共享会话 (为true时共用一个为false时新登录挤掉旧登录)
config.setIsReadBody(true); // 是否尝试从请求体里读取token
config.setIsReadHead(true); // 是否尝试从header里读取token
config.setIsReadCookie(true); // 是否尝试从cookie里读取token
config.setActivityTimeout(-1); // token临时有效期 (指定时间内无操作就视为token过期) 单位:
config.setAllowConcurrentLogin(true); // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
config.setIsShare(true); // 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
config.setTokenStyle("uuid"); // token风格
config.setIsV(true); // 是否在初始化配置时打印版本字符画
return config;
}

View File

@ -1,354 +0,0 @@
package com.pj.satoken;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpLogic;
/**
* user认证实现
* @author kong
*/
public class StpUserUtil {
/**
* 底层的 StpLogic 对象
*/
public static StpLogic stpLogic = new StpLogic("user");
// =================== 获取token 相关 ===================
/**
* 返回token名称
* @return 此StpLogic的token名称
*/
public static String getTokenName() {
return stpLogic.getTokenName();
}
/**
* 获取当前tokenValue
* @return 当前tokenValue
*/
public static String getTokenValue() {
return stpLogic.getTokenValue();
}
/**
* 获取指定loginId的tokenValue
* @param loginId 账号id
* @return token值
*/
public static String getTokenValueByLoginId(Object loginId) {
return stpLogic.getTokenValueByLoginId(loginId);
}
/**
* 获取当前StpLogin的loginKey
* @return 当前StpLogin的loginKey
*/
public static String getLoginKey(){
return stpLogic.getLoginKey();
}
/**
* 获取当前会话的token信息
* @return token信息
*/
public static SaTokenInfo getTokenInfo() {
return stpLogic.getTokenInfo();
}
// =================== 登录相关操作 ===================
/**
* 在当前会话上登录id
* @param loginId 登录id建议的类型long | int | String
*/
public static void setLoginId(Object loginId) {
stpLogic.setLoginId(loginId);
}
/**
* 当前会话注销登录
*/
public static void logout() {
stpLogic.logout();
}
/**
* 指定loginId的会话注销登录正常注销下线
* @param loginId 账号id
*/
public static void logoutByLoginId(Object loginId) {
stpLogic.logoutByLoginId(loginId);
}
/**
* 指定loginId的会话注销登录踢人下线
* @param loginId 账号id
*/
public static void kickoutByLoginId(Object loginId) {
stpLogic.kickoutByLoginId(loginId);
}
// 查询相关
/**
* 获取当前会话是否已经登录
* @return 是否已登录
*/
public static boolean isLogin() {
return stpLogic.isLogin();
}
/**
* 检验当前会话是否已经登录如未登录则抛出异常
*/
public static void checkLogin() {
stpLogic.checkLogin();
}
/**
* 获取当前会话账号id, 如果未登录则抛出异常
* @return 账号id
*/
public static Object getLoginId() {
return stpLogic.getLoginId();
}
/**
* 获取当前会话登录id, 如果未登录则返回默认值
* @param <T> 返回类型
* @param defaultValue 默认值
* @return 登录id
*/
public static <T> T getLoginId(T defaultValue) {
return stpLogic.getLoginId(defaultValue);
}
/**
* 获取当前会话登录id, 如果未登录则返回null
* @return 账号id
*/
public static Object getLoginIdDefaultNull() {
return stpLogic.getLoginIdDefaultNull();
}
/**
* 获取当前会话登录id, 并转换为String
* @return 账号id
*/
public static String getLoginIdAsString() {
return stpLogic.getLoginIdAsString();
}
/**
* 获取当前会话登录id, 并转换为int
* @return 账号id
*/
public static int getLoginIdAsInt() {
return stpLogic.getLoginIdAsInt();
}
/**
* 获取当前会话登录id, 并转换为long
* @return 账号id
*/
public static long getLoginIdAsLong() {
return stpLogic.getLoginIdAsLong();
}
/**
* 获取指定token对应的登录id如果未登录则返回 null
* @param tokenValue token
* @return 登录id
*/
public static Object getLoginIdByToken(String tokenValue) {
return stpLogic.getLoginIdByToken(tokenValue);
}
// =================== session相关 ===================
/**
* 获取指定loginId的session, 如果session尚未创建isCreate=是否新建并返回
* @param loginId 账号id
* @param isCreate 是否新建
* @return SaSession
*/
public static SaSession getSessionByLoginId(Object loginId, boolean isCreate) {
return stpLogic.getSessionByLoginId(loginId, isCreate);
}
/**
* 获取指定loginId的session, 如果session尚未创建isCreate=是否新建并返回
* @param loginId 账号id
* @param isCreate 是否新建
* @return SaSession
*/
public static SaSession getSessionByLoginId(Object loginId) {
return stpLogic.getSessionByLoginId(loginId);
}
/**
* 获取当前会话的session, 如果session尚未创建isCreate=是否新建并返回
* @param isCreate 是否新建
* @return 当前会话的session
*/
public static SaSession getSession(boolean isCreate) {
return stpLogic.getSession(isCreate);
}
/**
* 获取当前会话的session如果session尚未创建则新建并返回
* @return 当前会话的session
*/
public static SaSession getSession() {
return stpLogic.getSession();
}
// =================== token专属session ===================
/**
* 获取指定token的专属session如果session尚未创建则新建并返回
* @param tokenValue token值
* @return session会话
*/
public static SaSession getTokenSessionByToken(String tokenValue) {
return stpLogic.getTokenSessionByToken(tokenValue);
}
/**
* 获取当前token的专属-session如果session尚未创建则新建并返回
* <p> 只有当前会话属于登录状态才可调用
* @return session会话
*/
public static SaSession getTokenSession() {
return stpLogic.getTokenSession();
}
// =================== [临时过期] 验证相关 ===================
/**
* 检查当前token 是否已经[临时过期]如果已经过期则抛出异常
*/
public static void checkActivityTimeout() {
stpLogic.checkActivityTimeout();
}
/**
* 续签当前token( [最后操作时间] 更新为当前时间戳)
* <h1>请注意: 即时token已经 [临时过期] 也可续签成功
* 如果此场景下需要提示续签失败可在此之前调用 checkActivityTimeout() 强制检查是否过期即可 </h1>
*/
public static void updateLastActivityToNow() {
stpLogic.updateLastActivityToNow();
}
// =================== 过期时间相关 ===================
/**
* 获取当前登录者的token剩余有效时间 (单位: )
* @return token剩余有效时间
*/
public static long getTimeout() {
return stpLogic.getTokenTimeout();
}
/**
* 获取指定loginId的token剩余有效时间 (单位: )
* @param loginId 指定loginId
* @return token剩余有效时间
*/
public static long getTimeoutByLoginId(Object loginId) {
return stpLogic.getTokenTimeoutByLoginId(loginId);
}
/**
* 获取当前登录者的Session剩余有效时间 (单位: )
* @return token剩余有效时间
*/
public static long getSessionTimeout() {
return stpLogic.getSessionTimeout();
}
/**
* 获取指定loginId的Session剩余有效时间 (单位: )
* @param loginId 指定loginId
* @return token剩余有效时间
*/
public static long getSessionTimeoutByLoginId(Object loginId) {
return stpLogic.getSessionTimeoutByLoginId(loginId);
}
/**
* 获取当前token[临时过期]剩余有效时间 (单位: )
* @return token[临时过期]剩余有效时间
*/
public static long getTokenActivityTimeout() {
return stpLogic.getTokenActivityTimeout();
}
/**
* 获取指定token[临时过期]剩余有效时间 (单位: )
* @param tokenValue 指定token
* @return token[临时过期]剩余有效时间
*/
public static long getTokenActivityTimeoutByToken(String tokenValue) {
return stpLogic.getTokenActivityTimeoutByToken(tokenValue);
}
// =================== 权限验证操作 ===================
/**
* 指定账号id是否含有指定权限
* @param loginId 账号id
* @param permissionCode 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(Object loginId, String permissionCode) {
return stpLogic.hasPermission(loginId, permissionCode);
}
/**
* 当前账号id是否含有指定权限
* @param permissionCode 权限码
* @return 是否含有指定权限
*/
public static boolean hasPermission(String permissionCode) {
return stpLogic.hasPermission(permissionCode);
}
/**
* 当前账号是否含有指定权限 没有就抛出异常
* @param permissionCode 权限码
*/
public static void checkPermission(String permissionCode) {
stpLogic.checkPermission(permissionCode);
}
/**
* 当前账号是否含有指定权限, [指定多个必须全都有]
* @param permissionCodeArray 权限码数组
*/
public static void checkPermissionAnd(String... permissionCodeArray) {
stpLogic.checkPermissionAnd(permissionCodeArray);
}
/**
* 当前账号是否含有指定权限, [指定多个有一个就可以通过]
* @param permissionCodeArray 权限码数组
*/
public static void checkPermissionOr(String... permissionCodeArray) {
stpLogic.checkPermissionOr(permissionCodeArray);
}
}

View File

@ -1,11 +1,17 @@
package com.pj.test;
import java.util.Date;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.session.SaSessionCustomUtil;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
@ -27,22 +33,22 @@ public class TestController {
System.out.println("当前会话的token" + StpUtil.getTokenValue());
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
StpUtil.setLoginId(id); // 在当前会话登录此账号
System.out.println("登录成功");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号:" + StpUtil.getLoginId());
System.out.println("当前登录账号:" + StpUtil.getLoginIdAsInt()); // 获取登录id并转为int
// System.out.println("当前token信息" + StpUtil.getTokenInfo());
// StpUtil.logout();
// System.out.println("注销登录");
// System.out.println("当前是否登录:" + StpUtil.isLogin());
// System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
// StpUtil.setLoginId(id); // 在当前会话登录此账号
// System.out.println("根据token找登录id" + StpUtil.getLoginIdByToken(StpUtil.getTokenValue()));
System.out.println("当前token信息" + StpUtil.getTokenInfo()); // 获取登录id并转为int
System.out.println("当前登录账号:" + StpUtil.getLoginIdDefaultNull());
return AjaxJson.getSuccess();
}
// 测试退出登录 浏览器访问 http://localhost:8081/test/logout
@RequestMapping("logout")
public AjaxJson logout() {
StpUtil.logout();
// StpUtil.logoutByLoginId(10001);
return AjaxJson.getSuccess();
}
@ -91,17 +97,17 @@ public class TestController {
return AjaxJson.getSuccess();
}
// 测试会话session接口 浏览器访问 http://localhost:8081/test/session
@RequestMapping("session")
public AjaxJson session() {
public AjaxJson session() throws JsonProcessingException {
System.out.println("======================= 进入方法测试会话session接口 ========================= ");
System.out.println("当前是否登录:" + StpUtil.isLogin());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("当前登录账号session的id" + StpUtil.getSession().getId());
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
StpUtil.getSession().setAttribute("name", "张三"); // 写入一个值
StpUtil.getSession().setAttribute("name", new Date()); // 写入一个值
System.out.println("测试取值name" + StpUtil.getSession().getAttribute("name"));
System.out.println( new ObjectMapper().writeValueAsString(StpUtil.getSession()));
return AjaxJson.getSuccess();
}
@ -155,10 +161,10 @@ public class TestController {
return AjaxJson.getSuccess();
}
// 测试注解式鉴权 浏览器访问 http://localhost:8081/test/atLogin
@SaCheckLogin // 注解式鉴权当前会话必须登录才能通过
@RequestMapping("atLogin")
public AjaxJson atLogin() {
// 测试注解式鉴权 浏览器访问 http://localhost:8081/test/atJurOr
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR) // 注解式鉴权只要具有其中一个权限即可通过校验
public AjaxJson atJurOr() {
return AjaxJson.getSuccessData("用户信息");
}
@ -175,10 +181,8 @@ public class TestController {
public AjaxJson kickOut() {
// 先登录上
StpUtil.setLoginId(10001);
// 清退下线
// StpUtil.logoutByLoginId(10001);
// 踢下线
StpUtil.kickoutByLoginId(10001);
StpUtil.logoutByLoginId(10001);
// 再尝试获取
StpUtil.getLoginId();
// 返回
@ -188,15 +192,7 @@ public class TestController {
// 测试 浏览器访问 http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test() {
StpUtil.setLoginId(10001);
// StpUtil.getSession();
StpUtil.logout();
// System.out.println(StpUtil.getSession().getId());
// System.out.println(StpUserUtil.getSession().getId());
// StpUtil.getSessionByLoginId(10001).setAttribute("name", "123");
// System.out.println(StpUtil.getSessionByLoginId(10001).getAttribute("name"));
StpUtil.getTokenSession().logout();
return AjaxJson.getSuccess();
}

View File

@ -9,21 +9,14 @@ spring:
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒, 默认-1 代表不限制 (例如可以设置为1800代表30分钟内无操作就过期)
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 在多人登录同一账号时,是否共享会话 (为true时共用一个为false时新登录挤掉旧登录)
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: true
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: true
# 是否尝试从请求体里读取token
is-read-body: true
# 是否尝试从header里读取token
is-read-head: true
# 是否尝试从cookie里读取token
is-read-cookie: true
# token风格
token-style: uuid
# 是否在初始化配置时打印版本字符画
is-v: true
tokenSessionCheckLogin: false
# redis配置

View File

@ -1,11 +1,11 @@
<p align="center">
<img alt="logo" src="https://gitee.com/sz6/sa-token/raw/master/sa-token-doc/doc/logo.png" width="150" height="150" style="margin-bottom: 10px;">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.7.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.8.0</h1>
<h4 align="center">一个JavaWeb轻量级权限认证框架功能全面上手简单</h4>
<h4 align="center">
<a href="https://gitee.com/sz6/sa-token/stargazers"><img src="https://gitee.com/sz6/sa-token/badge/star.svg"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.7.0-2B9939"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.8.0-2B9939"></a>
<a href="https://github.com/click33/sa-token/stargazers"><img src="https://img.shields.io/github/stars/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/watchers"><img src="https://img.shields.io/github/watchers/click33/sa-token"></a>
<a href="https://github.com/click33/sa-token/network/members"><img src="https://img.shields.io/github/forks/click33/sa-token"></a>
@ -17,28 +17,29 @@
## 😘 在线资料
- ##### [官网首页http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- ##### [在线文档http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html)
- ##### [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
- ##### [开源不易求鼓励点个star吧](https://github.com/click33/sa-token)
- [官网首页http://sa-token.dev33.cn/](http://sa-token.dev33.cn/)
- [在线文档http://sa-token.dev33.cn/doc/index.html](http://sa-token.dev33.cn/doc/index.html)
- [需求提交:我们深知一个优秀的项目需要海纳百川,点我在线提交需求](http://sa-app.dev33.cn/wall.html?name=sa-token)
- [开源不易求鼓励点个star吧](https://github.com/click33/sa-token)
## ⭐ sa-token是什么
- **sa-token是一个JavaWeb轻量级权限认证框架其API调用非常简单有多简单呢以登录验证为例你只需要**
**sa-token是一个JavaWeb轻量级权限认证框架其API调用非常简单有多简单呢以登录验证为例你只需要**
``` java
// 在登录时写入当前会话的账号id
StpUtil.setLoginId(10001);
StpUtil.setLoginId(10001);
// 然后在任意需要校验登录处调用以下API --- 如果当前会话未登录,这句代码会抛出 `NotLoginException`异常
StpUtil.checkLogin();
```
- **然后在任意需要验证登录权限的地方:**
``` java
// 检测是否登录 --- 如果当前会话未登录,下面这句代码会抛出 `NotLoginException`异常
StpUtil.checkLogin();
```
- **没有复杂的封装!不要任何的配置!先写入,后鉴权!只需这两行简单的调用,即可轻松完成系统登录鉴权!**
**没有复杂的封装!不要任何的配置!只需这两行简单的调用,即可轻松完成系统登录鉴权!**
## 🔥 框架设计思想
@ -47,20 +48,38 @@ StpUtil.checkLogin();
- 功能强大:能涵盖的功能全部涵盖,不让你用个框架还要自己给框架打各种补丁
**如果上面的示例能够证明`sa-token`的简单那么以下API则可以证明`sa-token`的强大**
``` java
StpUtil.setLoginId(10001); // 标记当前会话登录的账号id
StpUtil.getLoginId(); // 获取当前会话登录的账号id
StpUtil.isLogin(); // 获取当前会话是否已经登录, 返回true或false
StpUtil.logout(); // 当前会话注销登录
StpUtil.logoutByLoginId(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令牌值
```
**sa-token的API众多请恕此处无法为您逐一展示更多示例请戳官方在线文档**
## 💦️️ 涵盖功能
- ⚡ **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- ⚡ **权限验证** —— 拦截违规调用,不同角色不同授权
- ⚡ **自定义session会话** —— 专业的数据缓存中心
- ⚡ **踢人下线** —— 将违规用户立刻清退下线
- ⚡ **模拟他人账号** —— 实时操作任意用户状态数据
- ⚡ **持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件
- ⚡ **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
- ⚡ **无cookie模式** —— APP、小程序等前后台分离场景
- ⚡ **注解式鉴权** —— 优雅的将鉴权与业务代码分离
- ⚡ **花式token生成** —— 内置六种token风格还可自定义token生成策略
- ⚡ **自动续签** —— 提供两种token过期策略灵活搭配使用还可自动续签
- ⚡ **组件自动注入** —— 零配置与Spring等框架集成
- ⚡ **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
- **登录验证** —— 轻松登录鉴权,并提供五种细分场景值
- **权限验证** —— 拦截违规调用,不同角色不同授权
- **Session会话** —— 专业的数据缓存中心
- **踢人下线** —— 将违规用户立刻清退下线
- **模拟他人账号** —— 实时操作任意用户状态数据
- **持久层扩展** —— 可集成redis、MongoDB等专业缓存中间件
- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
- **无Cookie模式** —— APP、小程序等前后台分离场景
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
- **花式token生成** —— 内置六种token风格还可自定义token生成策略
- **自动续签** —— 提供两种token过期策略灵活搭配使用还可自动续签
- **组件自动注入** —— 零配置与Spring等框架集成
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
## 🔨 贡献代码

View File

@ -8,10 +8,10 @@
- **使用**
- [登录验证](/use/login-auth)
- [权限验证](/use/jur-auth)
- [session会话](/use/session)
- [Session会话](/use/session)
- [踢人下线](/use/kick)
- [持久层扩展(集成redis)](/use/dao-extend)
- [cookie模式(前后台分离)](/use/not-cookie)
- [持久层扩展(集成Redis)](/use/dao-extend)
- [Cookie模式(前后台分离)](/use/not-cookie)
- [模拟他人](/use/mock-person)
- [多账号验证](/use/many-account)
- [注解式鉴权](/use/at-check)

View File

@ -7,7 +7,7 @@
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="keywords" content="sa-token|sa-token框架|sa-token文档|sa-token在线文档|权限认证框架">
<meta name="description" content="sa-token是一个JavaWeb权限认证框架强大、简单、好用登录验证、权限验证、自定义session会话、踢人下线、持久层扩展、无cookie模式、模拟他人账号、多账号体系、注解式鉴权、花式token、自动续签、Spring集成...,零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<meta name="description" content="sa-token是一个JavaWeb权限认证框架功能全面上手简单登录验证、权限验证、Session会话、踢人下线、集成Redis、前后台分离、模拟他人账号、多账号体系、注解式鉴权、花式token、自动续签、Spring集成...,零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<link rel="stylesheet" href="https://unpkg.com/docsify@4.11.3/lib/themes/vue.css">
<link rel="stylesheet" href="./lib/index.css">
<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.7.0/doc/index.html">v1.7.0</option>
<option value="http://sa-token.dev33.cn/v/v1.6.0/doc/index.html">v1.6.0</option>
<option value="http://sa-token.dev33.cn/v/v1.5.1/doc/index.html">v1.5.1</option>
<option value="http://sa-token.dev33.cn/v/v1.4.0/doc/index.html">v1.4.0</option>
@ -36,7 +37,7 @@
</div>
<script>
var name = '<img style="width: 50px; height: 50px; vertical-align: middle;" src="logo.png" alt="logo" /> ';
name += '<b style="font-size: 24px; vertical-align: middle;">sa-token</b> <sub>v1.7.0</sub>'
name += '<b style="font-size: 24px; vertical-align: middle;">sa-token</b> <sub>v1.8.0</sub>'
window.$docsify = {
name: name, // 名字
repo: 'https://github.com/click33/sa-token', // github地址

View File

@ -16,6 +16,8 @@
/* 背景变黑 */
.main-box [data-lang]{padding: 0px !important; border-radius: 8px; overflow: hidden;}
.main-box [class^="lang-"]{border: 0px red solid; padding: 1.5em 1.2em;/* background-color: #282828; */ background-color: #090300; color: #FFF;}
.main-box [data-lang]{overflow: auto;}
/* .main-box h2{margin-top: 70px;} */
/* xml语言样式优化 */
.lang-xml .token.comment{color: #CDAB53;}
@ -34,7 +36,8 @@
/* js语言样式优化 */
.main-box .lang-js{color: #01a252;}
.lang-js .token.comment{color: #CDAB53;}
.lang-js .token.string{color: #fded02;}
/* .lang-js .token.string{color: #fded02;} */
.lang-js .token.string{color: #ddd;}
.lang-js .token.punctuation{color: #ddd;}

View File

@ -1,6 +1,25 @@
# 更新日志
### 2020-12-24 @v1.8.0
- 优化:优化源码注释
- 修复:修复部分文档错别字
- 修复:修复项目文件夹名称错误
- 优化:优化文档配色,更舒服的代码展示
- 新增:提供`sa-token`集成 `redis``spring-boot-starter` 方案 **[重要]**
- 新增:新增集成 `redis` 时,以`jackson`作为序列化方案 **[重要]**
- 新增dao层默认实现增加定时清理过期数据功能 **[重要]**
- 新增:新增`token专属session`, 更灵活的会话管理 **[重要]**
- 新增:增加配置,指定在获取`token专属session`时是否必须登录
- 新增在无token时自动创建会话完美兼容`token-session`会话模型! **[重要]**
- 修改权限码限定必须为String类型
- 优化注解验证模式由boolean属性改为枚举方式
- 删除:`StpUtil`删除部分冗长API保持API清爽性
- 新增:新增角色验证 (角色验证与权限验证已完全分离) **[重要]**
- 优化:移除`StpUtil.kickoutByLoginId()`API由`logoutByLoginId`代替
- 升级:开源协议修改为`Apache-2.0`
### 2020-12-24 @v1.7.0
- 优化项目架构改为maven多模块形式方便增加新模块 **[重要]**
- 优化:与`springboot`的集成改为`springboot-starter`模式,无需`@SaTokenSetup`注解即可完成自动装配 **[重要]**
@ -33,7 +52,7 @@
### 2020-9-7 @v1.4.0
- 优化修改一些函数、变量名称使其更符合阿里java代码规范
- 优化:`tokenValue`的读取优先级改为:`request` > `body` > `header` > `cookie`
- 优化:`tokenValue`的读取优先级改为:`request` > `body` > `header` > `cookie` **[重要]**
- 新增:新增`isReadCookie`配置,决定是否从`cookie`里读取`token`信息
- 优化:如果`isReadCookie`配置为`false`,那么在登录时也不会把`cookie`写入`cookie`
- 新增:新增`getSessionByLoginId(Object loginId, boolean isCreate)`方法
@ -50,7 +69,7 @@
### 2020-3-7 @v1.2.0
- 新增:新增注解式验证,可在路由方法中使用注解进行权限验证
- 新增:新增注解式验证,可在路由方法中使用注解进行权限验证 **[重要]**
- 参考:[注解式验证](use/at-check)

View File

@ -2,14 +2,14 @@
------
## maven依赖
## Maven依赖
在项目中直接通过 `pom.xml` 导入 `sa-token` 的依赖即可
``` xml
<!-- sa-token 权限认证, 在线文档http://sa-token.dev33.cn/ -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency>
```
@ -21,11 +21,18 @@
- github地址[https://github.com/click33/sa-token](https://github.com/click33/sa-token)
- gitee地址[https://gitee.com/sz6/sa-token](https://gitee.com/sz6/sa-token)
- 开源不易,求鼓励,给个`star`吧
- 源码目录介绍
- `sa-token-dev`: 源码
- `sa-token-spring-boot-starter`: springboot插件化集成
- `sa-token-demo-springboot`: springboot集成示例
- `sa-token-doc`: 文档介绍
- 源码目录介绍:
```
── sa-token
├── sa-token-core // sa-token核心模块
├── sa-token-spring-boot-starter // sa-token整合springboot快速集成
├── sa-token-dao-redis // sa-token整合redis (使用jdk默认序列化方式)
├── sa-token-dao-redis-jackson // sa-token整合redis (使用jackson序列化方式)
├── sa-token-demo-springboot // sa-token示例
├── sa-token-doc // sa-token开发文档
├──pom.xml
```

View File

@ -18,7 +18,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency>
```
@ -34,20 +34,14 @@ spring:
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期, 默认-1 代表不限制
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 在多人登录同一账号时,是否共享会话 (为true时共用一个为false时新登录挤掉旧登录)
is-share: true
# 是否尝试从请求体里读取token
is-read-body: true
# 是否尝试从header里读取token
is-read-head: true
# 是否尝试从cookie里读取token
is-read-cookie: true
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: false
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否在初始化配置时打印版本字符画
is-v: true
```
> - 如果你习惯于 `application.properties` 类型的配置文件,那也很好办:
@ -60,7 +54,7 @@ spring:
@SpringBootApplication
public class SaTokenDemoApplication {
public static void main(String[] args) throws JsonProcessingException {
SpringApplication.run(SaTokenDemoApplication.class, args); // run-->
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("启动成功sa-token配置如下" + SaTokenManager.getConfig());
}
}

View File

@ -2,8 +2,7 @@
---
- 尽管我们可以方便的一句代码完成权限验证,但是有时候我们仍希望可以将鉴权代码与我们的业务代码分离开来
- 怎么做?
- `sa-token`内置两个注解,帮助你使用注解完成鉴权操作
- 怎么做?`sa-token`内置三个注解,帮助你使用注解完成鉴权操作
## 1、注册拦截器
@ -28,29 +27,59 @@
#### 登录验证
``` java
@SaCheckLogin // 注解式鉴权:当前会话必须登录才能通过
// 注解式鉴权:当前会话必须登录才能通过
@SaCheckLogin
@RequestMapping("info")
public String info() {
return "查询用户信息";
}
```
#### 权限验证
#### 角色验证
``` java
@SaCheckPermission("user-add") // 注解式鉴权:当前会话必须具有指定权限才能通过
// 注解式鉴权:当前会话必须具有指定角色标识才能通过
@SaCheckRole("super-admin")
@RequestMapping("add")
public String add() {
return "用户增加";
}
```
#### 注意事项
#### 权限验证
``` java
// 注解式鉴权:当前会话必须具有指定权限才能通过
@SaCheckPermission("user-add")
@RequestMapping("add")
public String add() {
return "用户增加";
}
```
<br>
以上两个注解都可以加在类上,代表为这个类所有方法进行鉴权
## 3、设定校验模式
`@SaCheckRole`与`@SaCheckPermission`注解可设置校验模式,例如:
``` java
// 注解式鉴权:只要具有其中一个权限即可通过校验
@RequestMapping("atJurOr")
@SaCheckPermission(value = {"user-add", "user-all", "user-delete"}, mode = SaMode.OR)
public AjaxJson atJurOr() {
return AjaxJson.getSuccessData("用户信息");
}
```
## 3、扩展
- mode有两种取值
- `SaMode.AND`, 标注一组权限,会话必须全部具有才可通过校验
- `SaMode.OR`, 标注一组权限,会话只要具有其一即可通过校验
<!-- ## 3、扩展
- 其实在注册拦截器时,我们也可以根据路由前缀设置不同 `StpLogic`, 从而达到不同模块不同鉴权方式的目的
- 以下为参考示例:
``` java
@ -63,7 +92,7 @@
registry.addInterceptor(new SaCheckInterceptor(StpUserUtil.stpLogic)).addPathPatterns("/user/**");
}
}
```
``` -->

View File

@ -16,20 +16,14 @@ spring:
token-name: satoken
# token有效期单位s 默认30天, -1代表永不过期
timeout: 2592000
# token临时有效期, 默认-1 代表不限制
activity-timeout: -1
# 在多人登录同一账号时,是否共享会话 (为true时共用一个为false时新登录挤掉旧登录)
is-share: true
# 是否尝试从请求体里读取token
is-read-body: true
# 是否尝试从header里读取token
is-read-head: true
# 是否尝试从cookie里读取token
is-read-cookie: true
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
activity-timeout: -1
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
allow-concurrent-login: false
# 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
is-share: false
# token风格
token-style: uuid
# 是否在初始化配置时打印版本字符画
is-v: true
```
- 如果你习惯于 `application.properties` 类型的配置文件,那也很好办:
@ -44,20 +38,17 @@ spring:
@Configuration
public class MySaTokenConfig {
// 获取配置Bean (以代码的方式配置sa-token)
// 获取配置Bean (以代码的方式配置sa-token, 此配置会覆盖yml中的配置)
@Primary
@Bean(name="MySaTokenConfig")
public SaTokenConfig getSaTokenConfig() {
SaTokenConfig config = new SaTokenConfig();
config.setTokenName("satoken"); // token名称 (同时也是cookie名称)
config.setTimeout(30 * 24 * 60 * 60); // token有效期单位s 默认30天, -1代表永不过期
config.setActivityTimeout(-1); // token临时有效期, 默认-1 代表不限制
config.setIsShare(true); // 在多人登录同一账号时,是否共享会话 (为true时共用一个为false时新登录挤掉旧登录)
config.setIsReadBody(true); // 是否尝试从请求体里读取token
config.setIsReadHead(true); // 是否尝试从header里读取token
config.setIsReadCookie(true); // 是否尝试从cookie里读取token
config.setTokenStyle("uuid"); // token风格
config.setIsV(true); // 是否在初始化配置时打印版本字符画
config.setTokenName("satoken"); // token名称 (同时也是cookie名称)
config.setTimeout(30 * 24 * 60 * 60); // token有效期单位s 默认30天
config.setActivityTimeout(-1); // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
config.setAllowConcurrentLogin(true); // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
config.setIsShare(true); // 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
config.setTokenStyle("uuid"); // token风格
return config;
}
@ -69,14 +60,15 @@ spring:
### 所有可配置项
| 参数名称 | 类型 | 默认值 | 说明 |
| :-------- | :-------- | :-------- | :-------- |
| tokenName | String | satoken | token名称同时也是cookie名称 |
| tokenName | String | satoken | token名称 (同时也是cookie名称) |
| timeout | long | 2592000 | token有效期单位/秒 默认30天-1代表永久有效 [参考token有效期详解](/fun/token-timeout) |
| activityTimeout | long | -1 | token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒, 默认-1 代表不限制 (例如可以设置为1800代表30分钟内无操作就过期) [参考token有效期详解](/fun/token-timeout) |
| isShare | Boolean | true | 在多人登录同一账号时是否共享会话为true时共用一个为false时新登录挤掉旧登录 |
| allowConcurrentLogin | Boolean | true | 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) |
| isShare | Boolean | true | 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) |
| isReadBody | Boolean | true | 是否尝试从请求体里读取token |
| isReadHead | Boolean | true | 是否尝试从header里读取token |
| isReadCookie | Boolean | true | 是否尝试从cookie里读取token |
| tokenStyle | String | uuid | token风格, [参考花式token](/use/token-style) |
| dataRefreshPeriod | int | 30 | 默认dao层实现类中每次清理过期数据间隔的时间 (单位: 秒) 默认值30秒设置为-1代表不启动定时清理 |
| tokenSessionCheckLogin | Boolean | 30 | 获取token专属session时是否必须登录 |
| tokenSessionCheckLogin | Boolean | true | 获取token专属session时是否必须登录 (如果配置为true会在每次获取token专属session时校验是否登录) |
| isV | Boolean | true | 是否在初始化配置时打印版本字符画 |

View File

@ -1,146 +1,48 @@
# 持久层扩展
---
- 每次重启项目就得重新登录一遍,我想把登录数据都放在`redis`里,这样重启项目也不用重新登录,行不行?**行!**
- 你需要做的就是重写`sa-token`的dao层实现方式参考以下方案
- 每次重启项目都需要重新登录一次,我想把会话数据都放在`Redis`、`MongoDB`等专业的缓存中间件中,这样重启项目也不用重新登录,行不行?**行!**
- sa-token 在架构设计时将数据持久操作全部抽象到接口`SaTokenDao`中,此设计可以保证开发者对数据持久层的灵活扩展
- 除了框架内部对`SaTokenDao`提供的基于内存的默认实现,官方仓库还提供了以下扩展方案:
### 1. 首先在pom.xml引入依赖
### 1. sa-token 整合 Redis (使用jdk默认序列化方式)
``` xml
<!-- SpringBoot整合redis -->
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-redis</artifactId>
<version>RELEASE</version>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>1.8.0</version>
</dependency>
```
优点兼容性好缺点session序列化后基本不可读对开发者来讲等同于乱码
### 2. sa-token 整合 Redis (使用jackson序列化方式)
``` xml
<!-- sa-token整合redis (使用jackson序列化方式) -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.8.0</version>
</dependency>
```
优点session序列化后可读性强可灵活手动修改缺点兼容性稍差
<br>
**请注意无论使用哪种序列化方式你都必须为项目提供一个Redis实例化方案例如**
``` xml
<!-- 提供redis连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
```
### 2. 实现SaTokenDao接口
新建文件`SaTokenDaoRedis.java`,实现接口`SaTokenDao`, 并加上注解`@Component`保证此类被springboot扫描到
- 代码参考:
```java
package com.pj.satoken;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.JdkSerializationRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.session.SaSession;
/**
* sa-token持久层的实现类 , 基于redis
*/
@Component // 打开此注解保证此类被springboot扫描即可完成sa-token与redis的集成
public class SaTokenDaoRedis implements SaTokenDao {
<br><br>
更多框架的集成方案正在更新中... (欢迎大家提交pr)
// string专用
@Autowired
StringRedisTemplate stringRedisTemplate;
// SaSession专用
RedisTemplate<String, SaSession> redisTemplate;
@Autowired
@SuppressWarnings({ "rawtypes", "unchecked" })
public void setRedisTemplate(RedisTemplate redisTemplate) {
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new JdkSerializationRedisSerializer());
this.redisTemplate = redisTemplate;
}
// 根据key获取value ,如果没有,则返回空
@Override
public String getValue(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
// 写入指定key-value键值对并设定过期时间(单位:秒)
@Override
public void setValue(String key, String value, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
stringRedisTemplate.opsForValue().set(key, value);
} else {
stringRedisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
}
}
// 更新指定key-value键值对 (过期时间取原来的值)
@Override
public void updateValue(String key, String value) {
long expire = getTimeout(key);
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键
return;
}
this.setValue(key, value, expire);
}
// 删除一个指定的key
@Override
public void deleteKey(String key) {
stringRedisTemplate.delete(key);
}
// 获取指定key的剩余存活时间 (单位: 秒)
@Override
public long getTimeout(String key) {
return stringRedisTemplate.getExpire(key);
}
// 根据指定key的session如果没有则返回空
@Override
public SaSession getSession(String sessionId) {
return redisTemplate.opsForValue().get(sessionId);
}
// 将指定session持久化
@Override
public void saveSession(SaSession session, long timeout) {
// 判断是否为永不过期
if(timeout == SaTokenDao.NEVER_EXPIRE) {
redisTemplate.opsForValue().set(session.getId(), session);
} else {
redisTemplate.opsForValue().set(session.getId(), session, timeout, TimeUnit.SECONDS);
}
}
// 更新指定session
@Override
public void updateSession(SaSession session) {
long expire = getSessionTimeout(session.getId());
if(expire == SaTokenDao.NOT_VALUE_EXPIRE) { // -2 = 无此键
return;
}
this.saveSession(session, expire);
}
// 删除一个指定的session
@Override
public void deleteSession(String sessionId) {
redisTemplate.delete(sessionId);
}
// 获取指定SaSession的剩余存活时间 (单位: 秒)
@Override
public long getSessionTimeout(String sessionId) {
return redisTemplate.getExpire(sessionId);
}
}
```
- 可参考代码:[码云SaTokenDaoRedis.java](https://gitee.com/sz6/sa-token/blob/master/sa-token-demo-springboot/src/main/java/com/pj/satoken/SaTokenDaoRedis.java)

View File

@ -30,7 +30,9 @@ import cn.dev33.satoken.stp.StpInterface;
@Component // 保证此类被springboot扫描完成sa-token的自定义权限验证扩展
public class StpInterfaceImpl implements StpInterface {
// 返回一个账号所拥有的权限码集合
/**
* 返回一个账号所拥有的权限码集合
*/
@Override
public List<Object> getPermissionCodeList(Object loginId, String loginKey) {
List<Object> list = new ArrayList<Object>(); // 本list仅做模拟实际项目中要根据具体业务逻辑来查询权限
@ -43,6 +45,17 @@ public class StpInterfaceImpl implements StpInterface {
return list;
}
/**
* 返回一个账号所拥有的角色标识集合 (权限与角色可分开校验)
*/
@Override
public List<String> getRoleList(Object loginId, String loginKey) {
List<String> list = new ArrayList<String>(); // 本list仅做模拟实际项目中要根据具体业务逻辑来查询角色
list.add("admin");
list.add("super-admin");
return list;
}
}
```

View File

@ -1,21 +1,16 @@
# 踢人下线
所谓踢人下线,核心操作就是找到其指定`loginId`的token并设置其失效
---
## 核心思想
- 所谓踢人下线,核心操作就是找到其指定`loginId`的token并设置其失效
## 具体API
#### StpUtil.logoutByLoginId(Object loginId)
- 让指定loginId的会话注销登录清退下线
让指定loginId的会话注销登录踢人下线例如
#### StpUtil.kickoutByLoginId(Object loginId)
- 让指定loginId的会话注销登录踢人下线
``` java
// 使账号id为10001的会话注销登录待到10001再次访问系统时会抛出`NotLoginException`异常,场景值为-5
StpUtil.logoutByLoginId(10001);
```
## 详解
- `logoutByLoginId``kickoutByLoginId` 都可以将用户强制下线,不同点在于:
- `logoutByLoginId` 是将人清退,用户得到的提示是 [token无效] ,对于失效原因尚未可知 NotLoginException场景值为-2
- `kickoutByLoginId` 是将人踢下线,用户可得到明确提示 [已被踢下线] NotLoginException场景值为-5

View File

@ -8,7 +8,7 @@
## 核心思想
- sa-token在设计时充分考虑了多账号体系时的各种逻辑
- 以上几篇介绍的api都是经过 `StpUtil`类的各种静态方法进行各种验证,而如果你深入它的源码,[点此阅览](https://gitee.com/sz6/sa-token/blob/master/sa-token-dev/src/main/java/cn/dev33/satoken/stp/StpUtil.java)
- 以上几篇介绍的api都是经过 `StpUtil`类的各种静态方法进行各种验证,而如果你深入它的源码,[点此阅览](https://gitee.com/sz6/sa-token/blob/master/sa-token-core/src/main/java/cn/dev33/satoken/stp/StpUtil.java)
- 就会发现,此类并没有任何代码逻辑,唯一做的事就是对成员变量`stpLogic`的各个API进行包装一下进行转发
- 这样做有两个优点
- `StpLogic`类的所有函数都可以被重写,按需扩展
@ -19,7 +19,7 @@
1. 新建一个新的权限验证类,比如: `StpUserUtil.java`
2. 将`StpUtil.java`类的全部代码复制粘贴到 `StpUserUtil.java`
3. 更改一下其 `loginKey` 比如:
```
``` java
// 底层的 StpLogic 对象
public static StpLogic stpLogic = new StpLogic("user"); // loginKey改为user
```

View File

@ -19,10 +19,10 @@
- 类似API还有
- `StpUtil.getSessionByLoginId(Object loginId, boolean isCreate)` 获取当前会话登录id, `isCreate`代表指定是否在无`session`的情况下新建并返回
#### StpUtil.hasRole(Object loginId, Object role)
#### StpUtil.hasRole(Object loginId, String role)
- 指定`loginId`是否含有指定角色
#### StpUtil.hasPermission(Object loginId, Object permission)
#### StpUtil.hasPermission(Object loginId, String permission)
- 指定`loginId`是否含有指定权限

View File

@ -1,37 +1,58 @@
# session会话
# Session会话
---
## 账号session
账号`session`指的是为每个登录账号分配的`session`
#### StpUtil.getSession()
- 返回当前登录账号的`session`(必须是登录后才能调用)
Session是会话中专业的数据缓存组件在`sa-token`中Session分为三种, 分别是:
- `账号Session`: 指的是框架为每个`loginId`分配的`Session`
- `令牌Session`: 指的是框架为每个`token`分配的`Session`
- `自定义Session`: 指的是以一个`特定的值`作为SessionId来分配的`Session`
## 自定义session
自定义`session`指的是未登录状态下以一个特定的值作为key来分配的`session`
#### SaSessionCustomUtil.isExists(String sessionId)
- 查询指定key的`session`,是否存在
#### SaSessionCustomUtil.getSessionById(String sessionId)
- 获取指定key的`session`,如果没有,则新建并返回
#### SaSessionCustomUtil.delSessionById(String sessionId)
- 删除指定key的`session`
## 账号Session
有关账号Session的API如下
``` java
StpUtil.getSession(); // 获取当前账号id的Session (必须是登录后才能调用)
StpUtil.getSession(true); // 获取当前账号id的Session, 并决定在Session尚未创建时是否新建并返回
StpUtil.getSessionByLoginId(10001); // 获取账号id为10001的Session
StpUtil.getSessionByLoginId(10001, true); // 获取账号id为10001的Session, 并决定在Session尚未创建时是否新建并返回
```
## session相关操作
## 令牌Session
有关令牌Session的API如下
``` java
StpUtil.getTokenSession(); // 获取当前token的专属Session
StpUtil.getTokenSessionByToken(token); // 获取指定token的专属Session
```
## 自定义Session
自定义Session指的是以一个`特定的值`作为SessionId来分配的`Session`, 借助自定义Session你可以为系统中的任意元素分配相应的session<br>
例如以商品id作为key为每个商品分配一个Session以便于缓存和商品相关的数据其相关API如下
``` java
SaSessionCustomUtil.isExists("goods-10001"); // 查询指定key的Session是否存在
SaSessionCustomUtil.getSessionById("goods-10001"); // 获取指定key的Session如果没有则新建并返回
SaSessionCustomUtil.getSessionById("goods-10001", false); // 获取指定key的Session如果没有第二个参数决定是否新建并返回
SaSessionCustomUtil.deleteSessionById("goods-10001"); // 删除指定key的Session
```
在未登录状态下是否可以获取令牌Session这取决于你配置的`tokenSessionCheckLogin`值是否为false详见[框架配置](/use/config?id=所有可配置项)
## Session相关操作
那么获取到的`SaSession`具体有哪些方法可供操作?
``` java
session.getId(); // 返回此Session的id
session.getCreateTime(); // 返回此Session的创建时间 (时间戳)
session.getAttribute('name'); // 在Session上获取一个值
session.getAttribute('name', 'zhang'); // 在Session上获取一个值并指定取不到值时返回的默认值
session.setAttribute('name', 'zhang'); // 在Session上写入一个值
session.removeAttribute('name'); // 在Session上移除一个值
session.clearAttribute(); // 清空此Session的所有值
session.containsAttribute('name'); // 获取此Session是否含有指定key (返回true或false)
session.attributeKeys(); // 获取此Session会话上所有key (返回Set<String>)
session.getDataMap(); // 返回此Session会话上的底层数据对象如果更新map里的值请调用session.update()方法避免数据过时)
session.update(); // 将这个Session从持久库更新一下
session.logout(); // 注销此Session会话
```
具体可参考`javax.servlet.http.HttpSession``SaSession`所含方法与其大体类似
#### getId()
- 返回此`session`的id
#### setAttribute(String key, Object value)
- 在此`session`对象上写入值
#### getAttribute(String key)
- 在此`session`对象上查询值
具体可参考`javax.servlet.http.HttpSession``SaSession`所含方法与其大体类似

View File

@ -81,6 +81,7 @@
1. 首先我们需要找一个合适的类库帮助我们生成雪花算法唯一id在此推荐 [Hutool](https://hutool.cn/docs/#/) ,在`pom.xml`里添加依赖:
``` xml
<!-- Hutool 一个小而全的Java工具类库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>

View File

@ -7,7 +7,7 @@
<meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="keywords" content="sa-token|sa-token框架|sa-token文档|sa-token在线文档|权限认证框架">
<meta name="description" content="sa-token是一个JavaWeb权限认证框架强大、简单、好用登录验证、权限验证、自定义session会话、踢人下线、持久层扩展、无cookie模式、模拟他人账号、多账号体系、注解式鉴权、花式token、自动续签、Spring集成...,零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<meta name="description" content="sa-token是一个JavaWeb权限认证框架功能全面上手简单登录验证、权限验证、Session会话、踢人下线、集成Redis、前后台分离、模拟他人账号、多账号体系、注解式鉴权、花式token、自动续签、Spring集成...,零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<link rel="stylesheet" href="https://unpkg.com/docsify@4.11.3/lib/themes/vue.css">
<link rel="shortcut icon" type="image/x-icon" href="doc/logo.png">
<link rel="stylesheet" href="index.css">
@ -43,10 +43,10 @@
<!-- 内容部分 -->
<div class="main-box">
<div class="content-box">
<h1>sa-token<small>v1.7.0</small></h1>
<h1>sa-token<small>v1.8.0</small></h1>
<div class="sub-title">一个JavaWeb轻量级权限认证框架功能全面上手简单</div>
<!-- <p>0配置开箱即用低学习成本</p> -->
<p>登录验证、权限验证、自定义session会话、踢人下线、持久层扩展、无cookie模式、模拟他人账号、多账号体系、注解式鉴权、花式token、自动续签、Spring集成...</p>
<p>登录验证、权限验证、Session会话、踢人下线、集成Redis、前后台分离、模拟他人账号、多账号体系、注解式鉴权、花式token、自动续签、Spring集成...</p>
<p>零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有</p>
<div class="btn-box">
<a href="https://github.com/click33/sa-token" target="_blank">GitHub</a>

View File

@ -7,7 +7,7 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</parent>
<packaging>jar</packaging>
@ -19,7 +19,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-core</artifactId>
<version>1.7.0</version>
<version>1.8.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>