Compare commits

..

27 Commits

Author SHA1 Message Date
shengzhang
a8117a8021 v1.15更新 2021-03-23 02:24:37 +08:00
shengzhang
c574e7f8d7 SaRouterUtil迁移到core核心包,优化依赖架构 2021-03-22 17:02:09 +08:00
shengzhang
36afaf74d3 token前缀校验改为强制模式,如果配置了前缀,则前端提交token时必须带有前缀 2021-03-22 00:04:05 +08:00
shengzhang
55b9c87d6a Session新增timeout操作API 2021-03-21 23:06:06 +08:00
shengzhang
50ffda7bef SaTokenManager新增stpLogicMap集合,记录所有StpLogic的初始化,方便查找 2021-03-20 16:51:18 +08:00
shengzhang
a122a8b083 StpUtil添加setTokenValue方法 2021-03-18 23:51:56 +08:00
shengzhang
69ce298a8a 适配token前缀模式 2021-03-18 13:06:24 +08:00
省长
f92e64b9cf update sa-token-doc/doc/use/jur-auth.md. 2021-03-16 17:27:13 +08:00
省长
d3138cefc8 !21 update sa-token-doc/doc/use/jur-auth.md.
Merge pull request !21 from AppleOfGray/N/A
2021-03-16 17:23:41 +08:00
AppleOfGray
71f2522f08 update sa-token-doc/doc/use/jur-auth.md. 2021-03-16 16:43:05 +08:00
省长
a5e9bd6714 !20 update sa-token-doc/doc/use/session.md.
Merge pull request !20 from AppleOfGray/N/A
2021-03-16 16:16:44 +08:00
AppleOfGray
27bb436a90 update sa-token-doc/doc/use/session.md. 2021-03-16 16:13:39 +08:00
省长
d75c91f0ab !19 update sa-token-doc/doc/use/many-account.md.
Merge pull request !19 from AppleOfGray/N/A
2021-03-16 15:56:38 +08:00
AppleOfGray
8ee9f215a6 update sa-token-doc/doc/use/many-account.md. 2021-03-16 15:55:36 +08:00
shengzhang
1df216879c 文档集成Redis章节新增redis配置示例说明,感谢群友 @-) 提供的建议 2021-03-16 14:44:38 +08:00
click33
91998af186 Merge pull request #48 from zhangzi0291/patch-1
修改cookie中没有path的问题
2021-03-16 00:36:34 +08:00
zhangzi0291
f33df38f64 修改cookie中没有path的问题
addCookies时如果path没有指定,path默认为/
2021-03-16 00:33:12 +08:00
shengzhang
aaf4bb8931 Session新增getString(key)方法,方便取值时类型转换 2021-03-15 17:05:07 +08:00
shengzhang
5808710bbf 修复错别字Request->Response 2021-03-14 14:39:21 +08:00
shengzhang
62787c3257 更改路由拦截示例 2021-03-14 00:04:11 +08:00
省长
6dd39505f9 增加友链 2021-03-13 17:28:03 +08:00
省长
d86f72b469 update README.md. 2021-03-13 17:25:41 +08:00
省长
7b7920123a update README.md. 2021-03-13 17:23:46 +08:00
shengzhang
8872982455 优化文档 2021-03-13 00:54:39 +08:00
shengzhang
80f07ae01e 技术栈 2021-03-13 00:47:43 +08:00
shengzhang
540730d9a4 优化文档 2021-03-12 09:51:37 +08:00
省长
03c8abb269 update README.md. 2021-03-12 09:46:11 +08:00
50 changed files with 809 additions and 582 deletions

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">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.14.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.15.0</h1>
<h4 align="center">这可能是史上功能最全的Java权限认证框架</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.14.0-2B9939"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.15.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>
@@ -58,6 +58,8 @@ sa-token是一个轻量级Java权限认证框架主要解决登录认证
- **路由拦截式鉴权** —— 根据路由拦截鉴权可适配restful模式
- **自动续签** —— 提供两种token过期策略灵活搭配使用还可自动续签
- **会话治理** —— 提供方便灵活的会话查询接口
- **记住我模式** —— 适配[记住我]模式,重启浏览器免验证
- **密码加密** —— 提供密码加密模块可快速MD5、SHA1、SHA256、AES、RSA加密
- **组件自动注入** —— 零配置与Spring等框架集成
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
@@ -180,7 +182,9 @@ sa-token秉承着开放的思想欢迎大家贡献代码为框架添砖加
## 友情链接
[**[ okhttps ]** 一个轻量级http通信框架API设计无比优雅支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
[**[ OkHttps ]** 一个轻量级http通信框架API设计无比优雅支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
[**[ 小诺快速开发平台 ]** 基于SpringBoot2 + AntDesignVue全新快速开发平台同时拥有三个版本](https://xiaonuo.vip/index#pricing)
## 交流群

View File

@@ -8,7 +8,7 @@
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<packaging>pom</packaging>
<version>1.14.0</version>
<version>1.15.0</version>
<!-- 项目介绍 -->
<name>sa-token</name>
@@ -38,7 +38,7 @@
<!-- 一些属性 -->
<properties>
<sa-token-version>1.14.0</sa-token-version>
<sa-token-version>1.15.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>

View File

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

View File

@@ -1,5 +1,8 @@
package cn.dev33.satoken;
import java.util.HashMap;
import java.util.Map;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.action.SaTokenActionDefaultImpl;
import cn.dev33.satoken.config.SaTokenConfig;
@@ -12,6 +15,7 @@ import cn.dev33.satoken.servlet.SaTokenServlet;
import cn.dev33.satoken.servlet.SaTokenServletDefaultImpl;
import cn.dev33.satoken.stp.StpInterface;
import cn.dev33.satoken.stp.StpInterfaceDefaultImpl;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
@@ -21,129 +25,143 @@ import cn.dev33.satoken.util.SaTokenInsideUtil;
*/
public class SaTokenManager {
/**
* 配置文件 Bean
*/
private static SaTokenConfig config;
public static SaTokenConfig getConfig() {
if (config == null) {
initConfig();
}
return config;
}
public static void setConfig(SaTokenConfig config) {
SaTokenManager.config = config;
if(config.getIsV()) {
SaTokenInsideUtil.printSaToken();
}
}
public synchronized static void initConfig() {
public static SaTokenConfig getConfig() {
if (config == null) {
setConfig(SaTokenConfigFactory.createConfig());
// 如果对象为空,则使用框架默认方式初始化
synchronized (SaTokenManager.class) {
if (config == null) {
setConfig(SaTokenConfigFactory.createConfig());
}
}
}
return config;
}
/**
* 持久化 Bean
*/
public static SaTokenDao saTokenDao;
public static SaTokenDao getSaTokenDao() {
if (saTokenDao == null) {
initSaTokenDao();
}
return saTokenDao;
}
private static SaTokenDao saTokenDao;
public static void setSaTokenDao(SaTokenDao saTokenDao) {
if(SaTokenManager.saTokenDao != null && (SaTokenManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) {
((SaTokenDaoDefaultImpl)SaTokenManager.saTokenDao).endRefreshTimer();
((SaTokenDaoDefaultImpl)SaTokenManager.saTokenDao).endRefreshThread();
}
SaTokenManager.saTokenDao = saTokenDao;
}
public synchronized static void initSaTokenDao() {
public static SaTokenDao getSaTokenDao() {
if (saTokenDao == null) {
setSaTokenDao(new SaTokenDaoDefaultImpl());
// 如果对象为空,则使用框架默认方式初始化
synchronized (SaTokenManager.class) {
if (saTokenDao == null) {
setSaTokenDao(new SaTokenDaoDefaultImpl());
}
}
}
return saTokenDao;
}
/**
* 权限认证 Bean
*/
public static StpInterface stpInterface;
public static StpInterface getStpInterface() {
if (stpInterface == null) {
initStpInterface();
}
return stpInterface;
}
private static StpInterface stpInterface;
public static void setStpInterface(StpInterface stpInterface) {
SaTokenManager.stpInterface = stpInterface;
}
public synchronized static void initStpInterface() {
public static StpInterface getStpInterface() {
if (stpInterface == null) {
setStpInterface(new StpInterfaceDefaultImpl());
// 如果对象为空,则使用框架默认方式初始化
synchronized (SaTokenManager.class) {
if (stpInterface == null) {
setStpInterface(new StpInterfaceDefaultImpl());
}
}
}
return stpInterface;
}
/**
* 框架行为 Bean
*/
public static SaTokenAction saTokenAction;
public static SaTokenAction getSaTokenAction() {
if (saTokenAction == null) {
initSaTokenAction();
}
return saTokenAction;
}
private static SaTokenAction saTokenAction;
public static void setSaTokenAction(SaTokenAction saTokenAction) {
SaTokenManager.saTokenAction = saTokenAction;
}
public synchronized static void initSaTokenAction() {
public static SaTokenAction getSaTokenAction() {
if (saTokenAction == null) {
setSaTokenAction(new SaTokenActionDefaultImpl());
// 如果对象为空,则使用框架默认方式初始化
synchronized (SaTokenManager.class) {
if (saTokenAction == null) {
setSaTokenAction(new SaTokenActionDefaultImpl());
}
}
}
return saTokenAction;
}
/**
* Cookie操作 Bean
*/
public static SaTokenCookie saTokenCookie;
public static SaTokenCookie getSaTokenCookie() {
if (saTokenCookie == null) {
initSaTokenCookie();
}
return saTokenCookie;
}
private static SaTokenCookie saTokenCookie;
public static void setSaTokenCookie(SaTokenCookie saTokenCookie) {
SaTokenManager.saTokenCookie = saTokenCookie;
}
public synchronized static void initSaTokenCookie() {
public static SaTokenCookie getSaTokenCookie() {
if (saTokenCookie == null) {
setSaTokenCookie(new SaTokenCookieDefaultImpl());
// 如果对象为空,则使用框架默认方式初始化
synchronized (SaTokenManager.class) {
if (saTokenCookie == null) {
setSaTokenCookie(new SaTokenCookieDefaultImpl());
}
}
}
return saTokenCookie;
}
/**
* Servlet操作 Bean
*/
public static SaTokenServlet saTokenServlet;
public static SaTokenServlet getSaTokenServlet() {
if (saTokenServlet == null) {
initSaTokenServlet();
}
return saTokenServlet;
}
private static SaTokenServlet saTokenServlet;
public static void setSaTokenServlet(SaTokenServlet saTokenServlet) {
SaTokenManager.saTokenServlet = saTokenServlet;
}
public synchronized static void initSaTokenServlet() {
public static SaTokenServlet getSaTokenServlet() {
if (saTokenServlet == null) {
setSaTokenServlet(new SaTokenServletDefaultImpl());
// 如果对象为空,则使用框架默认方式初始化
if (saTokenServlet == null) {
setSaTokenServlet(new SaTokenServletDefaultImpl());
}
}
return saTokenServlet;
}
/**
* StpLogic集合, 记录框架所有成功初始化的StpLogic
*/
public static Map<String, StpLogic> stpLogicMap = new HashMap<String, StpLogic>();
/**
* 向集合中 put 一个 StpLogic
* @param stpLogic StpLogic
*/
public static void putStpLogic(StpLogic stpLogic) {
stpLogicMap.put(stpLogic.getLoginKey(), stpLogic);
}
/**
* 根据 LoginKey 获取对应的StpLogic如果不存在则返回null
* @param loginKey 对应的LoginKey
* @return 对应的StpLogic
*/
public static StpLogic getStpLogic(String loginKey) {
return stpLogicMap.get(loginKey);
}
}

View File

@@ -51,6 +51,9 @@ public class SaTokenConfig {
/** 写入Cookie时显式指定的作用域, 常用于单点登录二级域名共享Cookie的场景 */
private String cookieDomain;
/** token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx) */
private String tokenPrefix;
/** 是否在初始化配置时打印版本字符画 */
private Boolean isV = true;
@@ -269,6 +272,22 @@ public class SaTokenConfig {
return this;
}
/**
* @return token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx)
*/
public String getTokenPrefix() {
return tokenPrefix;
}
/**
* @param tokenPrefix token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx)
* @return 对象自身
*/
public SaTokenConfig setTokenPrefix(String tokenPrefix) {
this.tokenPrefix = tokenPrefix;
return this;
}
/**
* @return 是否在初始化配置时打印版本字符画
*/
@@ -285,6 +304,7 @@ public class SaTokenConfig {
return this;
}
/**
* toString
@@ -295,8 +315,8 @@ public class SaTokenConfig {
+ ", allowConcurrentLogin=" + allowConcurrentLogin + ", isShare=" + isShare + ", isReadBody="
+ isReadBody + ", isReadHead=" + isReadHead + ", isReadCookie=" + isReadCookie + ", tokenStyle="
+ tokenStyle + ", dataRefreshPeriod=" + dataRefreshPeriod + ", tokenSessionCheckLogin="
+ tokenSessionCheckLogin + ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain + ", isV="
+ isV + "]";
+ tokenSessionCheckLogin + ", autoRenew=" + autoRenew + ", cookieDomain=" + cookieDomain
+ ", tokenPrefix=" + tokenPrefix + ", isV=" + isV + "]";
}

View File

@@ -44,7 +44,7 @@ public class SaTokenCookieUtil {
*/
public static void addCookie(HttpServletResponse response, String name, String value, String path, String domain, int timeout) {
Cookie cookie = new Cookie(name, value);
if(SaTokenInsideUtil.isEmpty(path) == false) {
if(SaTokenInsideUtil.isEmpty(path) == true) {
path = "/";
}
if(SaTokenInsideUtil.isEmpty(domain) == false) {
@@ -95,4 +95,4 @@ public class SaTokenCookieUtil {
}
}
}
}

View File

@@ -4,12 +4,9 @@ package cn.dev33.satoken.dao;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.util.SaTaskUtil;
import cn.dev33.satoken.util.SaTaskUtil.FunctionRunClass;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
@@ -34,7 +31,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
* 构造函数
*/
public SaTokenDaoDefaultImpl() {
initRefreshTimer();
initRefreshThread();
}
@@ -166,9 +163,15 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
// --------------------- 定时清理过期数据
/**
* 定时任务对象
* 执行数据清理的线程
*/
public Timer refreshTimer;
public Thread refreshThread;
/**
* 是否继续执行数据清理的线程标记
*/
public boolean refreshFlag;
/**
* 清理所有已经过期的key
@@ -183,30 +186,46 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
/**
* 初始化定时任务
*/
public void initRefreshTimer() {
// 如果已经被初始化过了, 则停止它
if(this.refreshTimer != null) {
this.endRefreshTimer();
}
// 开始新的定时任务
if(SaTokenManager.getConfig().getDataRefreshPeriod() < 0) {
public void initRefreshThread() {
// 如果配置了<=0的值则不启动定时清理
if(SaTokenManager.getConfig().getDataRefreshPeriod() <= 0) {
return;
}
int period = SaTokenManager.getConfig().getDataRefreshPeriod() * 1000;
this.refreshTimer = SaTaskUtil.setInterval(new FunctionRunClass() {
@Override
public void run() {
refreshDataMap();
// 启动定时刷新
this.refreshFlag = true;
this.refreshThread = new Thread(() -> {
for (;;) {
try {
try {
// 如果已经被标记为结束
if(refreshFlag == false) {
return;
}
// 执行清理
refreshDataMap();
} catch (Exception e) {
e.printStackTrace();
}
// 休眠N秒
int dataRefreshPeriod = SaTokenManager.getConfig().getDataRefreshPeriod();
if(dataRefreshPeriod <= 0) {
dataRefreshPeriod = 1;
}
Thread.sleep(dataRefreshPeriod * 1000);
} catch (Exception e) {
e.printStackTrace();
}
}
}, period, period);
});
refreshThread.start();
}
/**
* 结束定时任务
*/
public void endRefreshTimer() {
this.refreshTimer.cancel();
public void endRefreshThread() {
this.refreshFlag = false;
}

View File

@@ -1,4 +1,4 @@
package cn.dev33.satoken.interceptor;
package cn.dev33.satoken.router;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

View File

@@ -1,13 +1,9 @@
package cn.dev33.satoken.interceptor;
package cn.dev33.satoken.router;
import java.util.Arrays;
import java.util.List;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.autowired.SaTokenSpringAutowired;
import cn.dev33.satoken.fun.IsRunFunction;
import cn.dev33.satoken.fun.SaFunction;
@@ -18,32 +14,6 @@ import cn.dev33.satoken.fun.SaFunction;
*/
public class SaRouterUtil {
/**
* 在进行路由匹配时所使用的 PathMatcher 对象
*/
private static PathMatcher pathMatcher;
/**
* @return 在进行路由匹配时所使用的的 PathMatcher 对象
*/
public static PathMatcher getPathMatcher() {
if(pathMatcher == null) {
pathMatcher = SaTokenSpringAutowired.pathMatcher;
if(pathMatcher == null) {
pathMatcher = new AntPathMatcher();
}
}
return pathMatcher;
}
/**
* @param pathMatcher 写入: 在进行路由匹配时所使用的的 PathMatcher 对象
*/
public static void setPathMatcher(PathMatcher pathMatcher) {
SaRouterUtil.pathMatcher = pathMatcher;
}
// -------------------- 路由匹配相关 --------------------
/**
@@ -53,10 +23,7 @@ public class SaRouterUtil {
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
if(getPathMatcher().match(pattern, path)) {
return true;
}
return false;
return SaTokenManager.getSaTokenServlet().matchPath(pattern, path);
}
/**

View File

@@ -25,4 +25,12 @@ public interface SaTokenServlet {
*/
public HttpServletResponse getResponse();
/**
* 校验指定路由匹配符是否可以匹配成功指定路径
* @param pattern 路由匹配符
* @param path 需要匹配的路径
* @return 是否匹配成功
*/
public boolean matchPath(String pattern, String path);
}

View File

@@ -18,7 +18,7 @@ public class SaTokenServletDefaultImpl implements SaTokenServlet {
*/
@Override
public HttpServletRequest getRequest() {
throw new SaTokenException("请实现SaTokenServlet接口后进行Servlet相关操作");
throw new SaTokenException("SaTokenServlet接口未实现");
}
/**
@@ -26,7 +26,16 @@ public class SaTokenServletDefaultImpl implements SaTokenServlet {
*/
@Override
public HttpServletResponse getResponse() {
throw new SaTokenException("请实现SaTokenServlet接口后进行Servlet相关操作");
throw new SaTokenException("SaTokenServlet接口未实现");
}
/**
* 校验指定路由匹配符是否可以匹配成功指定路径
*/
@Override
public boolean matchPath(String pattern, String path) {
throw new SaTokenException("SaTokenServlet接口未实现");
}
}

View File

@@ -1,7 +1,5 @@
package cn.dev33.satoken.session;
import cn.dev33.satoken.SaTokenManager;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
@@ -9,6 +7,8 @@ import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaTokenManager;
/**
* Session Model
*
@@ -237,27 +237,6 @@ public class SaSession implements Serializable {
}
// ----------------------- 存取值 (类型转换)
/**
* 从Session中取值转化为Object类型
* @param key key
* @return 值
*/
public Object getObject(String key) {
return getAttribute(key);
}
/**
* 判断一个值是否为null
* @param value 指定值
* @return 此value是否为null
*/
public boolean valueIsNull(Object value) {
return value == null || value.equals("");
}
// ----------------------- 一些操作
/**
@@ -279,4 +258,245 @@ public class SaSession implements Serializable {
}
}
/**
* 获取此Session的剩余存活时间 (单位: 秒)
* @return 此Session的剩余存活时间 (单位: 秒)
*/
public long getTimeout() {
return SaTokenManager.getSaTokenDao().getSessionTimeout(this.id);
}
/**
* 修改此Session的剩余存活时间
* @param timeout 过期时间 (单位: 秒)
*/
public void updateTimeout(long timeout) {
SaTokenManager.getSaTokenDao().updateSessionTimeout(this.id, timeout);
}
/**
* 修改此Session的最小剩余存活时间 (只有在Session的过期时间低于指定的minTimeout时才会进行修改)
* @param minTimeout 过期时间 (单位: 秒)
*/
public void updateMinTimeout(long minTimeout) {
if(getTimeout() < minTimeout) {
SaTokenManager.getSaTokenDao().updateSessionTimeout(this.id, minTimeout);
}
}
/**
* 修改此Session的最大剩余存活时间 (只有在Session的过期时间高于指定的maxTimeout时才会进行修改)
* @param maxTimeout 过期时间 (单位: 秒)
*/
public void updateMaxTimeout(long maxTimeout) {
if(getTimeout() > maxTimeout) {
SaTokenManager.getSaTokenDao().updateSessionTimeout(this.id, maxTimeout);
}
}
// ----------------------- 存取值 (类型转换)
/**
* 写值
* @param key 名称
* @param value 值
*/
public void set(String key, Object value) {
dataMap.put(key, value);
update();
}
/**
* 写值(只有在此key原本无值的时候才会写入)
* @param key 名称
* @param value 值
*/
public void setDefaultValue(String key, Object value) {
if(has(key) == false) {
dataMap.put(key, value);
update();
}
}
/**
* 取值
* @param key key
* @return 值
*/
public Object get(String key) {
return dataMap.get(key);
}
/**
*
* 取值 (指定默认值)
* @param <T> 默认值的类型
* @param key key
* @param defaultValue 取不到值时返回的默认值
* @return 值
*/
public <T> T get(String key, T defaultValue) {
return getValueByDefaultValue(get(key), defaultValue);
}
/**
* 取值 (转String类型)
* @param key key
* @return 值
*/
public String getString(String key) {
Object value = get(key);
if(value == null) {
return null;
}
return String.valueOf(value);
}
/**
* 取值 (转int类型)
* @param key key
* @return 值
*/
public int getInt(String key) {
return getValueByDefaultValue(get(key), 0);
}
/**
* 取值 (转long类型)
* @param key key
* @return 值
*/
public long getLong(String key) {
return getValueByDefaultValue(get(key), 0L);
}
/**
* 取值 (转double类型)
* @param key key
* @return 值
*/
public double getDouble(String key) {
return getValueByDefaultValue(get(key), 0.0);
}
/**
* 取值 (转float类型)
* @param key key
* @return 值
*/
public float getFloat(String key) {
return getValueByDefaultValue(get(key), 0.0f);
}
/**
* 取值 (指定转换类型)
* @param <T> 泛型
* @param key key
* @param cs 指定转换类型
* @return 值
*/
public <T> T getModel(String key, Class<T> cs) {
return getValueByClass(get(key), cs);
}
/**
* 取值 (指定转换类型, 并指定值为Null时返回的默认值)
* @param <T> 泛型
* @param key key
* @param cs 指定转换类型
* @param defaultValue 值为Null时返回的默认值
* @return 值
*/
@SuppressWarnings("unchecked")
public <T> T getModel(String key, Class<T> cs, Object defaultValue) {
Object value = get(key);
if(valueIsNull(value)) {
return (T)defaultValue;
}
return getValueByClass(value, cs);
}
/**
* 是否含有某个key
* @param key has
* @return 是否含有
*/
public boolean has(String key) {
return !valueIsNull(get(key));
}
// --------- 工具方法
/**
* 判断一个值是否为null
* @param value 指定值
* @return 此value是否为null
*/
public boolean valueIsNull(Object value) {
return value == null || value.equals("");
}
/**
* 将指定值转化为指定类型
* @param <T> 泛型
* @param obj 值
* @param cs 类型
* @return 转换后的值
*/
@SuppressWarnings("unchecked")
protected <T> T getValueByClass(Object obj, Class<T> cs) {
// 如果 obj 本来就是 cs 类型
if(obj != null && obj.getClass().equals(cs)) {
return (T)obj;
}
// 开始转换
String obj2 = String.valueOf(obj);
Object obj3 = null;
if (cs.equals(String.class)) {
obj3 = obj2;
} else if (cs.equals(int.class) || cs.equals(Integer.class)) {
obj3 = Integer.valueOf(obj2);
} else if (cs.equals(long.class) || cs.equals(Long.class)) {
obj3 = Long.valueOf(obj2);
} else if (cs.equals(short.class) || cs.equals(Short.class)) {
obj3 = Short.valueOf(obj2);
} else if (cs.equals(byte.class) || cs.equals(Byte.class)) {
obj3 = Byte.valueOf(obj2);
} else if (cs.equals(float.class) || cs.equals(Float.class)) {
obj3 = Float.valueOf(obj2);
} else if (cs.equals(double.class) || cs.equals(Double.class)) {
obj3 = Double.valueOf(obj2);
} else if (cs.equals(boolean.class) || cs.equals(Boolean.class)) {
obj3 = Boolean.valueOf(obj2);
} else {
obj3 = (T)obj;
}
return (T)obj3;
}
/**
* 根据默认值来获取值
* @param <T> 泛型
* @param value 值
* @param defaultValue 默认值
* @return 转换后的值
*/
@SuppressWarnings("unchecked")
protected <T> T getValueByDefaultValue(Object value, T defaultValue) {
// 如果 obj 为 null则直接返回默认值
if(valueIsNull(value)) {
return (T)defaultValue;
}
// 开始转换
Class<T> cs = (Class<T>) defaultValue.getClass();
return getValueByClass(value, cs);
}
}

View File

@@ -24,6 +24,7 @@ import cn.dev33.satoken.fun.SaFunction;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.session.TokenSign;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.dev33.satoken.util.SaTokenInsideUtil;
/**
* sa-token 权限验证,逻辑实现类
@@ -44,6 +45,8 @@ public class StpLogic {
*/
public StpLogic(String loginKey) {
this.loginKey = loginKey;
// 在 SaTokenManager 中记录下此 StpLogic以便根据 LoginKey 进行查找此对象
SaTokenManager.putStpLogic(this);
}
/**
@@ -84,6 +87,32 @@ public class StpLogic {
return SaTokenManager.getSaTokenAction().createToken(loginId, loginKey);
}
/**
* 在当前会话写入当前tokenValue
* @param tokenValue token值
* @param cookieTimeout Cookie存活时间(秒)
*/
public void setTokenValue(String tokenValue, int cookieTimeout){
SaTokenConfig config = getConfig();
// 将token保存到本次request里
HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest();
// 判断是否配置了token前缀
String tokenPrefix = config.getTokenPrefix();
if(SaTokenInsideUtil.isEmpty(tokenPrefix)) {
request.setAttribute(splicingKeyJustCreatedSave(), tokenValue);
} else {
// 如果配置了token前缀则拼接上前缀一起写入
request.setAttribute(splicingKeyJustCreatedSave(), tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT + tokenValue);
}
// 注入Cookie
if(config.getIsReadCookie() == true){
HttpServletResponse response = SaTokenManager.getSaTokenServlet().getResponse();
SaTokenManager.getSaTokenCookie().addCookie(response, getTokenName(), tokenValue,
"/", config.getCookieDomain(), cookieTimeout);
}
}
/**
* 获取当前tokenValue
* @return 当前tokenValue
@@ -115,7 +144,18 @@ public class StpLogic {
}
}
// 5. 返回
// 5. 如果打开了前缀模式
String tokenPrefix = getConfig().getTokenPrefix();
if(SaTokenInsideUtil.isEmpty(tokenPrefix) == false && SaTokenInsideUtil.isEmpty(tokenValue) == false) {
// 如果token以指定的前缀开头, 则裁剪掉它, 否则视为未提供token
if(tokenValue.startsWith(tokenPrefix + SaTokenConsts.TOKEN_CONNECTOR_CHAT)) {
tokenValue = tokenValue.substring(tokenPrefix.length() + SaTokenConsts.TOKEN_CONNECTOR_CHAT.length());
} else {
tokenValue = null;
}
}
// 6. 返回
return tokenValue;
}
@@ -175,7 +215,6 @@ public class StpLogic {
public void setLoginId(Object loginId, SaLoginModel loginModel) {
// ------ 1、获取相应对象
HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest();
SaTokenConfig config = getConfig();
SaTokenDao dao = SaTokenManager.getSaTokenDao();
loginModel.build(config);
@@ -190,7 +229,7 @@ public class StpLogic {
}
} else {
// --- 如果不允许并发登录
// 如果此时[id-session]不为null说明此账号在其他地正在登录现在需要先把其它地的同设备token标记为被顶下线
// 如果此时[user-session]不为null说明此账号在其他地正在登录现在需要先把其它地的同设备token标记为被顶下线
SaSession session = getSessionByLoginId(loginId, false);
if(session != null) {
List<TokenSign> tokenSignList = session.getTokenSignList();
@@ -200,7 +239,7 @@ public class StpLogic {
dao.update(splicingKeyTokenValue(tokenSign.getValue()), NotLoginException.BE_REPLACED);
// 2. 清理掉[token-最后操作时间]
clearLastActivity(tokenSign.getValue());
// 3. 清理账号session上的token签名记录
// 3. 清理user-session上的token签名记录
session.removeTokenSign(tokenSign.getValue());
}
}
@@ -216,27 +255,20 @@ public class StpLogic {
if(session == null) {
session = getSessionByLoginId(loginId);
} else {
// 保证此Session的有效期 >= token的有效期
if(dao.getSessionTimeout(session.getId()) < loginModel.getTimeout()) {
dao.updateSessionTimeout(session.getId(), loginModel.getTimeout());
}
session.updateMinTimeout(loginModel.getTimeout());
}
// 在session上记录token签名
session.addTokenSign(new TokenSign(tokenValue, loginModel.getDevice()));
// ------ 4. 持久化其它数据
// token -> uid
dao.set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), loginModel.getTimeout());
// 将token保存到本次request里
request.setAttribute(splicingKeyJustCreatedSave(), tokenValue);
dao.set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), loginModel.getTimeout());
// 写入 [最后操作时间]
setLastActivityToNow(tokenValue);
// 注入Cookie
if(config.getIsReadCookie() == true){
HttpServletResponse response = SaTokenManager.getSaTokenServlet().getResponse();
SaTokenManager.getSaTokenCookie().addCookie(response, getTokenName(), tokenValue,
"/", config.getCookieDomain(), loginModel.getCookieTimeout());
}
setLastActivityToNow(tokenValue);
// 在当前会话写入当前tokenValue
setTokenValue(tokenValue, loginModel.getCookieTimeout());
}
/**
@@ -577,16 +609,11 @@ public class StpLogic {
if(tokenValue == null || Objects.equals(tokenValue, "")) {
// 随机一个token送给Ta
tokenValue = createTokenValue(null);
// Request做上标记
SaTokenManager.getSaTokenServlet().getRequest().setAttribute(splicingKeyJustCreatedSave(), tokenValue);
// 写入 [最后操作时间]
setLastActivityToNow(tokenValue);
// cookie注入
if(getConfig().getIsReadCookie() == true){
int cookieTimeout = (int)(getConfig().getTimeout() == SaTokenDao.NEVER_EXPIRE ? Integer.MAX_VALUE : getConfig().getTimeout());
SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue,
"/", getConfig().getCookieDomain(), cookieTimeout);
}
// 在当前会话写入这个tokenValue
int cookieTimeout = (int)(getConfig().getTimeout() == SaTokenDao.NEVER_EXPIRE ? Integer.MAX_VALUE : getConfig().getTimeout());
setTokenValue(tokenValue, cookieTimeout);
}
}
// 返回这个token对应的专属session
@@ -1092,7 +1119,7 @@ public class StpLogic {
* @return key
*/
public String splicingKeySwitch() {
return SaTokenConsts.SWITCH_TO_SAVE_KEY + getLoginKey();
return SaTokenConsts.SWITCH_TO_SAVE_KEY + loginKey;
}
/**
@@ -1100,7 +1127,7 @@ public class StpLogic {
* @return key
*/
public String splicingKeyJustCreatedSave() {
return SaTokenConsts.JUST_CREATED_SAVE_KEY + getLoginKey();
return SaTokenConsts.JUST_CREATED_SAVE_KEY + loginKey;
}

View File

@@ -36,6 +36,15 @@ public class StpUtil {
return stpLogic.getTokenName();
}
/**
* 在当前会话写入当前tokenValue
* @param tokenValue token值
* @param cookieTimeout Cookie存活时间(秒)
*/
public static void setTokenValue(String tokenValue, int cookieTimeout){
stpLogic.setTokenValue(tokenValue, cookieTimeout);
}
/**
* 获取当前tokenValue
* @return 当前tokenValue

View File

@@ -1,60 +0,0 @@
package cn.dev33.satoken.util;
import java.util.Timer;
import java.util.TimerTask;
/**
* 任务调度Util
* @author kong
*
*/
public class SaTaskUtil {
/**
* 延时指定毫秒执行一个函数
* @param fc 要执行的函数
* @param delay 延时的毫秒数
* @return timer任务对象
*/
public static Timer setTimeout(FunctionRunClass fc, int delay) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
fc.run();
timer.cancel();
}
}, delay);
return timer;
}
/**
* 延时delay毫秒每隔period毫秒执行一个函数
* @param fc 要执行的函数
* @param delay 延时的毫秒数
* @param period 每隔多少毫秒执行一次
* @return timer任务对象
*/
public static Timer setInterval(FunctionRunClass fc, int delay, int period) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
fc.run();
}
}, delay, period);
return timer;
}
/**
* 封装一个内部类,便于操作
* @author kong
*/
public static interface FunctionRunClass{
/**
* 要执行的方法
*/
public void run();
}
}

View File

@@ -13,7 +13,7 @@ public class SaTokenConsts {
/**
* sa-token 版本号
*/
public static final String VERSION_NO = "v1.14.0";
public static final String VERSION_NO = "v1.15.0";
/**
* sa-token 开源地址
@@ -42,8 +42,8 @@ public class SaTokenConsts {
* 常量key标记: 在进行临时身份切换时使用的key
*/
public static final String SWITCH_TO_SAVE_KEY = "SWITCH_TO_SAVE_KEY_";
// =================== token-style 相关 ===================
/**
@@ -75,6 +75,13 @@ public class SaTokenConsts {
* token风格: tik风格 (2_14_16)
*/
public static final String TOKEN_STYLE_RANDOM_TIK = "tik";
// =================== 其它 ===================
/**
* 连接token前缀和token值的字符
*/
public static final String TOKEN_CONNECTOR_CHAT = " ";
}

View File

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

View File

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

View File

@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.14.0</sa-token-version>
<sa-token-version>1.15.0</sa-token-version>
</properties>
<dependencies>

View File

@@ -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.14.0</sa-token-version>
<sa-token-version>1.15.0</sa-token-version>
</properties>
<dependencies>

View File

@@ -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.14.0</sa-token-version>
<sa-token-version>1.15.0</sa-token-version>
</properties>
<dependencies>
@@ -39,7 +39,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-oauth2</artifactId>
<version>1.14.0-alpha</version>
<version>1.15.0-alpha</version>
</dependency>
<!-- sa-token整合redis (使用jackson序列化方式) -->

View File

@@ -16,7 +16,7 @@
<!-- 定义sa-token版本号 -->
<properties>
<sa-token-version>1.14.0</sa-token-version>
<sa-token-version>1.15.0</sa-token-version>
</properties>
<dependencies>

View File

@@ -1,5 +1,6 @@
package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@@ -8,7 +9,7 @@ import cn.dev33.satoken.SaTokenManager;
@SpringBootApplication
public class SaTokenDemoApplication {
public static void main(String[] args) throws Exception {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功sa-token配置如下" + SaTokenManager.getConfig());
}

View File

@@ -4,29 +4,16 @@ 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.config.SaTokenConfig;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
/**
* sa-token代码方式进行配置
* @author kong
*
*/
@Configuration
public class MySaTokenConfig implements WebMvcConfigurer {
// 获取配置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天
config.setActivityTimeout(-1); // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
config.setAllowConcurrentLogin(true); // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
config.setIsShare(true); // 在多人登录同一账号时是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
config.setTokenStyle("uuid"); // token风格
return config;
}
// 注册sa-token的拦截器打开注解式鉴权功能
@Override
public void addInterceptors(InterceptorRegistry registry) {
@@ -34,5 +21,4 @@ public class MySaTokenConfig implements WebMvcConfigurer {
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
}
}

View File

@@ -5,9 +5,10 @@ import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.bind.annotation.ResponseBody;
import com.pj.util.AjaxJson;
@@ -18,7 +19,7 @@ import cn.dev33.satoken.exception.NotRoleException;
/**
* 全局异常处理
*/
@RestControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin")
public class GlobalException {
// 在当前类每个方法进入之前触发的操作
@@ -28,14 +29,14 @@ public class GlobalException {
}
// 全局异常拦截(拦截项目中的所有异常)
@ResponseBody
@ExceptionHandler
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
throws Exception {
// 打印堆栈,以供调试
System.out.println("全局异常---------------");
e.printStackTrace();
// 不同异常返回不同状态码

View File

@@ -3,8 +3,6 @@ package com.pj.test;
import java.util.Date;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -241,7 +239,7 @@ public class TestController {
// 测试 浏览器访问: http://localhost:8081/test/test
@RequestMapping("test")
public AjaxJson test(HttpServletResponse response) {
public AjaxJson test() {
System.out.println("进来了");
// StpUtil.setLoginId(10001, new SaLoginModel()
// .setDevice("PC") // 此次登录的客户端设备标识, 用于[同端互斥登录]时指定此次登录的设备名称

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">
</p>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.14.0</h1>
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.15.0</h1>
<h4 align="center">这可能是史上功能最全的Java权限认证框架</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.14.0-2B9939"></a>
<a href="https://github.com/click33/sa-token"><img src="https://img.shields.io/badge/sa--token-v1.15.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>
@@ -58,6 +58,8 @@ sa-token是一个轻量级Java权限认证框架主要解决登录认证
- **路由拦截式鉴权** —— 根据路由拦截鉴权可适配restful模式
- **自动续签** —— 提供两种token过期策略灵活搭配使用还可自动续签
- **会话治理** —— 提供方便灵活的会话查询接口
- **记住我模式** —— 适配[记住我]模式,重启浏览器免验证
- **密码加密** —— 提供密码加密模块可快速MD5、SHA1、SHA256、AES、RSA加密
- **组件自动注入** —— 零配置与Spring等框架集成
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
@@ -182,6 +184,7 @@ sa-token秉承着开放的思想欢迎大家贡献代码为框架添砖加
## 友情链接
[**[ okhttps ]** 一个轻量级http通信框架API设计无比优雅支持 WebSocket 以及 Stomp 协议](https://gitee.com/ejlchina-zhxu/okhttps)
[**[ 小诺快速开发平台 ]** 基于SpringBoot2 + AntDesignVue全新快速开发平台同时拥有三个版本](https://xiaonuo.vip/index#pricing)
## 交流群
QQ交流群[1002350610 点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM)

View File

@@ -17,6 +17,7 @@
- [注解式鉴权](/use/at-check)
- [路由拦截式鉴权](/use/route-check)
- [花式token](/use/token-style)
- [Token前缀](/use/token-prefix)
- [框架配置](/use/config)
- [会话治理](/use/search-session)
- [记住我模式](/use/remember-me)
@@ -37,6 +38,7 @@
- [token有效期详解](/fun/token-timeout)
- [Session模型详解](/fun/session-model)
- [TokenInfo参数详解](/fun/token-info)
- [框架源码所有技术栈](/fun/tech-stack)

View File

@@ -0,0 +1,15 @@
# sa-token 源码用到的所有技术栈
包括但不限于以下:
- Maven多模块项目
- Servlet API、临时Cookie与永久Cookie、Request参数获取
- SpringBoot2.0、Redis、Jackson、Hutool、jwt
- SpringBoot自定义starter、Spring包扫码 + 依赖注入、AOP注解切面、yml配置映射、拦截器
- Java8 接口与default实现、静态方法、枚举、定时器、异常类、泛型、反射、IO流、自定义注解、Lambda表达式、函数式编程
- package-info注释、Serializable序列化接口、synchronized锁
- java加密算法MD5、SHA1、SHA256、AES、RSA
- OAuth2.0、同域单点登录、集群与分布式、路由Ant匹配

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、分布式会话、单点登录、前后台分离、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、Spring集成...零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<meta name="description" content="sa-token是一个java权限认证框架功能全面上手简单登录验证、权限验证、Session会话、踢人下线、集成Redis、分布式会话、单点登录、前后台分离、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成...有了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">
@@ -21,6 +21,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.14.0/doc/index.html">v1.14.0</option>
<option value="http://sa-token.dev33.cn/v/v1.13.0/doc/index.html">v1.13.0</option>
<option value="http://sa-token.dev33.cn/v/v1.12.1/doc/index.html">v1.12.1</option>
<option value="http://sa-token.dev33.cn/v/v1.12.0/doc/index.html">v1.12.0</option>
@@ -43,7 +44,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.14.0</sub>'
name += '<b style="font-size: 24px; vertical-align: middle;">sa-token</b> <sub>v1.15.0</sub>'
window.$docsify = {
name: name, // 名字
repo: 'https://github.com/click33/sa-token', // github地址

View File

@@ -19,6 +19,7 @@
[[vue-next-admin] - 一套为开发者快速开发准备的基于 vue2.x 越看越精彩的后台管理系统一站式平台模板](https://gitee.com/lyt-top/vue-admin-wonderful)
[[小诺快速开发平台] - 基于SpringBoot2 + AntDesignVue全新快速开发平台同时拥有三个版本](https://xiaonuo.vip/index#pricing)
<br>
虚位以待...

View File

@@ -1,5 +1,26 @@
# 更新日志
### 2021-3-23 @v1.15.0
- 新增:文档添加源码涉及技术栈说明
- 优化:优化路由拦截器模块文档,更简洁的示例
- 修复修复非web环境下的错误提示Request->Response
- 修复修复Cookie注入时path判断错误感谢@zhangzi0291提供的PR
- 新增文档集成Redis章节新增redis配置示例说明感谢群友 `@-)` 提供的建议
- 新增增加token前缀模式可在配置token读取前缀适配`Bearer token`规范 **[重要]**
- 优化:`SaTokenManager`初始化Bean去除`initXxx`方法,优化代码逻辑
- 新增:`SaTokenManager`新增`stpLogicMap`集合,记录所有`StpLogic`的初始化,方便查找
- 新增:`Session`新增timeout操作API可灵活修改Session的剩余有效时间
- 新增token前缀改为强制校验模式如果配置了前缀则前端提交token时必须带有
- 优化:精简`SaRouteInterceptor`,只保留自定义验证和默认的登陆验证,去除冗余功能
- 优化:`SaRouterUtil`迁移到core核心包优化依赖架构
- 优化默认Dao实现类里`Timer定时器`改为子线程 + sleep 模拟
- 新增:`Session`新增各种类型转换API可快速方便存取值 **[重要]**
- 升级注意:
- `SaRouterUtil`类迁移到核心包注意更换import地址
- `SaRouteInterceptor`去出冗余API详情参考路由鉴权部分
### 2021-3-12 @v1.14.0
- 新增:新增`SaLoginModel`登录参数Model适配 [记住我] 模式 **[重要]**
- 新增:新增 `StpUtil.setLoginId()` 时指定token有效期可灵活控制用户的一次登录免验证时长

View File

@@ -9,14 +9,14 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.14.0</version>
<version>1.15.0</version>
</dependency>
```
## Gradle依赖
Gradle用户引入依赖
```
implementation 'cn.dev33:sa-token-spring-boot-starter:1.14.0'
implementation 'cn.dev33:sa-token-spring-boot-starter:1.15.0'
```

View File

@@ -19,7 +19,7 @@
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.14.0</version>
<version>1.15.0</version>
</dependency>
```

View File

@@ -21,7 +21,7 @@ sa-token内置两种模式完成注解鉴权分别是`AOP模式`和`拦截器
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-aop</artifactId>
<version>1.14.0</version>
<version>1.15.0</version>
</dependency>
```

View File

@@ -71,4 +71,5 @@ public class MySaTokenConfig {
| dataRefreshPeriod | int | 30 | 默认dao层实现类中每次清理过期数据间隔的时间 (单位: 秒) 默认值30秒设置为-1代表不启动定时清理 |
| tokenSessionCheckLogin | Boolean | true | 获取token专属session时是否必须登录 (如果配置为true会在每次获取token专属session时校验是否登录) |
| autoRenew | Boolean | true | 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用getLoginId()时进行一次过期检查与续签操作) |
| tokenPrefix | Boolean | true | token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx) [参考token前缀](/use/token-prefix) |
| isV | Boolean | true | 是否在初始化配置时打印版本字符画 |

View File

@@ -14,7 +14,7 @@ Sa-token默认将会话数据保存在内存中此模式读写速度最快
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis</artifactId>
<version>1.14.0</version>
<version>1.15.0</version>
</dependency>
```
优点兼容性好缺点Session序列化后基本不可读对开发者来讲等同于乱码
@@ -26,7 +26,7 @@ Sa-token默认将会话数据保存在内存中此模式读写速度最快
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-dao-redis-jackson</artifactId>
<version>1.14.0</version>
<version>1.15.0</version>
</dependency>
```
优点Session序列化后可读性强可灵活手动修改缺点兼容性稍差
@@ -47,7 +47,33 @@ Sa-token默认将会话数据保存在内存中此模式读写速度最快
```
**2. 引入了依赖我还需要为Redis配置连接信息吗** <br>
需要只有项目初始化了正确的Redis实例`sa-token`才可以使用Redis进行数据持久化参考[application-dev.yml](https://gitee.com/sz6/sa-plus/blob/master/sp-server/src/main/resources/application-dev.yml)
需要只有项目初始化了正确的Redis实例`sa-token`才可以使用Redis进行数据持久化参考以下`yml配置`
``` java
# 端口
spring:
# redis配置
redis:
# Redis数据库索引默认为0
database: 1
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器连接密码默认为空
# password:
# 连接超时时间(毫秒)
timeout: 1000ms
lettuce:
pool:
# 连接池最大连接数
max-active: 200
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1ms
# 连接池中的最大空闲连接
max-idle: 10
# 连接池中的最小空闲连接
min-idle: 0
```
**3. 集成Redis后是我额外手动保存数据还是框架自动保存** <br>

View File

@@ -9,6 +9,7 @@
再往底了说,就是每个账号都会拥有一个权限码集合,我来验证这个集合中是否包含指定的权限码 <br/>
例如:当前账号拥有权限码集合:`["user:add", "user:delete", "user:get"]`,这时候我来验证权限 `"user:update"`,则其结果就是:**验证失败,禁止访问**
(注意: 冒号无特殊含义,可有可无)
![无权限](../static/not-jur.png)

View File

@@ -44,11 +44,11 @@
``` java
// 底层的 StpLogic 对象
public static StpLogic stpLogic = new StpLogic("user") {
// 重写 `getTokenName` 函数,返回一个与 `StpUtil` 不同的token名称, 防止冲突
// 重写 `splicingKeyTokenName` 函数,返回一个与 `StpUtil` 不同的token名称, 防止冲突
@Override
public String getTokenName() {
return super.getKeyTokenName() + "-user";
}
public String splicingKeyTokenName() {
return super.splicingKeyTokenName()+"-user";
}
};
```

View File

@@ -10,7 +10,7 @@
## 1、注册路由拦截器
`springboot2.0`为例, 新建配置类`MySaTokenConfig.java`
`SpringBoot2.0`为例, 新建配置类`MySaTokenConfig.java`
``` java
@Configuration
public class MySaTokenConfig implements WebMvcConfigurer {
@@ -22,49 +22,19 @@ public class MySaTokenConfig implements WebMvcConfigurer {
}
}
```
以上代码,我们注册了一个登录验证拦截器,并且排除了`/user/doLogin`接口用来开放登录 <br>
以上代码,我们注册了一个登录验证拦截器,并且排除了`/user/doLogin`接口用来开放登录(除了`/user/doLogin`以外的所有接口都需要登录才能访问) <br>
那么我们如何进行权限认证拦截呢,且往下看
## 2、所有拦截器示例
``` java
@Configuration
public class MySaTokenConfig implements WebMvcConfigurer {
// 注册sa-token的所有拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册一个登录验证拦截器
registry.addInterceptor(SaRouteInterceptor.createLoginVal()).addPathPatterns("/**").excludePathPatterns("/user/doLogin");
// 注册一个角色认证拦截器
registry.addInterceptor(SaRouteInterceptor.createRoleVal("super-admin")).addPathPatterns("/**");
// 注册一个权限认证拦截器
registry.addInterceptor(SaRouteInterceptor.createPermissionVal("user:add", "user:deelete")).addPathPatterns("/UserController/**");
// 注册一个自定义认证拦截器 (可以写任意认证代码)
registry.addInterceptor(new SaRouteInterceptor((request, response, handler)->{
System.out.println("---------- 进入自定义认证 --------------- ");
// 你可以在这里写任意认证代码, 例如: StpUtil.checkLogin();
})).addPathPatterns("/**");
}
}
```
(你不必像上面的示例一样注册所有拦截器,只要按需注册即可
## 3、让我们利用自定义拦截器做点快活的事情
你可以根据路由划分模块,不同模块不同鉴权
## 2、自定义权限验证规则
你可以使用函数式编程自定义验证规则
``` java
@Configuration
public class MySaTokenConfig implements WebMvcConfigurer {
// 注册sa-token的所有拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册路由拦截器,自定义验证规则
registry.addInterceptor(new SaRouteInterceptor((request, response, handler)->{
// 根据路由划分模块,不同模块不同鉴权
SaRouterUtil.match("/user/**", () -> StpUtil.checkPermission("user"));
@@ -78,27 +48,8 @@ public class MySaTokenConfig implements WebMvcConfigurer {
}
```
## 4、多账号模式下使用方式
很简单,把StpUtil类换成新的权限验证类(比如多账号验证示例里面的StpUserUtil)即可其它调用方法不变
``` java
@Configuration
public class MySaTokenConfig implements WebMvcConfigurer {
// 注册sa-token的所有拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new SaRouteInterceptor((request, response, handler)->{
// 根据路由划分模块,不同模块不同鉴权
SaRouterUtil.match("/user/**", () -> StpUserUtil.checkPermission("user"));
SaRouterUtil.match("/admin/**", () -> StpUtil.checkPermission("admin"));
})).addPathPatterns("/**");
}
}
```
## 5、完整示例
最终的代码,可能会类似于下面的样子:
## 3、完整示例
所有用法示例:
``` java
@Configuration
@@ -106,12 +57,15 @@ public class MySaTokenConfig implements WebMvcConfigurer {
// 注册sa-token的拦截器
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 自定义验证拦截器
// 注册路由拦截器,自定义验证规则
registry.addInterceptor(new SaRouteInterceptor((request, response, handler) -> {
// 登录验证 -- 拦截所有路由,并排除/user/doLogin 用于开放登录
SaRouterUtil.match("/**", "/user/doLogin", () -> StpUtil.checkLogin());
// 登录验证 -- 排除多个路径
SaRouterUtil.match(Arrays.asList("/**"), Arrays.asList("/user/doLogin", "/user/reg"), () -> StpUtil.checkLogin());
// 角色认证 -- 拦截以 admin 开头的路由,必须具备[admin]角色或者[super-admin]角色才可以通过认证
SaRouterUtil.match("/admin/**", () -> StpUtil.checkRoleOr("admin", "super-admin"));
@@ -126,6 +80,16 @@ public class MySaTokenConfig implements WebMvcConfigurer {
// 匹配 restful 风格路由
SaRouterUtil.match("/article/get/{id}", () -> StpUtil.checkPermission("article"));
// 检查请求方式
SaRouterUtil.match("/notice/**", () -> {
if(request.getMethod().equals(HttpMethod.GET.toString())) {
StpUtil.checkPermission("notice");
}
});
// 在多账号模式下可以使用任意StpUtil进行校验
SaRouterUtil.match("/user/**", () -> StpUserUtil.checkLogin());
})).addPathPatterns("/**");
}
}
@@ -135,8 +99,18 @@ public class MySaTokenConfig implements WebMvcConfigurer {
## 注意事项
在`v1.14`及以前版本下,路由拦截器提供了提供了封装式写法,该方法代码比较冗余,在`v1.15`版本已移除,解决方案如下:
``` java
// 原写法
registry.addInterceptor(SaRouteInterceptor.createPermissionVal("user")).addPathPatterns("/user/**");
// 改为以下方式,效果同上
registry.addInterceptor(new SaRouteInterceptor((request, response, handler) -> {
SaRouterUtil.match("/user/**", () -> StpUtil.checkPermission("user"));
})).addPathPatterns("/**");
```

View File

@@ -65,19 +65,97 @@ SaSessionCustomUtil.deleteSessionById("goods-10001");
### 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会话 (从持久库删除此Session)
// 返回此Session的id
session.getId();
// 返回此Session的创建时间 (时间戳)
session.getCreateTime();
// 在Session上获取一个
session.getAttribute('name');
// 在Session上获取一个值并指定取不到值时返回的默认值
session.getAttribute('name', 'zhang');
// 在Session上写入一个值
session.setAttribute('name', 'zhang');
// 在Session上移除一个值
session.removeAttribute('name');
// 清空此Session的所有值
session.clearAttribute();
// 获取此Session是否含有指定key (返回true或false)
session.containsAttribute('name');
// 获取此Session会话上所有key (返回Set<String>)
session.attributeKeys();
// 返回此Session会话上的底层数据对象如果更新map里的值请调用session.update()方法避免产生脏数据)
session.getDataMap();
// 将这个Session从持久库更新一下
session.update();
// 注销此Session会话 (从持久库删除此Session)
session.logout();
```
具体可参考`javax.servlet.http.HttpSession``SaSession`所含方法与其大体类似
### 类型转换API
由于Session存取值默认的类型都是Object因此我们通常会写很多不必要类型转换代码 <br>
为了简化操作sa-token自`v1.15.0`封装了存取值API的类型转换你可以非常方便的调用以下方法
``` java
// 写值
session.set("name", "zhang");
// 写值(只有在此key原本无值的时候才会写入)
session.set("name", "zhang");
// 取值
session.get("name");
// 取值 (指定默认值)
session.get("name", "<defaultValue>");
// 取值 (转String类型)
session.getString("name");
// 取值 (转int类型)
session.getInt("age");
// 取值 (转long类型)
session.getLong("age");
// 取值 (转double类型)
session.getDouble("result");
// 取值 (转float类型)
session.getFloat("result");
// 取值 (指定转换类型)
session.getModel("key", Student.class);
// 取值 (指定转换类型, 并指定值为Null时返回的默认值)
session.getModel("key", Student.class, <defaultValue>);
// 是否含有某个key
session.has("key");
```
### Session环境隔离说明
有同学经常会把 `SaSession` 与 `HttpSession` 进行混淆,例如:
``` java
@PostMapping("/resetPoints")
public void reset(HttpSession session) {
// 在HttpSession上写入一个值
session.setAttribute("name", 66);
// 在SaSession进行取值
System.out.println(StpUtil.getSession().getAttribute("name")); // 输出null
}
```
**要点:**
1. `SaSession` 与 `HttpSession` 没有任何关系,在`HttpSession`上写入的值,在`SaSession`中无法取出
2. `HttpSession`并未被框架接管在使用sa-token时请在任何情况下均使用`SaSession`,不要使用`HttpSession`

View File

@@ -0,0 +1,32 @@
# Token前缀
### 需求场景
在某些系统中前端提交token时会在前面加个固定的前缀例如
``` js
{
"satoken": "Bearer xxxx-xxxx-xxxx-xxxx"
}
```
此时后端如果不做任何特殊处理,框架将会把`Bearer `视为token的一部分无法正常读取token信息导致鉴权失败
为此我们需要在yml中添加如下配置
``` java
spring:
# sa-token配置
sa-token:
# token前缀
tokenPrefix: Bearer
```
此时 sa-token 便可在读取token时裁剪掉 `Bearer`,成功获取`xxxx-xxxx-xxxx-xxxx`
### 注意点
1. `token前缀` 与 `token值` 之间必须有一个空格
2. 一旦配置了`token前缀`则前端提交token时必须带有前缀否则会导致框架无法读取token
3. 由于`Cookie`中无法存储空格字符也就意味配置token前缀后`Cookie`鉴权方式将会失效此时只能将token提交到`header`里进行传输

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、分布式会话、单点登录、前后台分离、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、Spring集成...零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有">
<meta name="description" content="sa-token是一个java权限认证框架功能全面上手简单登录验证、权限验证、Session会话、踢人下线、集成Redis、分布式会话、单点登录、前后台分离、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成...有了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">
@@ -41,10 +41,10 @@
<!-- 内容部分 -->
<div class="main-box">
<div class="content-box">
<h1>sa-token<small>v1.14.0</small></h1>
<h1>sa-token<small>v1.15.0</small></h1>
<div class="sub-title">这可能是史上功能最全的java权限认证框架</div>
<!-- <p>0配置开箱即用低学习成本</p> -->
<p>登录验证、权限验证、Session会话、踢人下线、集成Redis、分布式会话、单点登录、前后台分离、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、Spring集成...</p>
<p>登录验证、权限验证、Session会话、踢人下线、集成Redis、分布式会话、单点登录、前后台分离、记住我模式、模拟他人账号、临时身份切换、多账号体系、注解式鉴权、路由拦截式鉴权、花式token、自动续签、同端互斥登录、会话治理、密码加密、jwt集成、Spring集成...</p>
<!-- <p>零配置开箱即用,覆盖所有应用场景,你所需要的功能,这里都有</p> -->
<p>有了sa-token你所有的权限认证问题都不再是问题</p>
<div class="btn-box">

View File

@@ -7,13 +7,13 @@
<parent>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-parent</artifactId>
<version>1.14.0</version>
<version>1.15.0</version>
</parent>
<packaging>jar</packaging>
<name>sa-token-dao-redis</name>
<artifactId>sa-token-oauth2</artifactId>
<version>1.14.0-alpha</version>
<version>1.15.0-alpha</version>
<description>sa-token realization oauth2.0</description>
<dependencies>

View File

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

View File

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

View File

@@ -105,19 +105,15 @@ public class SaTokenSpringAutowired {
SaTokenManager.setSaTokenServlet(saTokenServlet);
}
/**
* 路由匹配器
*/
public static PathMatcher pathMatcher;
/**
* 利用自动匹配特性获取SpringMVC框架内部使用的路由匹配器
*
* @param pathMatcher 要设置的 pathMatcher
*/
@Autowired(required = false)
public static void setPathMatcher(PathMatcher pathMatcher) {
SaTokenSpringAutowired.pathMatcher = pathMatcher;
public void setPathMatcher(PathMatcher pathMatcher) {
SaTokenServletSpringImpl.setPathMatcher(pathMatcher);
}
}

View File

@@ -5,8 +5,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.stp.StpLogic;
import cn.dev33.satoken.router.SaRouteFunction;
import cn.dev33.satoken.stp.StpUtil;
/**
@@ -15,204 +14,25 @@ import cn.dev33.satoken.stp.StpUtil;
*/
public class SaRouteInterceptor implements HandlerInterceptor {
// ----------------- 属性 -----------------
/**
* 底层的 StpLogic 对象
*/
private StpLogic stpLogic;
/**
* 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证)
*/
private int type;
/**
* 验证模式 AND | OR
*/
private SaMode mode;
/**
* 标识码数组
*/
private String[] codes;
/**
* 自定义模式下的执行函数
*/
private SaRouteFunction function;
/**
* 表示登录验证
*/
public static final int LOGIN = 1;
/**
* 表示角色验证
*/
public static final int ROLE = 2;
/**
* 表示权限验证
*/
public static final int PERMISSION = 3;
/**
* 表示自定义验证
*/
public static final int CUSTOM = 4;
/**
* @return 底层的 StpLogic 对象
*/
public StpLogic getStpLogic() {
if(stpLogic == null) {
stpLogic = StpUtil.stpLogic;
}
return stpLogic;
}
/**
* @param stpLogic 底层的 StpLogic 对象
* @return 拦截器自身
*/
public SaRouteInterceptor setStpLogic(StpLogic stpLogic) {
this.stpLogic = stpLogic;
return this;
}
/**
* @return 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证)
*/
public int getType() {
return type;
}
/**
* @param type 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证)
* @return 拦截器自身
*/
public SaRouteInterceptor setType(int type) {
this.type = type;
return this;
}
/**
* @return 验证模式 AND | OR
*/
public SaMode getMode() {
return mode;
}
/**
* @param mode 验证模式 AND | OR
* @return 拦截器自身
*/
public SaRouteInterceptor setMode(SaMode mode) {
this.mode = mode;
return this;
}
/**
* @return 标识码数组
*/
public String[] getCodes() {
return codes;
}
/**
* @param codes 标识码数组
* @return 拦截器自身
*/
public SaRouteInterceptor setCodes(String... codes) {
this.codes = codes;
return this;
}
/**
* @return 自定义模式下的执行函数
*/
public SaRouteFunction getFunction() {
return function;
}
/**
* @param function 设置自定义模式下的执行函数
* @return 拦截器自身
*/
public SaRouteInterceptor setFunction(SaRouteFunction function) {
this.type = SaRouteInterceptor.CUSTOM;
this.function = function;
return this;
}
// ----------------- 构建相关 -----------------
/**
* 创建 (全参数)
* @param type 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证)
* @param mode 验证模式 AND | OR
* @param codes 标识码数组
*/
public SaRouteInterceptor(int type, SaMode mode, String... codes) {
super();
this.type = type;
this.mode = mode;
this.codes = codes;
}
/**
* 创建 (默认为登录验证)
*/
public SaRouteInterceptor() {
this(SaRouteInterceptor.LOGIN, null, new String[0]);
}
public SaRouteFunction function;
/**
* 创建 (默认为自定义认证)
* @param function 自定义模式下的执行函数
*/
public SaRouteInterceptor(SaRouteFunction function) {
this(SaRouteInterceptor.CUSTOM, null, new String[0]);
setFunction(function);
this.function = function;
}
/**
* 构建一个模式为登录认证的sa路由拦截器
* @return sa拦截器
*/
public static SaRouteInterceptor createLoginVal() {
return new SaRouteInterceptor();
}
/**
* 构建一个模式为角色认证的sa路由拦截器
* @param roles 需要验证的角色标识列表
* @return sa拦截器
*/
public static SaRouteInterceptor createRoleVal(String... roles) {
return new SaRouteInterceptor(SaRouteInterceptor.ROLE, SaMode.AND, roles);
}
/**
* 构建一个模式为权限认证的sa路由拦截器
* @param permissions 需要验证的权限列表
* @return sa拦截器
*/
public static SaRouteInterceptor createPermissionVal(String... permissions) {
return new SaRouteInterceptor(SaRouteInterceptor.PERMISSION, SaMode.AND, permissions);
}
/**
* 创建一个模式为自定义认证的sa路由拦截器
* 静态方法快速构建一个
* @param function 自定义模式下的执行函数
* @return sa拦截器
* @return sa路由拦截器
*/
public static SaRouteInterceptor createCustomVal(SaRouteFunction function) {
public static SaRouteInterceptor newInstance(SaRouteFunction function) {
return new SaRouteInterceptor(function);
}
@@ -226,22 +46,10 @@ public class SaRouteInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
// 根据模式进行验证
if(this.type == SaRouteInterceptor.LOGIN) {
getStpLogic().checkLogin();
} else if(this.type == SaRouteInterceptor.ROLE) {
if(mode == SaMode.AND) {
getStpLogic().checkRoleAnd(codes);
} else {
getStpLogic().checkRoleOr(codes);
}
} else if(this.type == SaRouteInterceptor.PERMISSION) {
if(mode == SaMode.AND) {
getStpLogic().checkPermissionAnd(codes);
} else {
getStpLogic().checkPermissionOr(codes);
}
} else if(this.type == SaRouteInterceptor.CUSTOM) {
// 如果未提供function默认进行登录验证
if(function == null) {
StpUtil.checkLogin();
} else {
function.run(request, response, handler);
}
@@ -250,6 +58,4 @@ public class SaRouteInterceptor implements HandlerInterceptor {
}
}

View File

@@ -3,10 +3,13 @@ package cn.dev33.satoken.spring;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.servlet.SaTokenServlet;
/**
* sa-token 对cookie的相关操作 接口实现类
* sa-token 对Cookie的相关操作 接口实现类
*
* @author kong
*
@@ -29,4 +32,37 @@ public class SaTokenServletSpringImpl implements SaTokenServlet {
return SpringMVCUtil.getResponse();
}
/**
* 路由匹配器
*/
private static PathMatcher pathMatcher;
/**
* 获取路由匹配器
* @return 路由匹配器
*/
public static PathMatcher getPathMatcher() {
if(pathMatcher == null) {
pathMatcher = new AntPathMatcher();
}
return pathMatcher;
}
/**
* 写入路由匹配器
* @param pathMatcher 路由匹配器
*/
public static void setPathMatcher(PathMatcher pathMatcher) {
SaTokenServletSpringImpl.pathMatcher = pathMatcher;
}
/**
* 校验指定路由匹配符是否可以匹配成功指定路径
*/
@Override
public boolean matchPath(String pattern, String path) {
return getPathMatcher().match(pattern, path);
}
}

View File

@@ -34,7 +34,7 @@ public class SpringMVCUtil {
public static HttpServletResponse getResponse() {
ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
if(servletRequestAttributes == null) {
throw new SaTokenException("非Web上下文无法获取Request");
throw new SaTokenException("非Web上下文无法获取Response");
}
return servletRequestAttributes.getResponse();
}