mirror of
https://gitee.com/dromara/sa-token.git
synced 2026-02-27 16:50:24 +08:00
Compare commits
42 Commits
v1.13.6
...
v1.14.4-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50ffda7bef | ||
|
|
a122a8b083 | ||
|
|
69ce298a8a | ||
|
|
f92e64b9cf | ||
|
|
d3138cefc8 | ||
|
|
71f2522f08 | ||
|
|
a5e9bd6714 | ||
|
|
27bb436a90 | ||
|
|
d75c91f0ab | ||
|
|
8ee9f215a6 | ||
|
|
1df216879c | ||
|
|
91998af186 | ||
|
|
f33df38f64 | ||
|
|
aaf4bb8931 | ||
|
|
5808710bbf | ||
|
|
62787c3257 | ||
|
|
6dd39505f9 | ||
|
|
d86f72b469 | ||
|
|
7b7920123a | ||
|
|
8872982455 | ||
|
|
80f07ae01e | ||
|
|
540730d9a4 | ||
|
|
03c8abb269 | ||
|
|
f9ad71db66 | ||
|
|
9fe0e1f97b | ||
|
|
9f6717f8c0 | ||
|
|
af2d04ca1f | ||
|
|
64e6b58a05 | ||
|
|
275b3cec6f | ||
|
|
e92ff9fb7d | ||
|
|
cffd462cd8 | ||
|
|
9ca2cad5e6 | ||
|
|
a076c8f8fc | ||
|
|
dbddd05a6e | ||
|
|
18760010fc | ||
|
|
9151f9a5c8 | ||
|
|
cdaf9fa65a | ||
|
|
c316746f0d | ||
|
|
0695ee6cdf | ||
|
|
3716b874e3 | ||
|
|
255a15033e | ||
|
|
ad70bb0300 |
21
README.md
21
README.md
@@ -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.13.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.14.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.13.0-2B9939"></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/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等框架集成
|
||||
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
|
||||
|
||||
@@ -87,7 +89,8 @@ StpUtil.checkLogin();
|
||||
@SaCheckPermission("user:add")
|
||||
@RequestMapping("/user/insert")
|
||||
public String insert(SysUser user) {
|
||||
return "用户增加";
|
||||
// ...
|
||||
return "用户增加";
|
||||
}
|
||||
```
|
||||
|
||||
@@ -179,16 +182,20 @@ 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)
|
||||
|
||||
|
||||
## 交流群
|
||||
QQ交流群:[1002350610 点击加入](https://jq.qq.com/?_wv=1027&k=45H977HM)
|
||||
|
||||

|
||||

|
||||
|
||||
**微信群** :
|
||||
**微信群**
|
||||
|
||||

|
||||

|
||||
|
||||
(扫码添加微信,备注:sa-token,邀您加入群聊)
|
||||
|
||||
<br>
|
||||
|
||||
4
pom.xml
4
pom.xml
@@ -8,7 +8,7 @@
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<packaging>pom</packaging>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
|
||||
<!-- 项目介绍 -->
|
||||
<name>sa-token</name>
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
<!-- 一些属性 -->
|
||||
<properties>
|
||||
<sa-token-version>1.13.0</sa-token-version>
|
||||
<sa-token-version>1.14.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>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
@@ -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,148 @@ 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();
|
||||
}
|
||||
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
|
||||
*/
|
||||
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) {
|
||||
for (String key : stpLogicMap.keySet()) {
|
||||
if(key.equals(loginKey)) {
|
||||
return stpLogicMap.get(key);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,14 +23,27 @@ public class IsRunFunction {
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据变量决定是否执行此函数
|
||||
*
|
||||
* 当 isRun == true 时执行此函数
|
||||
* @param function 函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public void exe(SaFunction function) {
|
||||
public IsRunFunction exe(SaFunction function) {
|
||||
if (isRun) {
|
||||
function.run();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 当 isRun == false 时执行此函数
|
||||
* @param function 函数
|
||||
* @return 对象自身
|
||||
*/
|
||||
public IsRunFunction noExe(SaFunction function) {
|
||||
if (!isRun) {
|
||||
function.run();
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
package cn.dev33.satoken.secure;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* Base64工具类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaBase64Util {
|
||||
|
||||
private static Base64.Encoder encoder = Base64.getEncoder();
|
||||
private static Base64.Decoder decoder = Base64.getDecoder();
|
||||
|
||||
/**
|
||||
* Base64编码,byte[] 转 String
|
||||
* @param bytes byte[]
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String encodeBytesToString(byte[] bytes){
|
||||
return encoder.encodeToString(bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64解码,String 转 byte[]
|
||||
* @param text 字符串
|
||||
* @return byte[]
|
||||
*/
|
||||
public static byte[] decodeStringToBytes(String text){
|
||||
return decoder.decode(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64编码,String 转 String
|
||||
* @param text 字符串
|
||||
* @return Base64格式字符串
|
||||
*/
|
||||
public static String encode(String text){
|
||||
try {
|
||||
return encoder.encodeToString(text.getBytes("UTF-8"));
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Base64解码,String 转 String
|
||||
* @param base64Text Base64格式字符串
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String decode(String base64Text){
|
||||
try {
|
||||
return new String(decoder.decode(base64Text), "UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,443 @@
|
||||
package cn.dev33.satoken.secure;
|
||||
|
||||
import java.security.InvalidParameterException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.interfaces.RSAPrivateKey;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
/**
|
||||
* sa-token 常见加密算法工具类
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaSecureUtil {
|
||||
|
||||
/**
|
||||
* Base64编码
|
||||
*/
|
||||
private static Base64.Encoder encoder = Base64.getEncoder();
|
||||
|
||||
/**
|
||||
* Base64解码
|
||||
*/
|
||||
private static Base64.Decoder decoder = Base64.getDecoder();
|
||||
|
||||
// ----------------------- 摘要加密 -----------------------
|
||||
|
||||
/**
|
||||
* md5加密
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String md5(String str) {
|
||||
str = (str == null ? "" : str);
|
||||
char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
|
||||
try {
|
||||
byte[] btInput = str.getBytes();
|
||||
MessageDigest mdInst = MessageDigest.getInstance("MD5");
|
||||
mdInst.update(btInput);
|
||||
byte[] md = mdInst.digest();
|
||||
int j = md.length;
|
||||
char[] strA = new char[j * 2];
|
||||
int k = 0;
|
||||
for (byte byte0 : md) {
|
||||
strA[k++] = hexDigits[byte0 >>> 4 & 0xf];
|
||||
strA[k++] = hexDigits[byte0 & 0xf];
|
||||
}
|
||||
return new String(strA);
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sha1加密
|
||||
*
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String sha1(String str) {
|
||||
try {
|
||||
str = (str == null ? "" : str);
|
||||
MessageDigest md = MessageDigest.getInstance("SHA1");
|
||||
byte[] b = str.getBytes();
|
||||
md.update(b);
|
||||
byte[] b2 = md.digest();
|
||||
int len = b2.length;
|
||||
String strA = "0123456789abcdef";
|
||||
char[] ch = strA.toCharArray();
|
||||
char[] chs = new char[len * 2];
|
||||
for (int i = 0, k = 0; i < len; i++) {
|
||||
byte b3 = b2[i];
|
||||
chs[k++] = ch[b3 >>> 4 & 0xf];
|
||||
chs[k++] = ch[b3 & 0xf];
|
||||
}
|
||||
return new String(chs);
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* sha256加密
|
||||
*
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String sha256(String str) {
|
||||
try {
|
||||
str = (str == null ? "" : str);
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
messageDigest.update(str.getBytes("UTF-8"));
|
||||
|
||||
byte[] bytes = messageDigest.digest();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String temp;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
temp = Integer.toHexString(bytes[i] & 0xFF);
|
||||
if (temp.length() == 1) {
|
||||
builder.append("0");
|
||||
}
|
||||
builder.append(temp);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* md5加盐加密: md5(md5(str) + md5(salt))
|
||||
* @param str 字符串
|
||||
* @param salt 盐
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
public static String md5BySalt(String str, String salt) {
|
||||
return md5(md5(str) + md5(salt));
|
||||
}
|
||||
|
||||
|
||||
// ----------------------- 对称加密 AES -----------------------
|
||||
|
||||
/**
|
||||
* 默认密码算法
|
||||
*/
|
||||
private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
|
||||
|
||||
/**
|
||||
* AES加密
|
||||
*
|
||||
* @param key 加密的密钥
|
||||
* @param text 需要加密的字符串
|
||||
* @return 返回Base64转码后的加密数据
|
||||
*/
|
||||
public static String aesEncrypt(String key, String text) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
|
||||
byte[] byteContent = text.getBytes("utf-8");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
|
||||
byte[] result = cipher.doFinal(byteContent);
|
||||
return encoder.encodeToString(result);
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
* @param key 加密的密钥
|
||||
* @param text 已加密的密文
|
||||
* @return 返回解密后的数据
|
||||
*/
|
||||
public static String aesDecrypt(String key, String text) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
|
||||
cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key));
|
||||
byte[] result = cipher.doFinal(decoder.decode(text));
|
||||
return new String(result, "utf-8");
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成加密秘钥
|
||||
* @param password 秘钥
|
||||
* @return SecretKeySpec
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {
|
||||
KeyGenerator kg = KeyGenerator.getInstance("AES");
|
||||
SecureRandom random = SecureRandom.getInstance("SHA1PRNG");
|
||||
random.setSeed(password.getBytes());
|
||||
kg.init(128, random);
|
||||
SecretKey secretKey = kg.generateKey();
|
||||
return new SecretKeySpec(secretKey.getEncoded(), "AES");
|
||||
}
|
||||
|
||||
|
||||
// ----------------------- 非对称加密 RSA -----------------------
|
||||
|
||||
private static final String ALGORITHM = "RSA";
|
||||
|
||||
private static final int KEY_SIZE = 1024;
|
||||
|
||||
|
||||
// ---------- 5个常用方法
|
||||
|
||||
/**
|
||||
* 生成密钥对
|
||||
* @return Map对象 (private=私钥, public=公钥)
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
public static HashMap<String, String> rsaGenerateKeyPair() throws Exception {
|
||||
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
|
||||
KeyPair keyPair;
|
||||
|
||||
try {
|
||||
keyPairGenerator.initialize(KEY_SIZE,
|
||||
new SecureRandom(UUID.randomUUID().toString().replaceAll("-", "").getBytes()));
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
} catch (InvalidParameterException e) {
|
||||
throw e;
|
||||
} catch (NullPointerException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
RSAPublicKey rsaPublicKey = (RSAPublicKey) keyPair.getPublic();
|
||||
RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) keyPair.getPrivate();
|
||||
|
||||
HashMap<String, String> map = new HashMap<String, String>(16);
|
||||
map.put("private", encoder.encodeToString(rsaPrivateKey.getEncoded()));
|
||||
map.put("public", encoder.encodeToString(rsaPublicKey.getEncoded()));
|
||||
return map;
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA公钥加密
|
||||
* @param publicKeyString 公钥
|
||||
* @param content 内容
|
||||
* @return 加密后内容
|
||||
*/
|
||||
public static String rsaEncryptByPublic(String publicKeyString, String content) {
|
||||
try {
|
||||
// 获得公钥对象
|
||||
PublicKey publicKey = getPublicKeyFromString(publicKeyString);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
||||
// 该密钥能够加密的最大字节长度
|
||||
int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8 - 11;
|
||||
byte[][] arrays = splitBytes(content.getBytes(), splitLength);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA私钥加密
|
||||
* @param privateKeyString 私钥
|
||||
* @param content 内容
|
||||
* @return 加密后内容
|
||||
*/
|
||||
public static String rsaEncryptByPrivate(String privateKeyString, String content) {
|
||||
try {
|
||||
PrivateKey privateKey = getPrivateKeyFromString(privateKeyString);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.ENCRYPT_MODE, privateKey);
|
||||
// 该密钥能够加密的最大字节长度
|
||||
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8 - 11;
|
||||
byte[][] arrays = splitBytes(content.getBytes(), splitLength);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA公钥解密
|
||||
* @param publicKeyString 公钥
|
||||
* @param content 已加密内容
|
||||
* @return 解密后内容
|
||||
*/
|
||||
public static String rsaDecryptByPublic(String publicKeyString, String content) {
|
||||
|
||||
try {
|
||||
PublicKey publicKey = getPublicKeyFromString(publicKeyString);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, publicKey);
|
||||
// 该密钥能够加密的最大字节长度
|
||||
int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8;
|
||||
byte[] contentBytes = hexStringToBytes(content);
|
||||
byte[][] arrays = splitBytes(contentBytes, splitLength);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(new String(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA私钥解密
|
||||
* @param privateKeyString 公钥
|
||||
* @param content 已加密内容
|
||||
* @return 解密后内容
|
||||
*/
|
||||
public static String rsaDecryptByPrivate(String privateKeyString, String content) {
|
||||
try {
|
||||
PrivateKey privateKey = getPrivateKeyFromString(privateKeyString);
|
||||
|
||||
Cipher cipher = Cipher.getInstance("RSA");
|
||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
||||
// 该密钥能够加密的最大字节长度
|
||||
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8;
|
||||
byte[] contentBytes = hexStringToBytes(content);
|
||||
byte[][] arrays = splitBytes(contentBytes, splitLength);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(new String(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- 获取*钥
|
||||
|
||||
/** 根据公钥字符串获取 公钥对象 */
|
||||
private static PublicKey getPublicKeyFromString(String key)
|
||||
throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
// 过滤掉\r\n
|
||||
key = key.replace("\r\n", "");
|
||||
|
||||
// 取得公钥
|
||||
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(decoder.decode(key));
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
|
||||
|
||||
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
|
||||
|
||||
return publicKey;
|
||||
}
|
||||
|
||||
/** 根据私钥字符串获取 私钥对象 */
|
||||
private static PrivateKey getPrivateKeyFromString(String key)
|
||||
throws NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
|
||||
// 过滤掉\r\n
|
||||
key = key.replace("\r\n", "");
|
||||
|
||||
// 取得私钥
|
||||
PKCS8EncodedKeySpec x509KeySpec = new PKCS8EncodedKeySpec(decoder.decode(key));
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
|
||||
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(x509KeySpec);
|
||||
|
||||
return privateKey;
|
||||
}
|
||||
|
||||
|
||||
// ---------- 一些辅助方法
|
||||
|
||||
/** 根据限定的每组字节长度,将字节数组分组 */
|
||||
private static byte[][] splitBytes(byte[] bytes, int splitLength) {
|
||||
|
||||
// bytes与splitLength的余数
|
||||
int remainder = bytes.length % splitLength;
|
||||
// 数据拆分后的组数,余数不为0时加1
|
||||
int quotient = remainder != 0 ? bytes.length / splitLength + 1 : bytes.length / splitLength;
|
||||
byte[][] arrays = new byte[quotient][];
|
||||
byte[] array = null;
|
||||
for (int i = 0; i < quotient; i++) {
|
||||
// 如果是最后一组(quotient-1),同时余数不等于0,就将最后一组设置为remainder的长度
|
||||
if (i == quotient - 1 && remainder != 0) {
|
||||
array = new byte[remainder];
|
||||
System.arraycopy(bytes, i * splitLength, array, 0, remainder);
|
||||
} else {
|
||||
array = new byte[splitLength];
|
||||
System.arraycopy(bytes, i * splitLength, array, 0, splitLength);
|
||||
}
|
||||
arrays[i] = array;
|
||||
}
|
||||
return arrays;
|
||||
}
|
||||
|
||||
/** 将字节数组转换成16进制字符串 */
|
||||
private static String bytesToHexString(byte[] bytes) {
|
||||
|
||||
StringBuffer sb = new StringBuffer(bytes.length);
|
||||
String temp = null;
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
temp = Integer.toHexString(0xFF & bytes[i]);
|
||||
if (temp.length() < 2) {
|
||||
sb.append(0);
|
||||
}
|
||||
sb.append(temp);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/** 将16进制字符串转换成字节数组 */
|
||||
private static byte[] hexStringToBytes(String hex) {
|
||||
|
||||
int len = (hex.length() / 2);
|
||||
hex = hex.toUpperCase();
|
||||
byte[] result = new byte[len];
|
||||
char[] chars = hex.toCharArray();
|
||||
for (int i = 0; i < len; i++) {
|
||||
int pos = i * 2;
|
||||
result[i] = (byte) (toByte(chars[pos]) << 4 | toByte(chars[pos + 1]));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/** 将char转换为byte */
|
||||
private static byte toByte(char c) {
|
||||
|
||||
return (byte) "0123456789ABCDEF".indexOf(c);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -236,6 +236,7 @@ public class SaSession implements Serializable {
|
||||
this.update();
|
||||
}
|
||||
|
||||
|
||||
// ----------------------- 一些操作
|
||||
|
||||
/**
|
||||
@@ -257,4 +258,65 @@ public class SaSession implements Serializable {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ----------------------- 存取值 (类型转换)
|
||||
|
||||
/**
|
||||
* 判断一个值是否为null
|
||||
* @param value 指定值
|
||||
* @return 此value是否为null
|
||||
*/
|
||||
public boolean valueIsNull(Object value) {
|
||||
return value == null || value.equals("");
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Session中取值,并转化为Object类型
|
||||
* @param key key
|
||||
* @return 值
|
||||
*/
|
||||
public Object getObject(String key) {
|
||||
return getAttribute(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Session中取值,并转化为String类型
|
||||
* @param key key
|
||||
* @return 值
|
||||
*/
|
||||
public String getString(String key) {
|
||||
Object value = getObject(key);
|
||||
if(value == null) {
|
||||
return null;
|
||||
}
|
||||
return String.valueOf(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Session中取值,并转化为int类型,如果value为空,则返回0
|
||||
* @param key key
|
||||
* @return 值
|
||||
*/
|
||||
public int getInt(String key) {
|
||||
Object value = getObject(key);
|
||||
if(valueIsNull(value)) {
|
||||
return 0;
|
||||
}
|
||||
return Integer.valueOf(String.valueOf(value));
|
||||
}
|
||||
|
||||
/**
|
||||
* 从Session中取值,并转化为long类型,如果value为空,则返回0
|
||||
* @param key key
|
||||
* @return 值
|
||||
*/
|
||||
public long getLong(String key) {
|
||||
Object value = getObject(key);
|
||||
if(valueIsNull(value)) {
|
||||
return 0;
|
||||
}
|
||||
return Long.valueOf(String.valueOf(value));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package cn.dev33.satoken.stp;
|
||||
|
||||
import cn.dev33.satoken.SaTokenManager;
|
||||
import cn.dev33.satoken.config.SaTokenConfig;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
/**
|
||||
@@ -17,16 +18,16 @@ public class SaLoginModel {
|
||||
*/
|
||||
public String device;
|
||||
|
||||
/**
|
||||
* 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
*/
|
||||
public Boolean isLastingCookie;
|
||||
|
||||
/**
|
||||
* 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
|
||||
*/
|
||||
public Long timeout;
|
||||
|
||||
/**
|
||||
* 是否为临时Cookie(临时Cookie会在浏览器关闭时自动删除)
|
||||
*/
|
||||
public Boolean isTempCookie;
|
||||
|
||||
|
||||
/**
|
||||
* @return device
|
||||
@@ -44,6 +45,22 @@ public class SaLoginModel {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return isLastingCookie
|
||||
*/
|
||||
public Boolean getIsLastingCookie() {
|
||||
return isLastingCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isLastingCookie 要设置的 isLastingCookie
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaLoginModel setIsLastingCookie(Boolean isLastingCookie) {
|
||||
this.isLastingCookie = isLastingCookie;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return timeout
|
||||
*/
|
||||
@@ -60,22 +77,21 @@ public class SaLoginModel {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return isTempCookie
|
||||
*/
|
||||
public Boolean getIsTempCookie() {
|
||||
return isTempCookie;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param isTempCookie 要设置的 isTempCookie
|
||||
* @return 对象自身
|
||||
* @return cookie时长
|
||||
*/
|
||||
public SaLoginModel setIsTempCookie(Boolean isTempCookie) {
|
||||
this.isTempCookie = isTempCookie;
|
||||
return this;
|
||||
public int getCookieTimeout() {
|
||||
if(isLastingCookie == false) {
|
||||
return -1;
|
||||
}
|
||||
if(timeout == SaTokenDao.NEVER_EXPIRE) {
|
||||
return Integer.MAX_VALUE;
|
||||
}
|
||||
return (int)(long)timeout;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构建对象,初始化默认值
|
||||
* @return 对象自身
|
||||
@@ -93,12 +109,12 @@ public class SaLoginModel {
|
||||
if(device == null) {
|
||||
device = SaTokenConsts.DEFAULT_LOGIN_DEVICE;
|
||||
}
|
||||
if(isLastingCookie == null) {
|
||||
isLastingCookie = true;
|
||||
}
|
||||
if(timeout == null) {
|
||||
timeout = config.getTimeout();
|
||||
}
|
||||
if(isTempCookie == null) {
|
||||
isTempCookie = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -112,4 +128,15 @@ public class SaLoginModel {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* toString
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaLoginModel [device=" + device + ", isLastingCookie=" + isLastingCookie + ", timeout=" + timeout + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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,23 @@ public class StpLogic {
|
||||
return SaTokenManager.getSaTokenAction().createToken(loginId, loginKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前会话写入当前tokenValue
|
||||
* @param tokenValue token值
|
||||
*/
|
||||
public void setTokenValue(String tokenValue, int cookieTimeout){
|
||||
// 将token保存到本次request里
|
||||
HttpServletRequest request = SaTokenManager.getSaTokenServlet().getRequest();
|
||||
request.setAttribute(splicingKeyJustCreatedSave(), tokenValue);
|
||||
// 注入Cookie
|
||||
SaTokenConfig config = getConfig();
|
||||
if(config.getIsReadCookie() == true){
|
||||
HttpServletResponse response = SaTokenManager.getSaTokenServlet().getResponse();
|
||||
SaTokenManager.getSaTokenCookie().addCookie(response, getTokenName(), tokenValue,
|
||||
"/", config.getCookieDomain(), cookieTimeout);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前tokenValue
|
||||
* @return 当前tokenValue
|
||||
@@ -115,7 +135,16 @@ public class StpLogic {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 返回
|
||||
// 5. 如果打开了前缀模式
|
||||
String tokenPrefix = getConfig().getTokenPrefix();
|
||||
if(SaTokenInsideUtil.isEmpty(tokenPrefix) == false && SaTokenInsideUtil.isEmpty(tokenValue) == false) {
|
||||
// 如果token以指定的前缀开头, 则裁剪掉它
|
||||
if(tokenValue.startsWith(tokenPrefix + " ")) {
|
||||
tokenValue = tokenValue.substring(tokenPrefix.length() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 6. 返回
|
||||
return tokenValue;
|
||||
}
|
||||
|
||||
@@ -161,10 +190,10 @@ public class StpLogic {
|
||||
/**
|
||||
* 在当前会话上登录id, 并指定登录设备
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param isTempCookie 是否为临时Cookie
|
||||
* @param isLastingCookie 是否为持久Cookie
|
||||
*/
|
||||
public void setLoginId(Object loginId, boolean isTempCookie) {
|
||||
setLoginId(loginId, new SaLoginModel().setIsTempCookie(isTempCookie));
|
||||
public void setLoginId(Object loginId, boolean isLastingCookie) {
|
||||
setLoginId(loginId, new SaLoginModel().setIsLastingCookie(isLastingCookie));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +204,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 +218,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 +228,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());
|
||||
}
|
||||
}
|
||||
@@ -227,17 +255,10 @@ public class StpLogic {
|
||||
// ------ 4. 持久化其它数据
|
||||
// token -> uid
|
||||
dao.set(splicingKeyTokenValue(tokenValue), String.valueOf(loginId), loginModel.getTimeout());
|
||||
// 将token保存到本次request里
|
||||
request.setAttribute(splicingKeyJustCreatedSave(), tokenValue);
|
||||
// 写入 [最后操作时间]
|
||||
setLastActivityToNow(tokenValue);
|
||||
// 注入Cookie
|
||||
if(config.getIsReadCookie() == true){
|
||||
HttpServletResponse response = SaTokenManager.getSaTokenServlet().getResponse();
|
||||
int cookieTimeout = loginModel.getIsTempCookie() ? -1 : (int)(long)loginModel.getTimeout();
|
||||
SaTokenManager.getSaTokenCookie().addCookie(response, getTokenName(), tokenValue,
|
||||
"/", config.getCookieDomain(), cookieTimeout);
|
||||
}
|
||||
// 在当前会话写入当前tokenValue
|
||||
setTokenValue(tokenValue, loginModel.getCookieTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -578,15 +599,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){
|
||||
SaTokenManager.getSaTokenCookie().addCookie(SaTokenManager.getSaTokenServlet().getResponse(), getTokenName(), tokenValue,
|
||||
"/", getConfig().getCookieDomain(), (int)getConfig().getTimeout());
|
||||
}
|
||||
// 在当前会话写入这个tokenValue
|
||||
int cookieTimeout = (int)(getConfig().getTimeout() == SaTokenDao.NEVER_EXPIRE ? Integer.MAX_VALUE : getConfig().getTimeout());
|
||||
setTokenValue(tokenValue, cookieTimeout);
|
||||
}
|
||||
}
|
||||
// 返回这个token对应的专属session
|
||||
@@ -1092,7 +1109,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 +1117,7 @@ public class StpLogic {
|
||||
* @return key
|
||||
*/
|
||||
public String splicingKeyJustCreatedSave() {
|
||||
return SaTokenConsts.JUST_CREATED_SAVE_KEY + getLoginKey();
|
||||
return SaTokenConsts.JUST_CREATED_SAVE_KEY + loginKey;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -36,6 +36,14 @@ public class StpUtil {
|
||||
return stpLogic.getTokenName();
|
||||
}
|
||||
|
||||
/**
|
||||
* 在当前会话写入当前tokenValue
|
||||
* @param tokenValue token值
|
||||
*/
|
||||
public static void setTokenValue(String tokenValue, int cookieTimeout){
|
||||
stpLogic.setTokenValue(tokenValue, cookieTimeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前tokenValue
|
||||
* @return 当前tokenValue
|
||||
@@ -75,10 +83,10 @@ public class StpUtil {
|
||||
/**
|
||||
* 在当前会话上登录id, 并指定登录设备
|
||||
* @param loginId 登录id,建议的类型:(long | int | String)
|
||||
* @param isTempCookie 是否为临时Cookie
|
||||
* @param isLastingCookie 是否为持久Cookie
|
||||
*/
|
||||
public void setLoginId(Object loginId, boolean isTempCookie) {
|
||||
stpLogic.setLoginId(loginId, isTempCookie);
|
||||
public static void setLoginId(Object loginId, boolean isLastingCookie) {
|
||||
stpLogic.setLoginId(loginId, isLastingCookie);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,7 +13,7 @@ public class SaTokenConsts {
|
||||
/**
|
||||
* sa-token 版本号
|
||||
*/
|
||||
public static final String VERSION_NO = "v1.13.0";
|
||||
public static final String VERSION_NO = "v1.14.0";
|
||||
|
||||
/**
|
||||
* sa-token 开源地址
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<properties>
|
||||
<sa-token-version>1.13.0</sa-token-version>
|
||||
<sa-token-version>1.14.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<properties>
|
||||
<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>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -30,7 +32,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- OkHttps网络请求库: http://okhttps.ejlchina.com/ -->
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
<properties>
|
||||
<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>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
@@ -30,21 +32,21 @@
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token 实现 oauth2.0 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-oauth2</artifactId>
|
||||
<version>1.13.0-alpha</version>
|
||||
<version>1.14.0-alpha</version>
|
||||
</dependency>
|
||||
|
||||
<!-- sa-token整合redis (使用jackson序列化方式) -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>${sa-token-version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
<!-- 定义sa-token版本号 -->
|
||||
<properties>
|
||||
<sa-token-version>1.13.0</sa-token-version>
|
||||
<sa-token-version>1.14.0</sa-token-version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,6 +9,8 @@ import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
|
||||
|
||||
/**
|
||||
* sa-token代码方式进行配置
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class MySaTokenConfig implements WebMvcConfigurer {
|
||||
@@ -34,5 +36,4 @@ public class MySaTokenConfig implements WebMvcConfigurer {
|
||||
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ import com.pj.util.AjaxJson;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
@@ -25,7 +24,7 @@ public class GlobalException {
|
||||
// 在当前类每个方法进入之前触发的操作
|
||||
@ModelAttribute
|
||||
public void get(HttpServletRequest request) throws IOException {
|
||||
StpUtil.checkPermission("user:add");
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -238,17 +238,17 @@ public class TestController {
|
||||
return AjaxJson.getSuccessData("登录成功");
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test
|
||||
@RequestMapping("test")
|
||||
public AjaxJson test(HttpServletResponse response) {
|
||||
// StpUtil.getTokenSession().logout();
|
||||
// StpUtil.logoutByLoginId(10001);
|
||||
// StpUtil.setLoginId(10001);
|
||||
// StpUtil.setLoginId(10001, new SaLoginModel().setIsTempCookie(true));
|
||||
// StpUtil.getLoginId();
|
||||
|
||||
System.out.println("进来了");
|
||||
// StpUtil.setLoginId(10001, new SaLoginModel()
|
||||
// .setDevice("PC") // 此次登录的客户端设备标识, 用于[同端互斥登录]时指定此次登录的设备名称
|
||||
// .setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
// .setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
|
||||
// );
|
||||
StpUtil.getTokenSession();
|
||||
return AjaxJson.getSuccess("访问成功");
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ spring:
|
||||
token-style: uuid
|
||||
|
||||
|
||||
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
|
||||
@@ -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.13.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">sa-token v1.14.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.13.0-2B9939"></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/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>
|
||||
@@ -55,9 +55,11 @@ sa-token是一个轻量级Java权限认证框架,主要解决:登录认证
|
||||
- **多账号认证体系** —— 比如一个商城项目的user表和admin表分开鉴权
|
||||
- **花式token生成** —— 内置六种token风格,还可自定义token生成策略
|
||||
- **注解式鉴权** —— 优雅的将鉴权与业务代码分离
|
||||
- **路由拦截式鉴权** —— 设定全局路由拦截,并排除指定路由
|
||||
- **路由拦截式鉴权** —— 根据路由拦截鉴权,可适配restful模式
|
||||
- **自动续签** —— 提供两种token过期策略,灵活搭配使用,还可自动续签
|
||||
- **会话治理** —— 提供方便灵活的会话查询接口
|
||||
- **记住我模式** —— 适配[记住我]模式,重启浏览器免验证
|
||||
- **密码加密** —— 提供密码加密模块,可快速MD5、SHA1、SHA256、AES、RSA加密
|
||||
- **组件自动注入** —— 零配置与Spring等框架集成
|
||||
- **更多功能正在集成中...** —— 如有您有好想法或者建议,欢迎加群交流
|
||||
|
||||
@@ -87,7 +89,8 @@ StpUtil.checkLogin();
|
||||
@SaCheckPermission("user:add")
|
||||
@RequestMapping("/user/insert")
|
||||
public String insert(SysUser user) {
|
||||
return "用户增加";
|
||||
// ...
|
||||
return "用户增加";
|
||||
}
|
||||
```
|
||||
|
||||
@@ -181,15 +184,17 @@ 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)
|
||||
|
||||

|
||||

|
||||
|
||||
**微信群** :
|
||||
**微信群**
|
||||
|
||||

|
||||

|
||||
|
||||
(扫码添加微信,备注:sa-token,邀您加入群聊)
|
||||
|
||||
<br>
|
||||
|
||||
@@ -19,7 +19,8 @@
|
||||
- [花式token](/use/token-style)
|
||||
- [框架配置](/use/config)
|
||||
- [会话治理](/use/search-session)
|
||||
<!-- - [记住我模式](/use/remember-me) -->
|
||||
- [记住我模式](/use/remember-me)
|
||||
- [密码加密](/use/password-secure)
|
||||
|
||||
- **进阶**
|
||||
- [集群、分布式](/senior/dcs)
|
||||
@@ -34,7 +35,9 @@
|
||||
- **附录**
|
||||
- [未登录场景值](/fun/not-login-scene)
|
||||
- [token有效期详解](/fun/token-timeout)
|
||||
- [Session模型详解](/fun/session-model)
|
||||
- [TokenInfo参数详解](/fun/token-info)
|
||||
- [框架源码所有技术栈](/fun/tech-stack)
|
||||
|
||||
|
||||
|
||||
|
||||
21
sa-token-doc/doc/fun/session-model.md
Normal file
21
sa-token-doc/doc/fun/session-model.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Session模型详解
|
||||
|
||||
---
|
||||
|
||||
在`sa-token`中, `Session` 分为三种, 分别是:
|
||||
- `User-Session`: 指的是框架为每个`loginId`分配的`Session`
|
||||
- `Token-Session`: 指的是框架为每个`token`分配的`Session`
|
||||
- `自定义Session`: 指的是以一个`特定的值`作为SessionId,来分配的`Session`
|
||||
|
||||
|
||||
User-Session和Token-Session到底有什么不同?下面这张图可以解释两者的区别
|
||||
|
||||

|
||||
|
||||
简而言之:
|
||||
- `Token-Session` 以token为主,只要token不同,那么对应的Session对象就不同
|
||||
- `User-Session` 以UserId为主,只要token指向的UserId一致,那么对应的Session对象就一致
|
||||
- `自定义Session` 以特定的key为主,不同key对应不同的Session对象
|
||||
|
||||
|
||||
|
||||
15
sa-token-doc/doc/fun/tech-stack.md
Normal file
15
sa-token-doc/doc/fun/tech-stack.md
Normal 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匹配
|
||||
|
||||
|
||||
|
||||
@@ -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.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>
|
||||
<option value="http://sa-token.dev33.cn/v/v1.11.0/doc/index.html">v1.11.0</option>
|
||||
@@ -42,7 +43,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.13.0</sub>'
|
||||
name += '<b style="font-size: 24px; vertical-align: middle;">sa-token</b> <sub>v1.14.0</sub>'
|
||||
window.$docsify = {
|
||||
name: name, // 名字
|
||||
repo: 'https://github.com/click33/sa-token', // github地址
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
[[sa-admin] - 一个多窗口后台模板,流畅、易上手、提高生产力](https://gitee.com/ejlchina-zhxu/okhttps)
|
||||
|
||||
[[vue-next-admin] - 一套为开发者快速开发准备的基于 vue2.x 越看越精彩的后台管理系统一站式平台模板](https://gitee.com/lyt-top/vue-admin-wonderful)
|
||||
|
||||
[[小诺快速开发平台] - 基于SpringBoot2 + AntDesignVue全新快速开发平台,同时拥有三个版本](https://xiaonuo.vip/index#pricing)
|
||||
|
||||
<br>
|
||||
虚位以待...
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
# 更新日志
|
||||
|
||||
### 2021-3-12 @v1.14.0
|
||||
- 新增:新增`SaLoginModel`登录参数Model,适配 [记住我] 模式 **[重要]**
|
||||
- 新增:新增 `StpUtil.setLoginId()` 时指定token有效期,可灵活控制用户的一次登录免验证时长
|
||||
- 新增:新增Cookie时间判断,在`timeout`设置为-1时,`Cookie`有效期将为`Integer.MAX_VALUE` **[重要]**
|
||||
- 新增:新增密码加密工具类,可快速MD5、SHA1、SHA256、AES、RSA加密 **[重要]**
|
||||
- 新增:新增 OAuth2.0 模块 **[重要]**
|
||||
- 新增:`SaTokenConfig`配置类所有set方法支持链式调用
|
||||
- 新增:`SaOAuth2Config` sa-token oauth2 配置类所有set方法新增支持链式调用
|
||||
- 优化:`StpLogic`类所有`getKey`方法重名为`splicingKey`,更语义化的函数名称
|
||||
- 新增:`IsRunFunction`新增`noExe`函数,用于指定当`isRun`值为`false`时执行的函数
|
||||
- 新增:`SaSession`新增数据存取值操作API
|
||||
- 优化:优化`SaTokenDao`接口,增加Object操作API
|
||||
- 优化:jwt示例`createToken`方法去除默认秘钥判断,只在启动项目时打印警告
|
||||
- 文档:常见问题新增示例(修改密码后如何立即掉线)
|
||||
- 文档:权限认证文档新增[如何把权限精确搭到按钮级]示例说明
|
||||
- 文档:优化文档,部分模块添加图片说明
|
||||
|
||||
|
||||
### 2021-2-9 @v1.13.0
|
||||
- 优化:优化源码注释与文档
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Gradle依赖
|
||||
Gradle用户引入依赖:
|
||||
```
|
||||
implementation 'cn.dev33:sa-token-spring-boot-starter:1.13.0'
|
||||
implementation 'cn.dev33:sa-token-spring-boot-starter:1.14.0'
|
||||
```
|
||||
|
||||
|
||||
@@ -34,8 +34,12 @@ implementation 'cn.dev33:sa-token-spring-boot-starter:1.13.0'
|
||||
├── sa-token-dao-redis // sa-token整合redis (使用jdk默认序列化方式)
|
||||
├── sa-token-dao-redis-jackson // sa-token整合redis (使用jackson序列化方式)
|
||||
├── sa-token-spring-aop // sa-token整合SpringAOP 注解鉴权
|
||||
├── sa-token-oauth2 // sa-token实现 OAuth2.0 模块(内测暂未发布)
|
||||
├── sa-token-demo-springboot // sa-token示例
|
||||
├── sa-token-demo-jwt // sa-token集成jwt示例
|
||||
├── sa-token-demo-oauth2 // sa-token集成OAuth2.0模块示例
|
||||
├── sa-token-demo-oauth2-client // OAuth2.0 客户端
|
||||
├── sa-token-demo-oauth2-server // OAuth2.0 服务端
|
||||
├── sa-token-doc // sa-token开发文档
|
||||
├──pom.xml
|
||||
```
|
||||
@@ -46,7 +50,7 @@ implementation 'cn.dev33:sa-token-spring-boot-starter:1.13.0'
|
||||
## jar包下载
|
||||
[点击下载:sa-token-1.6.0.jar](https://oss.dev33.cn/sa-token/sa-token-1.6.0.jar)
|
||||
|
||||
(注意:当前仅提供`v1.6.0`版本jar包下载,更多版本请前往maven中央仓库获取)
|
||||
(注意:当前仅提供`v1.6.0`版本jar包下载,更多版本请前往maven中央仓库获取,[直达链接](https://search.maven.org/search?q=sa-token))
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
BIN
sa-token-doc/doc/static/kickout.png
vendored
Normal file
BIN
sa-token-doc/doc/static/kickout.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
sa-token-doc/doc/static/not-jur.png
vendored
Normal file
BIN
sa-token-doc/doc/static/not-jur.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
BIN
sa-token-doc/doc/static/not-login.png
vendored
Normal file
BIN
sa-token-doc/doc/static/not-login.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
sa-token-doc/doc/static/session-model.png
vendored
Normal file
BIN
sa-token-doc/doc/static/session-model.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
@@ -21,7 +21,7 @@ sa-token内置两种模式完成注解鉴权,分别是`AOP模式`和`拦截器
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-aop</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
|
||||
@@ -9,13 +9,12 @@ Sa-token默认将会话数据保存在内存中,此模式读写速度最快,
|
||||
|
||||
|
||||
### 1. sa-token 整合 Redis (使用jdk默认序列化方式)
|
||||
(注意: 整合Redis只需要引入对应的pom依赖即可,所有上层API保持不变)
|
||||
``` xml
|
||||
<!-- sa-token整合redis (使用jdk默认序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</dependency>
|
||||
```
|
||||
优点:兼容性好,缺点:Session序列化后基本不可读,对开发者来讲等同于乱码
|
||||
@@ -27,7 +26,7 @@ Sa-token默认将会话数据保存在内存中,此模式读写速度最快,
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</dependency>
|
||||
```
|
||||
优点:Session序列化后可读性强,可灵活手动修改,缺点:兼容性稍差
|
||||
@@ -35,7 +34,10 @@ Sa-token默认将会话数据保存在内存中,此模式读写速度最快,
|
||||
|
||||
<br>
|
||||
|
||||
**请注意,无论使用哪种序列化方式,你都必须为项目提供一个Redis实例化方案,例如:**
|
||||
### 集成Redis请注意
|
||||
|
||||
|
||||
**1. 无论使用哪种序列化方式,你都必须为项目提供一个Redis实例化方案,例如:**
|
||||
``` xml
|
||||
<!-- 提供redis连接池 -->
|
||||
<dependency>
|
||||
@@ -44,6 +46,38 @@ Sa-token默认将会话数据保存在内存中,此模式读写速度最快,
|
||||
</dependency>
|
||||
```
|
||||
|
||||
**2. 引入了依赖,我还需要为Redis配置连接信息吗?** <br>
|
||||
需要!只有项目初始化了正确的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>
|
||||
框架自动保存。集成`Redis`只需要引入对应的`pom依赖`即可,框架所有上层API保持不变
|
||||
|
||||
|
||||
<br><br>
|
||||
|
||||
@@ -8,7 +8,10 @@
|
||||
有,就让你通过。没有?那么禁止访问!
|
||||
|
||||
再往底了说,就是每个账号都会拥有一个权限码集合,我来验证这个集合中是否包含指定的权限码 <br/>
|
||||
例如:当前账号拥有权限码集合:`["user:add", "user:delete", "user:get"]`,这时候我来验证权 `"user:update"`,则结果就是:**验证失败,禁止访问**
|
||||
例如:当前账号拥有权限码集合:`["user:add", "user:delete", "user:get"]`,这时候我来验证权限 `"user:update"`,则其结果就是:**验证失败,禁止访问**
|
||||
(注意: 冒号无特殊含义,可有可无)
|
||||
|
||||

|
||||
|
||||
所以现在问题的核心就是:
|
||||
1. 如何获取一个账号所拥有的的权限码集合
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# 踢人下线
|
||||
所谓踢人下线,核心操作就是找到其指定`loginId`对应的`token`,并设置其失效
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
所谓登录验证,说白了就是限制某些接口只有登录后才能访问(如:查询我的账号资料) <br>
|
||||
那么判断一个会话是否登录的依据是什么?当然是登录成功后框架给你做个标记!然后在需要鉴权的接口里检查此标记,有标记者视为已登录,无标记者视为未登录!
|
||||
|
||||

|
||||
|
||||
|
||||
### 登录与注销
|
||||
|
||||
@@ -43,12 +43,12 @@
|
||||
|
||||
``` java
|
||||
// 底层的 StpLogic 对象
|
||||
public static StpLogic stpLogic = new StpLogic("login") {
|
||||
// 重写 `getTokenName` 函数,返回一个与 `StpUtil` 不同的token名称, 防止冲突
|
||||
public static StpLogic stpLogic = new StpLogic("user") {
|
||||
// 重写 `splicingKeyTokenName` 函数,返回一个与 `StpUtil` 不同的token名称, 防止冲突
|
||||
@Override
|
||||
public String getTokenName() {
|
||||
return super.getKeyTokenName() + "-user";
|
||||
}
|
||||
public String splicingKeyTokenName() {
|
||||
return super.splicingKeyTokenName()+"-user";
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
83
sa-token-doc/doc/use/password-secure.md
Normal file
83
sa-token-doc/doc/use/password-secure.md
Normal file
@@ -0,0 +1,83 @@
|
||||
# 密码加密
|
||||
|
||||
严格来讲,密码加密不属于 [权限认证] 的范畴,但是对于大多数系统来讲,密码加密又是安全认证不可或缺的部分,
|
||||
所以,应大家要求,`sa-token`在`v1.14版本`添加密码加密模块,该模块非常简单,仅仅封装了一些常见的加密算法
|
||||
|
||||
|
||||
|
||||
### 摘要加密
|
||||
md5、sha1、sha256
|
||||
``` java
|
||||
// md5加密
|
||||
SaSecureUtil.md5("123456");
|
||||
|
||||
// sha1加密
|
||||
SaSecureUtil.sha1("123456");
|
||||
|
||||
// sha256加密
|
||||
SaSecureUtil.sha256("123456");
|
||||
|
||||
// md5加盐加密: md5(md5(str) + md5(salt))
|
||||
SaSecureUtil.md5BySalt("123456", "salt");
|
||||
```
|
||||
|
||||
|
||||
### 对称加密
|
||||
AES加密
|
||||
``` java
|
||||
// 定义秘钥和明文
|
||||
String key = "123456";
|
||||
String text = "sa-token 一个轻量级java权限认证框架";
|
||||
|
||||
// 加密
|
||||
String ciphertext = SaSecureUtil.aesEncrypt(key, text);
|
||||
System.out.println("AES加密后:" + ciphertext);
|
||||
|
||||
// 解密
|
||||
String text2 = SaSecureUtil.aesDecrypt(key, ciphertext);
|
||||
System.out.println("AES解密后:" + text2);
|
||||
```
|
||||
|
||||
|
||||
### 非对称加密
|
||||
RSA加密
|
||||
``` java
|
||||
// 定义私钥和公钥
|
||||
String privateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAO+wmt01pwm9lHMdq7A8gkEigk0XKMfjv+4IjAFhWCSiTeP7dtlnceFJbkWxvbc7Qo3fCOpwmfcskwUc3VSgyiJkNJDs9ivPbvlt8IU2bZ+PBDxYxSCJFrgouVOpAr8ar/b6gNuYTi1vt3FkGtSjACFb002/68RKUTye8/tdcVilAgMBAAECgYA1COmrSqTUJeuD8Su9ChZ0HROhxR8T45PjMmbwIz7ilDsR1+E7R4VOKPZKW4Kz2VvnklMhtJqMs4MwXWunvxAaUFzQTTg2Fu/WU8Y9ha14OaWZABfChMZlpkmpJW9arKmI22ZuxCEsFGxghTiJQ3tK8npj5IZq5vk+6mFHQ6aJAQJBAPghz91Dpuj+0bOUfOUmzi22obWCBncAD/0CqCLnJlpfOoa9bOcXSusGuSPuKy5KiGyblHMgKI6bq7gcM2DWrGUCQQD3SkOcmia2s/6i7DUEzMKaB0bkkX4Ela/xrfV+A3GzTPv9bIBamu0VIHznuiZbeNeyw7sVo4/GTItq/zn2QJdBAkEA8xHsVoyXTVeShaDIWJKTFyT5dJ1TR++/udKIcuiNIap34tZdgGPI+EM1yoTduBM7YWlnGwA9urW0mj7F9e9WIQJAFjxqSfmeg40512KP/ed/lCQVXtYqU7U2BfBTg8pBfhLtEcOg4wTNTroGITwe2NjL5HovJ2n2sqkNXEio6Ji0QQJAFLW1Kt80qypMqot+mHhS+0KfdOpaKeMWMSR4Ij5VfE63WzETEeWAMQESxzhavN1WOTb3/p6icgcVbgPQBaWhGg==";
|
||||
String publicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDvsJrdNacJvZRzHauwPIJBIoJNFyjH47/uCIwBYVgkok3j+3bZZ3HhSW5Fsb23O0KN3wjqcJn3LJMFHN1UoMoiZDSQ7PYrz275bfCFNm2fjwQ8WMUgiRa4KLlTqQK/Gq/2+oDbmE4tb7dxZBrUowAhW9NNv+vESlE8nvP7XXFYpQIDAQAB";
|
||||
|
||||
// 文本
|
||||
String text = "sa-token 一个轻量级java权限认证框架";
|
||||
|
||||
// 使用公钥加密
|
||||
String ciphertext = SaSecureUtil.rsaEncryptByPublic(publicKey, text);
|
||||
System.out.println("公钥加密后:" + ciphertext);
|
||||
|
||||
// 使用私钥解密
|
||||
String text2 = SaSecureUtil.rsaDecryptByPrivate(privateKey, ciphertext);
|
||||
System.out.println("私钥解密后:" + text2);
|
||||
```
|
||||
|
||||
你可能会有疑问,私钥和公钥这么长的一大串,我怎么弄出来,手写吗?当然不是,调用以下方法生成即可
|
||||
``` java
|
||||
// 生成一对公钥和私钥,其中Map对象 (private=私钥, public=公钥)
|
||||
System.out.println(SaSecureUtil.rsaGenerateKeyPair());
|
||||
```
|
||||
|
||||
|
||||
### Base64编码与解码
|
||||
``` java
|
||||
// 文本
|
||||
String text = "sa-token 一个轻量级java权限认证框架";
|
||||
|
||||
// 使用Base64编码
|
||||
String base64Text = SaBase64Util.encode(text);
|
||||
System.out.println("Base64编码后:" + base64Text);
|
||||
|
||||
// 使用Base64解码
|
||||
String text2 = SaBase64Util.decode(base64Text);
|
||||
System.out.println("Base64解码后:" + text2);
|
||||
```
|
||||
|
||||
<br>
|
||||
如需更多加密算法请提交pr
|
||||
@@ -10,9 +10,71 @@
|
||||
|
||||
### 在sa-token中实现记住我功能
|
||||
|
||||
sa-token的登录授权,默认就是`记住我`模式,为了实现`非记住我`模式, 你需要做一些适配
|
||||
sa-token的登录授权,**默认就是`[记住我]`模式**,为了实现`[非记住我]`模式, 你需要在登录时如下设置:
|
||||
|
||||
``` java
|
||||
// 设置登录账号id为10001,第二个参数指定是否为[记住我],当此值为false后,关闭浏览器后再次打开需要重新登录
|
||||
StpUtil.setLoginId(10001, false);
|
||||
```
|
||||
|
||||
那么,sa-token实现`[记住我]`的具体原理是?
|
||||
|
||||
|
||||
### 实现原理
|
||||
Cookie作为浏览器提供的默认会话跟踪机制,其生命周期有两种形式,分别是:
|
||||
- 临时Cookie:有效期为本次会话,只要关闭浏览器窗口,Cookie就会消失
|
||||
- 永久Cookie:有效期为一个具体的时间,在时间未到期之前,即使用户关闭了浏览器Cookie也不会消失
|
||||
|
||||
利用Cookie的此特性,我们便可以轻松实现 [记住我] 模式:
|
||||
- 勾选[记住我]按钮时:调用`StpUtil.setLoginId(10001, true)`,在浏览器写入一个`永久Cookie`储存token,此时用户即使重启浏览器token依然有效
|
||||
- 不勾选[记住我]按钮时:调用`StpUtil.setLoginId(10001, false)`,在浏览器写入一个`临时Cookie`储存token,此时用户在重启浏览器后token便会消失,导致会话失效
|
||||
|
||||
|
||||
### 前后台分离模式下如何实现[记住我]?
|
||||
|
||||
此时机智的你😏很快发现一个问题,Cookie虽好,却无法在前后端分离环境下使用,那是不是代表上述方案在APP、小程序等环境中无效?
|
||||
|
||||
准确的讲,答案是肯定的,任何基于Cookie的认证方案在前后台分离环境下都会失效(原因在于这些客户端默认没有实现Cookie功能),不过好在,这些客户端一般都提供了替代方案,
|
||||
唯一遗憾的是,此场景中token的生命周期需要我们在前端手动控制
|
||||
|
||||
以经典跨端框架 [uni-app](https://uniapp.dcloud.io/) 为例,我们可以使用如下方式达到同样的效果:
|
||||
``` js
|
||||
// 使用本地存储保存token,达到 [永久Cookie] 的效果
|
||||
uni.setStorageSync("satoken", "xxxx-xxxx-xxxx-xxxx-xxx");
|
||||
|
||||
// 使用globalData保存token,达到 [临时Cookie] 的效果
|
||||
getApp().globalData.satoken = "xxxx-xxxx-xxxx-xxxx-xxx";
|
||||
```
|
||||
|
||||
如果你决定在PC浏览器环境下进行前后台分离模式开发,那么更加简单:
|
||||
``` js
|
||||
// 使用 localStorage 保存token,达到 [永久Cookie] 的效果
|
||||
localStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx");
|
||||
|
||||
// 使用 sessionStorage 保存token,达到 [临时Cookie] 的效果
|
||||
sessionStorage.setItem("satoken", "xxxx-xxxx-xxxx-xxxx-xxx");
|
||||
```
|
||||
|
||||
Remember me, it's too easy!
|
||||
|
||||
|
||||
|
||||
### 登录时指定token有效期
|
||||
登录时不仅可以指定是否为`[记住我]`模式,还可以指定一个特定的时间作为token有效时长,如下示例:
|
||||
``` java
|
||||
// 示例1:
|
||||
// 指定token有效期(单位: 秒),如下所示token七天有效
|
||||
StpUtil.setLoginId(10001, new SaLoginModel().setTimeout(60 * 60 * 24 * 7));
|
||||
|
||||
// ----------------------- 示例2:所有参数
|
||||
// `SaLoginModel`为登录参数Model,其有诸多参数决定登录时的各种逻辑,例如:
|
||||
StpUtil.setLoginId(10001, new SaLoginModel()
|
||||
.setDevice("PC") // 此次登录的客户端设备标识, 用于[同端互斥登录]时指定此次登录的设备名称
|
||||
.setIsLastingCookie(true) // 是否为持久Cookie(临时Cookie在浏览器关闭时会自动删除,持久Cookie在重新打开后依然存在)
|
||||
.setTimeout(60 * 60 * 24 * 7) // 指定此次登录token的有效期, 单位:秒 (如未指定,自动取全局配置的timeout值)
|
||||
);
|
||||
```
|
||||
|
||||
要
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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,9 +48,8 @@ public class MySaTokenConfig implements WebMvcConfigurer {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 4、完整的示例
|
||||
最终的代码,可能会类似于下面的样子:
|
||||
## 3、完整示例
|
||||
所有用法示例:
|
||||
|
||||
``` java
|
||||
@Configuration
|
||||
@@ -88,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"));
|
||||
|
||||
@@ -108,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("/**");
|
||||
}
|
||||
}
|
||||
@@ -120,3 +102,5 @@ public class MySaTokenConfig implements WebMvcConfigurer {
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ Session是会话中专业的数据缓存组件,通过`Session`我们可以很
|
||||
- `Token-Session`: 指的是框架为每个`token`分配的`Session`
|
||||
- `自定义Session`: 指的是以一个`特定的值`作为SessionId,来分配的`Session`
|
||||
|
||||
> 有关User-Session与Token-Session的详细区别,请参考:[Session模型详解](/fun/session-model)
|
||||
|
||||
|
||||
### User-Session
|
||||
有关账号Session的API如下:
|
||||
@@ -79,3 +81,16 @@ session.logout(); // 注销此Session会话 (从持久
|
||||
具体可参考`javax.servlet.http.HttpSession`,`SaSession`所含方法与其大体类似
|
||||
|
||||
|
||||
### Session环境隔离说明
|
||||
在springboot环境下取得的session环境和通过StpUtil获取的session环境并不相通, 示例
|
||||
``` java
|
||||
@PostMapping("/resetPoints")
|
||||
public void reset(HttpSession session) {
|
||||
session.setAttribute("pointsKey", 66);
|
||||
SaSession session2 = StpUtil.getSession();
|
||||
Object value = session2.getAttribute("pointsKey");
|
||||
System.out.println(value)
|
||||
// 输出null
|
||||
}
|
||||
```
|
||||
而且, 在使用sa-token多账号模式下, 不同的StpUtil获取的session之间也是环境隔离
|
||||
@@ -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.13.0</small></h1>
|
||||
<h1>sa-token<small>v1.14.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">
|
||||
|
||||
@@ -9,3 +9,4 @@ sa-token-oauth2 模块是 sa-token 实现 oauth2.0 的部分,目前该模块
|
||||
3. 根据控制台打印,访问测试地址即可:[http://localhost:8002/login.html](http://localhost:8002/login.html)
|
||||
|
||||
可结合代码注释学习查看
|
||||
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>sa-token-dao-redis</name>
|
||||
<artifactId>sa-token-oauth2</artifactId>
|
||||
<version>1.13.0-alpha</version>
|
||||
<version>1.14.0-alpha</version>
|
||||
<description>sa-token realization oauth2.0</description>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -523,7 +523,7 @@ public interface SaOAuth2Interface {
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取key:[Client&账号]最新授权码记录, 持久化使用的key
|
||||
* 获取key:[Client and 账号]最新授权码记录, 持久化使用的key
|
||||
* @param loginId 账号id
|
||||
* @param clientId 应用id
|
||||
* @return key
|
||||
|
||||
@@ -51,6 +51,7 @@ public class SaOAuth2Util {
|
||||
* @param clientId 应用id
|
||||
* @param scope 权限
|
||||
* @param loginId 账号id
|
||||
* @return 是否已经授权
|
||||
*/
|
||||
public static boolean isGrant(Object loginId, String clientId, String scope) {
|
||||
return SaOAuth2Manager.getInterface().isGrant(loginId, clientId, scope);
|
||||
|
||||
@@ -50,6 +50,7 @@ public class RequestAuthModel {
|
||||
|
||||
/**
|
||||
* @param clientId 要设置的 clientId
|
||||
* @return 对象自身
|
||||
*/
|
||||
public RequestAuthModel setClientId(String clientId) {
|
||||
this.clientId = clientId;
|
||||
@@ -65,6 +66,7 @@ public class RequestAuthModel {
|
||||
|
||||
/**
|
||||
* @param scope 要设置的 scope
|
||||
* @return 对象自身
|
||||
*/
|
||||
public RequestAuthModel setScope(String scope) {
|
||||
this.scope = scope;
|
||||
@@ -80,6 +82,7 @@ public class RequestAuthModel {
|
||||
|
||||
/**
|
||||
* @param loginId 要设置的 loginId
|
||||
* @return 对象自身
|
||||
*/
|
||||
public RequestAuthModel setLoginId(Object loginId) {
|
||||
this.loginId = loginId;
|
||||
@@ -95,6 +98,7 @@ public class RequestAuthModel {
|
||||
|
||||
/**
|
||||
* @param redirectUri 要设置的 redirectUri
|
||||
* @return 对象自身
|
||||
*/
|
||||
public RequestAuthModel setRedirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
@@ -110,6 +114,7 @@ public class RequestAuthModel {
|
||||
|
||||
/**
|
||||
* @param responseType 要设置的 responseType
|
||||
* @return 对象自身
|
||||
*/
|
||||
public RequestAuthModel setResponseType(String responseType) {
|
||||
this.responseType = responseType;
|
||||
@@ -125,6 +130,7 @@ public class RequestAuthModel {
|
||||
|
||||
/**
|
||||
* @param state 要设置的 state
|
||||
* @return 对象自身
|
||||
*/
|
||||
public RequestAuthModel setState(String state) {
|
||||
this.state = state;
|
||||
@@ -133,6 +139,7 @@ public class RequestAuthModel {
|
||||
|
||||
/**
|
||||
* 检查此Model参数是否有效
|
||||
* @return 对象自身
|
||||
*/
|
||||
public RequestAuthModel checkModel() {
|
||||
if(SaTokenInsideUtil.isEmpty(clientId)) {
|
||||
|
||||
@@ -26,7 +26,7 @@ public class ScopeModel {
|
||||
}
|
||||
/**
|
||||
* 构造一个
|
||||
* @param id 权限id
|
||||
* @param name 权限名称
|
||||
* @param introduce 权限详细介绍
|
||||
*/
|
||||
public ScopeModel(String name, String introduce) {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<parent>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-parent</artifactId>
|
||||
<version>1.13.0</version>
|
||||
<version>1.14.0</version>
|
||||
</parent>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user