Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1e87426481 | |||
| 69cecb5497 | |||
| fc903cf296 | |||
| a917da0b72 | |||
| 9e2d1fb19b | |||
| 85f0f9f55a | |||
| 24b6168ce4 | |||
| c6c1ad1ac5 | |||
| 9469251376 | |||
| 0eb9e5bcdc | |||
| 0a25a62abb | |||
| 081b6a1b70 | |||
| 6d0a2455f1 | |||
| 0cf045e171 | |||
| a1111c5e50 | |||
| f2c883621e | |||
| 46a98b8ad5 | |||
| 1eac25ea46 | |||
| 80153984e5 | |||
| b7a004d7ce | |||
| f5dbe6c6ba | |||
| 374cf70785 | |||
| 40d1056713 | |||
| 6de36aec40 | |||
| 2cccf35223 | |||
| 1feca73e2a | |||
| 9ce29219d6 | |||
| 0cc497f21f | |||
| 44ecbe7845 | |||
| 4bb07de8ff | |||
| c744bd7744 | |||
| d09366602a | |||
| 418172362a | |||
| 81c1ddacde | |||
| 28b3c0567a | |||
| cbce0aefd9 | |||
| 75bfb395c5 | |||
| 660b932f61 | |||
| 7fc188123d | |||
| 3e183a270b | |||
| 30d9b325dd | |||
| 4370f366b5 | |||
| 9816e0dc91 | |||
| 2dce51bfbf | |||
| 3f079c3514 | |||
| abfccdb5d1 | |||
| 0f2aa12727 | |||
| 422f7d2e4c | |||
| 7ab607486c | |||
| c62f61755f | |||
| 90d14ef23b | |||
| 385cf3b2e4 | |||
| 37efe9a8d8 | |||
| 026aef16bd | |||
| d3d9fd32ec | |||
| 6b8dcbc42f | |||
| 25231c4b9e | |||
| 57d2843f90 | |||
| c333d08f34 | |||
| 6f75c7ec24 | |||
| 6fe8c6bad9 | |||
| aaf190132b | |||
| 9614b24c92 |
@@ -1,7 +1,7 @@
|
||||
<p align="center">
|
||||
<img alt="logo" src="https://sa-token.cc/logo.png" width="150" height="150">
|
||||
</p>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.33.0</h1>
|
||||
<h1 align="center" style="margin: 30px 0 30px; font-weight: bold;">Sa-Token v1.34.0</h1>
|
||||
<h4 align="center">一个轻量级 Java 权限认证框架,让鉴权变得简单、优雅!</h4>
|
||||
<p align="center">
|
||||
<a href="https://gitee.com/dromara/sa-token/stargazers"><img src="https://gitee.com/dromara/sa-token/badge/star.svg?theme=gvp"></a>
|
||||
@@ -194,3 +194,9 @@ QQ交流群:707350988 [点击加入](https://jq.qq.com/?_wv=1027&k=tqbzHT2D)
|
||||
|
||||
<br>
|
||||
|
||||
加入群聊的好处:
|
||||
- 第一时间收到框架更新通知。
|
||||
- 第一时间收到框架 bug 通知。
|
||||
- 第一时间收到新增开源案例通知。
|
||||
- 和众多大佬一起互相 (huá shǔi) 交流 (mō yú)。
|
||||
|
||||
|
||||
@@ -6,6 +6,8 @@ call mvn clean
|
||||
cd sa-token-demo
|
||||
|
||||
cd sa-token-demo-alone-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-alone-redis-cluster & call mvn clean & cd ..
|
||||
cd sa-token-demo-case & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo-consumer & call mvn clean & cd ..
|
||||
cd sa-token-demo-dubbo-provider & call mvn clean & cd ..
|
||||
cd sa-token-demo-grpc & call mvn clean & cd ..
|
||||
@@ -16,15 +18,19 @@ cd sa-token-demo-quick-login & call mvn clean & cd ..
|
||||
cd sa-token-demo-solon & call mvn clean & cd ..
|
||||
cd sa-token-demo-case & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot3-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot-redis & call mvn clean & cd ..
|
||||
cd sa-token-demo-springboot-redisson & call mvn clean & cd ..
|
||||
cd sa-token-demo-test & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso1-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso2-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso3-client & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso3-client-nosdk & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso-server & call mvn clean & cd ..
|
||||
cd sa-token-demo-sso-server-solon & call mvn clean & cd ..
|
||||
cd sa-token-demo-thymeleaf & call mvn clean & cd ..
|
||||
cd sa-token-demo-webflux & call mvn clean & cd ..
|
||||
cd sa-token-demo-webflux-springboot3 & call mvn clean & cd ..
|
||||
cd sa-token-demo-websocket & call mvn clean & cd ..
|
||||
cd sa-token-demo-websocket-spring & call mvn clean & cd ..
|
||||
|
||||
|
||||
@@ -24,9 +24,10 @@
|
||||
<module>sa-token-plugin</module>
|
||||
<!-- <module>sa-token-test</module> -->
|
||||
<!-- <module>sa-token-demo/sa-token-demo-solon</module> -->
|
||||
</modules>
|
||||
|
||||
<!-- 开源协议 apache 2.0 -->
|
||||
<!-- <module>sa-token-demo/sa-token-demo-sso-server-solon</module> -->
|
||||
</modules>
|
||||
|
||||
<!-- 开源协议 apache 2.0 -->
|
||||
<licenses>
|
||||
<license>
|
||||
<name>Apache 2</name>
|
||||
@@ -38,7 +39,7 @@
|
||||
|
||||
<!-- 一些属性 -->
|
||||
<properties>
|
||||
<revision>1.33.0</revision>
|
||||
<revision>1.34.0</revision>
|
||||
<jdk.version>1.8</jdk.version>
|
||||
<project.build.sourceEncoding>utf-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>utf-8</project.reporting.outputEncoding>
|
||||
@@ -94,14 +95,31 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.2</version>
|
||||
<version>3.10.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<encoding>UTF-8</encoding>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-javadoc-plugin</artifactId>
|
||||
<version>2.10.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>attach-javadocs</id>
|
||||
<goals>
|
||||
<goal>jar</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<additionalparam>-Xdoclint:none</additionalparam>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<!-- 统一版本号管理 -->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
|
||||
@@ -8,7 +8,7 @@ import java.lang.annotation.Target;
|
||||
import cn.dev33.satoken.basic.SaBasicTemplate;
|
||||
|
||||
/**
|
||||
* Http Basic 认证:只有通过 Basic 认证后才能进入该方法
|
||||
* Http Basic 认证校验:只有通过 Basic 认证后才能进入该方法
|
||||
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
|
||||
* @author kong
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 登录认证:只有登录之后才能进入该方法
|
||||
* 登录认证校验:只有登录之后才能进入该方法
|
||||
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
|
||||
* @author kong
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 权限认证:必须具有指定权限才能进入该方法
|
||||
* 权限认证校验:必须具有指定权限才能进入该方法
|
||||
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
|
||||
* @author kong
|
||||
*
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* 角色认证:必须具有指定角色标识才能进入该方法
|
||||
* 角色认证校验:必须具有指定角色标识才能进入该方法
|
||||
* <p> 可标注在函数、类上(效果等同于标注在此类的所有方法上)
|
||||
* @author kong
|
||||
*
|
||||
|
||||
@@ -10,86 +10,86 @@ import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* Cookie Model
|
||||
* Cookie Model
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class SaCookie {
|
||||
|
||||
|
||||
/**
|
||||
* 写入响应头时使用的key
|
||||
* 写入响应头时使用的key
|
||||
*/
|
||||
public static final String HEADER_NAME = "Set-Cookie";
|
||||
|
||||
/**
|
||||
* 名称
|
||||
* 名称
|
||||
*/
|
||||
private String name;
|
||||
|
||||
|
||||
/**
|
||||
* 值
|
||||
* 值
|
||||
*/
|
||||
private String value;
|
||||
|
||||
/**
|
||||
* 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
* 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
*/
|
||||
private int maxAge = -1;
|
||||
|
||||
private int maxAge = -1;
|
||||
|
||||
/**
|
||||
* 域
|
||||
*/
|
||||
private String domain;
|
||||
private String domain;
|
||||
|
||||
/**
|
||||
* 路径
|
||||
* 路径
|
||||
*/
|
||||
private String path;
|
||||
|
||||
/**
|
||||
* 是否只在 https 协议下有效
|
||||
* 是否只在 https 协议下有效
|
||||
*/
|
||||
private Boolean secure = false;
|
||||
|
||||
private Boolean secure = false;
|
||||
|
||||
/**
|
||||
* 是否禁止 js 操作 Cookie
|
||||
* 是否禁止 js 操作 Cookie
|
||||
*/
|
||||
private Boolean httpOnly = false;
|
||||
|
||||
private Boolean httpOnly = false;
|
||||
|
||||
/**
|
||||
* 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
*/
|
||||
private String sameSite;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 构造一个
|
||||
* 构造一个
|
||||
*/
|
||||
public SaCookie() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 构造一个
|
||||
* 构造一个
|
||||
* @param name 名字
|
||||
* @param value 值
|
||||
* @param value 值
|
||||
*/
|
||||
public SaCookie(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @return 名称
|
||||
* @return 名称
|
||||
*/
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param name 名称
|
||||
* @return 对象自身
|
||||
* @param name 名称
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setName(String name) {
|
||||
this.name = name;
|
||||
@@ -97,15 +97,15 @@ public class SaCookie {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 值
|
||||
* @return 值
|
||||
*/
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
* @param value 值
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setValue(String value) {
|
||||
this.value = value;
|
||||
@@ -113,15 +113,15 @@ public class SaCookie {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
* @return 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
*/
|
||||
public int getMaxAge() {
|
||||
return maxAge;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param maxAge 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
* @return 对象自身
|
||||
* @param maxAge 有效时长 (单位:秒),-1代表为临时Cookie 浏览器关闭后自动删除
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setMaxAge(int maxAge) {
|
||||
this.maxAge = maxAge;
|
||||
@@ -129,15 +129,15 @@ public class SaCookie {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 域
|
||||
* @return 域
|
||||
*/
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param domain 域
|
||||
* @return 对象自身
|
||||
* @param domain 域
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setDomain(String domain) {
|
||||
this.domain = domain;
|
||||
@@ -145,15 +145,15 @@ public class SaCookie {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 路径
|
||||
* @return 路径
|
||||
*/
|
||||
public String getPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param path 路径
|
||||
* @return 对象自身
|
||||
* @param path 路径
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setPath(String path) {
|
||||
this.path = path;
|
||||
@@ -161,15 +161,15 @@ public class SaCookie {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否只在 https 协议下有效
|
||||
* @return 是否只在 https 协议下有效
|
||||
*/
|
||||
public Boolean getSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param secure 是否只在 https 协议下有效
|
||||
* @return 对象自身
|
||||
* @param secure 是否只在 https 协议下有效
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setSecure(Boolean secure) {
|
||||
this.secure = secure;
|
||||
@@ -177,15 +177,15 @@ public class SaCookie {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return 是否禁止 js 操作 Cookie
|
||||
* @return 是否禁止 js 操作 Cookie
|
||||
*/
|
||||
public Boolean getHttpOnly() {
|
||||
return httpOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param httpOnly 是否禁止 js 操作 Cookie
|
||||
* @return 对象自身
|
||||
* @param httpOnly 是否禁止 js 操作 Cookie
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setHttpOnly(Boolean httpOnly) {
|
||||
this.httpOnly = httpOnly;
|
||||
@@ -201,7 +201,7 @@ public class SaCookie {
|
||||
|
||||
/**
|
||||
* @param sameSite 第三方限制级别(Strict=完全禁止,Lax=部分允许,None=不限制)
|
||||
* @return 对象自身
|
||||
* @return 对象自身
|
||||
*/
|
||||
public SaCookie setSameSite(String sameSite) {
|
||||
this.sameSite = sameSite;
|
||||
@@ -209,57 +209,57 @@ public class SaCookie {
|
||||
}
|
||||
|
||||
|
||||
// toString
|
||||
// toString
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SaCookie [name=" + name + ", value=" + value + ", maxAge=" + maxAge + ", domain=" + domain + ", path=" + path
|
||||
return "SaCookie [name=" + name + ", value=" + value + ", maxAge=" + maxAge + ", domain=" + domain + ", path=" + path
|
||||
+ ", secure=" + secure + ", httpOnly=" + httpOnly + ", sameSite="
|
||||
+ sameSite + "]";
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建一下
|
||||
* 构建一下
|
||||
*/
|
||||
public void builde() {
|
||||
if(path == null) {
|
||||
path = "/";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 转换为响应头 Set-Cookie 参数需要的值
|
||||
* @return /
|
||||
* @return /
|
||||
*/
|
||||
public String toHeaderValue() {
|
||||
this.builde();
|
||||
|
||||
|
||||
if(SaFoxUtil.isEmpty(name)) {
|
||||
throw new SaTokenException("name不能为空").setCode(SaErrorCode.CODE_12002);
|
||||
}
|
||||
if(value != null && value.indexOf(";") > -1) {
|
||||
if(value != null && value.contains(";")) {
|
||||
throw new SaTokenException("无效Value:" + value).setCode(SaErrorCode.CODE_12003);
|
||||
}
|
||||
|
||||
// Set-Cookie: name=value; Max-Age=100000; Expires=Tue, 05-Oct-2021 20:28:17 GMT; Domain=localhost; Path=/; Secure; HttpOnly; SameSite=Lax
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(name + "=" + value);
|
||||
|
||||
// Set-Cookie: name=value; Max-Age=100000; Expires=Tue, 05-Oct-2021 20:28:17 GMT; Domain=localhost; Path=/; Secure; HttpOnly; SameSite=Lax
|
||||
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(name).append("=").append(value);
|
||||
|
||||
if(maxAge >= 0) {
|
||||
sb.append("; Max-Age=" + maxAge);
|
||||
sb.append("; Max-Age=").append(maxAge);
|
||||
String expires;
|
||||
if(maxAge == 0) {
|
||||
expires = Instant.EPOCH.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
||||
} else {
|
||||
expires = OffsetDateTime.now().plusSeconds(maxAge).format(DateTimeFormatter.RFC_1123_DATE_TIME);
|
||||
}
|
||||
sb.append("; Expires=" + expires);
|
||||
sb.append("; Expires=").append(expires);
|
||||
}
|
||||
if(!SaFoxUtil.isEmpty(domain)) {
|
||||
sb.append("; Domain=" + domain);
|
||||
sb.append("; Domain=").append(domain);
|
||||
}
|
||||
if(!SaFoxUtil.isEmpty(path)) {
|
||||
sb.append("; Path=" + path);
|
||||
sb.append("; Path=").append(path);
|
||||
}
|
||||
if(secure) {
|
||||
sb.append("; Secure");
|
||||
@@ -268,10 +268,10 @@ public class SaCookie {
|
||||
sb.append("; HttpOnly");
|
||||
}
|
||||
if(!SaFoxUtil.isEmpty(sameSite)) {
|
||||
sb.append("; SameSite=" + sameSite);
|
||||
sb.append("; SameSite=").append(sameSite);
|
||||
}
|
||||
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -155,4 +155,15 @@ public interface SaErrorCode {
|
||||
/** RSA 私钥解密异常 */
|
||||
public static final int CODE_12119 = 12119;
|
||||
|
||||
// ------------
|
||||
|
||||
/** 参与参数签名的秘钥不可为空 */
|
||||
public static final int CODE_12201 = 12201;
|
||||
|
||||
/** 给定的签名无效 */
|
||||
public static final int CODE_12202 = 12202;
|
||||
|
||||
/** timestamp 超出允许的范围 */
|
||||
public static final int CODE_12203 = 12203;
|
||||
|
||||
}
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
package cn.dev33.satoken.id;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.exception.IdTokenInvalidException;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
/**
|
||||
* <h1> 本类设计已过时,未来版本可能移除此类,请及时更换为 SaSameTemplate ,使用方式保持不变 </h1>
|
||||
*
|
||||
* Sa-Token-Id 身份凭证模块
|
||||
* <p> 身份凭证的获取与校验,可用于微服务内部调用鉴权
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public class SaIdTemplate {
|
||||
|
||||
/**
|
||||
* 在 Request 上储存 Id-Token 时建议使用的key
|
||||
*/
|
||||
public static final String ID_TOKEN = "SA_ID_TOKEN";
|
||||
|
||||
// -------------------- 获取 & 校验
|
||||
|
||||
/**
|
||||
* 获取当前Id-Token, 如果不存在,则立即创建并返回
|
||||
* @return 当前token
|
||||
*/
|
||||
public String getToken() {
|
||||
String currentToken = getTokenNh();
|
||||
if(SaFoxUtil.isEmpty(currentToken)) {
|
||||
// 注意这里的自刷新不能做到高并发可用
|
||||
currentToken = refreshToken();
|
||||
}
|
||||
return currentToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个Id-Token是否有效
|
||||
* @param token 要验证的token
|
||||
* @return 这个token是否有效
|
||||
*/
|
||||
public boolean isValid(String token) {
|
||||
// 1、 如果传入的token未空,立即返回false
|
||||
if(SaFoxUtil.isEmpty(token)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 2、 验证当前 Id-Token 及 Past-Id-Token
|
||||
return token.equals(getToken()) || token.equals(getPastTokenNh());
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
|
||||
* @param token 要验证的token
|
||||
*/
|
||||
public void checkToken(String token) {
|
||||
if(isValid(token) == false) {
|
||||
token = (token == null ? "" : token);
|
||||
throw new IdTokenInvalidException("无效Id-Token:" + token);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
|
||||
*/
|
||||
public void checkCurrentRequestToken() {
|
||||
checkToken(SaHolder.getRequest().getHeader(ID_TOKEN));
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
|
||||
* @return 新Token
|
||||
*/
|
||||
public String refreshToken() {
|
||||
|
||||
// 1. 先将当前 Id-Token 写入到 Past-Id-Token 中
|
||||
String idToken = getTokenNh();
|
||||
if(SaFoxUtil.isEmpty(idToken) == false) {
|
||||
savePastToken(idToken, getTokenTimeout());
|
||||
}
|
||||
|
||||
// 2. 再刷新当前Id-Token
|
||||
String newIdToken = createToken();
|
||||
saveToken(newIdToken);
|
||||
|
||||
// 3. 返回新的 Id-Token
|
||||
return newIdToken;
|
||||
}
|
||||
|
||||
|
||||
// ------------------------------ 保存Token
|
||||
|
||||
/**
|
||||
* 保存Id-Token
|
||||
* @param token token
|
||||
*/
|
||||
public void saveToken(String token) {
|
||||
if(SaFoxUtil.isEmpty(token)) {
|
||||
return;
|
||||
}
|
||||
SaManager.getSaTokenDao().set(splicingTokenSaveKey(), token, SaManager.getConfig().getIdTokenTimeout());
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存Past-Id-Token
|
||||
* @param token token
|
||||
* @param timeout 有效期(单位:秒)
|
||||
*/
|
||||
public void savePastToken(String token, long timeout){
|
||||
if(SaFoxUtil.isEmpty(token)) {
|
||||
return;
|
||||
}
|
||||
SaManager.getSaTokenDao().set(splicingPastTokenSaveKey(), token, timeout);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 获取Token
|
||||
|
||||
/**
|
||||
* 获取Id-Token,不做任何处理
|
||||
* @return token
|
||||
*/
|
||||
public String getTokenNh() {
|
||||
return SaManager.getSaTokenDao().get(splicingTokenSaveKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Past-Id-Token,不做任何处理
|
||||
* @return token
|
||||
*/
|
||||
public String getPastTokenNh() {
|
||||
return SaManager.getSaTokenDao().get(splicingPastTokenSaveKey());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Id-Token的剩余有效期 (单位:秒)
|
||||
* @return token
|
||||
*/
|
||||
public long getTokenTimeout() {
|
||||
return SaManager.getSaTokenDao().getTimeout(splicingTokenSaveKey());
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 创建Token
|
||||
|
||||
/**
|
||||
* 创建一个Id-Token
|
||||
* @return Token
|
||||
*/
|
||||
public String createToken() {
|
||||
return SaFoxUtil.getRandomString(64);
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 拼接key
|
||||
|
||||
/**
|
||||
* 拼接key:Id-Token 的存储 key
|
||||
* @return key
|
||||
*/
|
||||
public String splicingTokenSaveKey() {
|
||||
return SaManager.getConfig().getTokenName() + ":var:id-token";
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接key:次级 Id-Token 的存储 key
|
||||
* @return key
|
||||
*/
|
||||
public String splicingPastTokenSaveKey() {
|
||||
return SaManager.getConfig().getTokenName() + ":var:past-id-token";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
package cn.dev33.satoken.id;
|
||||
|
||||
/**
|
||||
* <h1> 本类设计已过时,未来版本可能移除此类,请及时更换为 SaSameUtil ,使用方式保持不变 </h1>
|
||||
*
|
||||
* Sa-Token-Id 身份凭证模块-工具类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Deprecated
|
||||
public class SaIdUtil {
|
||||
|
||||
private SaIdUtil(){}
|
||||
|
||||
/**
|
||||
* 在 Request 上储存 Id-Token 时建议使用的key
|
||||
*/
|
||||
public static final String ID_TOKEN = SaIdTemplate.ID_TOKEN;
|
||||
|
||||
/**
|
||||
* 底层 SaIdTemplate 对象
|
||||
*/
|
||||
public static SaIdTemplate saIdTemplate = new SaIdTemplate();
|
||||
|
||||
// -------------------- 获取 & 校验
|
||||
|
||||
/**
|
||||
* 获取当前Id-Token, 如果不存在,则立即创建并返回
|
||||
* @return 当前token
|
||||
*/
|
||||
public static String getToken() {
|
||||
return saIdTemplate.getToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断一个Id-Token是否有效
|
||||
* @param token 要验证的token
|
||||
* @return 这个token是否有效
|
||||
*/
|
||||
public static boolean isValid(String token) {
|
||||
return saIdTemplate.isValid(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验一个Id-Token是否有效 (如果无效则抛出异常)
|
||||
* @param token 要验证的token
|
||||
*/
|
||||
public static void checkToken(String token) {
|
||||
saIdTemplate.checkToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验当前Request提供的Id-Token是否有效 (如果无效则抛出异常)
|
||||
*/
|
||||
public static void checkCurrentRequestToken() {
|
||||
saIdTemplate.checkCurrentRequestToken();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新一次Id-Token (注意集群环境中不要多个服务重复调用)
|
||||
* @return 新Token
|
||||
*/
|
||||
public static String refreshToken() {
|
||||
return saIdTemplate.refreshToken();
|
||||
}
|
||||
|
||||
|
||||
// -------------------- 获取Token
|
||||
|
||||
/**
|
||||
* 获取Id-Token,不做任何处理
|
||||
* @return token
|
||||
*/
|
||||
public static String getTokenNh() {
|
||||
return saIdTemplate.getTokenNh();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Past-Id-Token,不做任何处理
|
||||
* @return token
|
||||
*/
|
||||
public static String getPastTokenNh() {
|
||||
return saIdTemplate.getPastTokenNh();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.dev33.satoken.secure;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
@@ -27,7 +28,7 @@ import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
/**
|
||||
* Sa-Token 常见加密算法工具类
|
||||
*
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@@ -35,7 +36,7 @@ public class SaSecureUtil {
|
||||
|
||||
private SaSecureUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Base64编码
|
||||
*/
|
||||
@@ -45,11 +46,11 @@ public class SaSecureUtil {
|
||||
* Base64解码
|
||||
*/
|
||||
private static Base64.Decoder decoder = Base64.getDecoder();
|
||||
|
||||
|
||||
// ----------------------- 摘要加密 -----------------------
|
||||
|
||||
|
||||
/**
|
||||
* md5加密
|
||||
* md5加密
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
@@ -75,8 +76,8 @@ public class SaSecureUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* sha1加密
|
||||
*
|
||||
* sha1加密
|
||||
*
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
@@ -103,8 +104,8 @@ public class SaSecureUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* sha256加密
|
||||
*
|
||||
* sha256加密
|
||||
*
|
||||
* @param str 指定字符串
|
||||
* @return 加密后的字符串
|
||||
*/
|
||||
@@ -112,8 +113,8 @@ public class SaSecureUtil {
|
||||
try {
|
||||
str = (str == null ? "" : str);
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
messageDigest.update(str.getBytes("UTF-8"));
|
||||
|
||||
messageDigest.update(str.getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
byte[] bytes = messageDigest.digest();
|
||||
StringBuilder builder = new StringBuilder();
|
||||
String temp;
|
||||
@@ -124,7 +125,7 @@ public class SaSecureUtil {
|
||||
}
|
||||
builder.append(temp);
|
||||
}
|
||||
|
||||
|
||||
return builder.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12113);
|
||||
@@ -132,20 +133,20 @@ public class SaSecureUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* md5加盐加密: md5(md5(str) + md5(salt))
|
||||
* md5加盐加密: md5(md5(str) + md5(salt))
|
||||
* @param str 字符串
|
||||
* @param salt 盐
|
||||
* @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";
|
||||
|
||||
@@ -159,7 +160,7 @@ public class SaSecureUtil {
|
||||
public static String aesEncrypt(String key, String text) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
|
||||
byte[] byteContent = text.getBytes("utf-8");
|
||||
byte[] byteContent = text.getBytes(StandardCharsets.UTF_8);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));
|
||||
byte[] result = cipher.doFinal(byteContent);
|
||||
return encoder.encodeToString(result);
|
||||
@@ -169,7 +170,7 @@ public class SaSecureUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* AES解密
|
||||
* AES解密
|
||||
* @param key 加密的密钥
|
||||
* @param text 已加密的密文
|
||||
* @return 返回解密后的数据
|
||||
@@ -179,16 +180,16 @@ public class SaSecureUtil {
|
||||
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");
|
||||
return new String(result, StandardCharsets.UTF_8);
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12115);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成加密秘钥
|
||||
* 生成加密秘钥
|
||||
* @param password 秘钥
|
||||
* @return SecretKeySpec
|
||||
* @return SecretKeySpec
|
||||
* @throws NoSuchAlgorithmException
|
||||
*/
|
||||
private static SecretKeySpec getSecretKey(final String password) throws NoSuchAlgorithmException {
|
||||
@@ -204,14 +205,14 @@ public class SaSecureUtil {
|
||||
// ----------------------- 非对称加密 RSA -----------------------
|
||||
|
||||
private static final String ALGORITHM = "RSA";
|
||||
|
||||
|
||||
private static final int KEY_SIZE = 1024;
|
||||
|
||||
|
||||
// ---------- 5个常用方法
|
||||
// ---------- 5个常用方法
|
||||
|
||||
/**
|
||||
* 生成密钥对
|
||||
* 生成密钥对
|
||||
* @return Map对象 (private=私钥, public=公钥)
|
||||
* @throws Exception 异常
|
||||
*/
|
||||
@@ -242,24 +243,24 @@ public class SaSecureUtil {
|
||||
public static String rsaEncryptByPublic(String publicKeyString, String content) {
|
||||
try {
|
||||
// 获得公钥对象
|
||||
PublicKey publicKey = getPublicKeyFromString(publicKeyString);
|
||||
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();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
|
||||
stringBuilder.append(bytesToHexString(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
return stringBuilder.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12116);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* RSA私钥加密
|
||||
* @param privateKeyString 私钥
|
||||
* @param content 内容
|
||||
@@ -274,17 +275,17 @@ public class SaSecureUtil {
|
||||
// 该密钥能够加密的最大字节长度
|
||||
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8 - 11;
|
||||
byte[][] arrays = splitBytes(content.getBytes(), splitLength);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(bytesToHexString(cipher.doFinal(array)));
|
||||
stringBuilder.append(bytesToHexString(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
return stringBuilder.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12117);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* RSA公钥解密
|
||||
* @param publicKeyString 公钥
|
||||
* @param content 已加密内容
|
||||
@@ -301,11 +302,11 @@ public class SaSecureUtil {
|
||||
int splitLength = ((RSAPublicKey) publicKey).getModulus().bitLength() / 8;
|
||||
byte[] contentBytes = hexStringToBytes(content);
|
||||
byte[][] arrays = splitBytes(contentBytes, splitLength);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(new String(cipher.doFinal(array)));
|
||||
stringBuilder.append(new String(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
return stringBuilder.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12118);
|
||||
}
|
||||
@@ -327,18 +328,18 @@ public class SaSecureUtil {
|
||||
int splitLength = ((RSAPrivateKey) privateKey).getModulus().bitLength() / 8;
|
||||
byte[] contentBytes = hexStringToBytes(content);
|
||||
byte[][] arrays = splitBytes(contentBytes, splitLength);
|
||||
StringBuffer stringBuffer = new StringBuffer();
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
for (byte[] array : arrays) {
|
||||
stringBuffer.append(new String(cipher.doFinal(array)));
|
||||
stringBuilder.append(new String(cipher.doFinal(array)));
|
||||
}
|
||||
return stringBuffer.toString();
|
||||
return stringBuilder.toString();
|
||||
} catch (Exception e) {
|
||||
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12119);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ---------- 获取*钥
|
||||
|
||||
// ---------- 获取*钥
|
||||
|
||||
/** 根据公钥字符串获取 公钥对象 */
|
||||
private static PublicKey getPublicKeyFromString(String key)
|
||||
@@ -352,9 +353,7 @@ public class SaSecureUtil {
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
|
||||
|
||||
PublicKey publicKey = keyFactory.generatePublic(x509KeySpec);
|
||||
|
||||
return publicKey;
|
||||
return keyFactory.generatePublic(x509KeySpec);
|
||||
}
|
||||
|
||||
/** 根据私钥字符串获取 私钥对象 */
|
||||
@@ -369,9 +368,7 @@ public class SaSecureUtil {
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
|
||||
|
||||
PrivateKey privateKey = keyFactory.generatePrivate(x509KeySpec);
|
||||
|
||||
return privateKey;
|
||||
return keyFactory.generatePrivate(x509KeySpec);
|
||||
}
|
||||
|
||||
|
||||
@@ -435,5 +432,5 @@ public class SaSecureUtil {
|
||||
return (byte) "0123456789ABCDEF".indexOf(c);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package cn.dev33.satoken.sign;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
import cn.dev33.satoken.secure.SaSecureUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
|
||||
@@ -15,15 +17,11 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
public interface SaSignTemplate {
|
||||
|
||||
/**
|
||||
* 将所有参数连接成一个字符串
|
||||
* 将所有参数连接成一个字符串(不排序),形如:b=28a=18c=3
|
||||
* @param paramsMap 参数列表
|
||||
* @return 字符串
|
||||
* @return 拼接出的参数字符串
|
||||
*/
|
||||
public default String joinParams(Map<String, Object> paramsMap) {
|
||||
// 保证字段按照字典顺序排列
|
||||
if(paramsMap instanceof TreeMap == false) {
|
||||
paramsMap = new TreeMap<>(paramsMap);
|
||||
}
|
||||
|
||||
// 按照 k1=v1&k2=v2&k3=v3 排列
|
||||
StringBuilder sb = new StringBuilder();
|
||||
@@ -43,6 +41,21 @@ public interface SaSignTemplate {
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 将所有参数按照字典顺序连接成一个字符串,形如:a=18b=28c=3
|
||||
* @param paramsMap 参数列表
|
||||
* @return 拼接出的参数字符串
|
||||
*/
|
||||
public default String joinParamsDictSort(Map<String, Object> paramsMap) {
|
||||
// 保证字段按照字典顺序排列
|
||||
if(paramsMap instanceof TreeMap == false) {
|
||||
paramsMap = new TreeMap<>(paramsMap);
|
||||
}
|
||||
|
||||
// 拼接
|
||||
return joinParams(paramsMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建签名:md5(paramsStr + keyStr)
|
||||
* @param paramsMap 参数列表
|
||||
@@ -50,10 +63,85 @@ public interface SaSignTemplate {
|
||||
* @return 签名
|
||||
*/
|
||||
public default String createSign(Map<String, Object> paramsMap, String key) {
|
||||
String paramsStr = joinParams(paramsMap);
|
||||
SaTokenException.throwByNull(key, "参与参数签名的秘钥不可为空", SaErrorCode.CODE_12201);
|
||||
|
||||
String paramsStr = joinParamsDictSort(paramsMap);
|
||||
String fullStr = paramsStr + "&key=" + key;
|
||||
return SaSecureUtil.md5(fullStr);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 判断:给定的参数 + 秘钥 生成的签名是否为有效签名
|
||||
* @param paramsMap 参数列表
|
||||
* @param key 秘钥
|
||||
* @param sign 待验证的签名
|
||||
* @return 签名是否有效
|
||||
*/
|
||||
public default boolean isValidSign(Map<String, Object> paramsMap, String key, String sign) {
|
||||
String theSign = createSign(paramsMap, key);
|
||||
return theSign.equals(sign);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:给定的参数 + 秘钥 生成的签名是否为有效签名,如果签名无效则抛出异常
|
||||
* @param paramsMap 参数列表
|
||||
* @param key 秘钥
|
||||
* @param sign 待验证的签名
|
||||
*/
|
||||
public default void checkSign(Map<String, Object> paramsMap, String key, String sign) {
|
||||
if(isValidSign(paramsMap, key, sign) == false) {
|
||||
throw new SaTokenException("无效签名:" + sign).setCode(SaErrorCode.CODE_12202);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数
|
||||
* @param paramsMap 参数列表
|
||||
* @param key 秘钥
|
||||
* @return 加工后的参数列表
|
||||
*/
|
||||
public default Map<String, Object> addSignParams(Map<String, Object> paramsMap, String key) {
|
||||
paramsMap.put("timestamp", String.valueOf(System.currentTimeMillis()));
|
||||
paramsMap.put("nonce", SaFoxUtil.getRandomString(32));
|
||||
paramsMap.put("sign", createSign(paramsMap, key));
|
||||
return paramsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给 paramsMap 追加 timestamp、nonce、sign 三个参数,并转换为参数字符串,形如:
|
||||
* <code>data=xxx8nonce=xxx8timestamp=xxx8sign=xxx</code>
|
||||
* @param paramsMap 参数列表
|
||||
* @param key 秘钥
|
||||
* @return 加工后的参数列表 转化为的参数字符串
|
||||
*/
|
||||
public default String addSignParamsToString(Map<String, Object> paramsMap, String key) {
|
||||
// 追加参数
|
||||
paramsMap = addSignParams(paramsMap, key);
|
||||
|
||||
// .
|
||||
return joinParams(paramsMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断:指定时间戳与当前时间戳的差距是否在允许的范围内
|
||||
* @param timestamp 待校验的时间戳
|
||||
* @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制
|
||||
* @return 是否在允许的范围内
|
||||
*/
|
||||
public default boolean isValidTimestamp(long timestamp, long allowDisparity) {
|
||||
long disparity = Math.abs(System.currentTimeMillis() - timestamp);
|
||||
return allowDisparity == -1 || disparity <= allowDisparity;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验:指定时间戳与当前时间戳的差距是否在允许的范围内,如果超出则抛出异常
|
||||
* @param timestamp 待校验的时间戳
|
||||
* @param allowDisparity 允许的最大时间差(单位:ms),-1 代表不限制
|
||||
*/
|
||||
public default void checkTimestamp(long timestamp, long allowDisparity) {
|
||||
if(isValidTimestamp(timestamp, allowDisparity) == false) {
|
||||
throw new SaTokenException("timestamp 超出允许的范围:" + timestamp).setCode(SaErrorCode.CODE_12203);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -412,14 +412,10 @@ public class StpLogic {
|
||||
if(isConcurrent) {
|
||||
// 全局配置是否允许复用旧 Token
|
||||
if(getConfigOfIsShare()) {
|
||||
// 为确保 jwt-simple 模式的 token Extra 数据生成不受旧token影响,这里必须确保 is-share 配置项在 ExtraData 为空时才可以生效
|
||||
// 即:在 login 时提供了 Extra 数据后,即使配置了 is-share=true 也不能复用旧 Token,必须创建新 Token
|
||||
if(loginModel.isSetExtraData() == false) {
|
||||
String tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
|
||||
// 复用成功的话就直接返回,否则还是要继续新建Token
|
||||
if(SaFoxUtil.isNotEmpty(tokenValue)) {
|
||||
return tokenValue;
|
||||
}
|
||||
String tokenValue = getTokenValueByLoginId(id, loginModel.getDeviceOrDefault());
|
||||
// 复用成功的话就直接返回,否则还是要继续新建Token
|
||||
if(SaFoxUtil.isNotEmpty(tokenValue)) {
|
||||
return tokenValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -506,13 +502,9 @@ public class StpLogic {
|
||||
}
|
||||
}
|
||||
List<TokenSign> list = session.tokenSignListCopyByDevice(device);
|
||||
// 遍历操作
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
// 只操作前n条
|
||||
if(i >= list.size() - maxLoginCount) {
|
||||
continue;
|
||||
}
|
||||
// 清理: token签名、token最后活跃时间
|
||||
// 遍历操作,只操作前n条
|
||||
for (int i = 0; i < list.size() - maxLoginCount; i++) {
|
||||
// 清理: token签名、token最后活跃时间
|
||||
String tokenValue = list.get(i).getValue();
|
||||
session.removeTokenSign(tokenValue);
|
||||
clearLastActivity(tokenValue);
|
||||
|
||||
@@ -3,6 +3,7 @@ package cn.dev33.satoken.temp;
|
||||
import cn.dev33.satoken.SaManager;
|
||||
import cn.dev33.satoken.strategy.SaStrategy;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaTokenConsts;
|
||||
|
||||
/**
|
||||
* Sa-Token 临时令牌验证模块接口
|
||||
@@ -12,18 +13,29 @@ import cn.dev33.satoken.util.SaFoxUtil;
|
||||
public interface SaTempInterface {
|
||||
|
||||
/**
|
||||
* 根据value创建一个token
|
||||
* 为 指定值 创建一个临时 Token
|
||||
* @param value 指定值
|
||||
* @param timeout 有效期,单位:秒
|
||||
* @param timeout 有效期,单位:秒,-1代表永久有效
|
||||
* @return 生成的token
|
||||
*/
|
||||
public default String createToken(Object value, long timeout) {
|
||||
return createToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, value, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 为 指定服务 指定值 创建一个 Token
|
||||
* @param service 服务标识
|
||||
* @param value 指定值
|
||||
* @param timeout 有效期,单位:秒,-1代表永久有效
|
||||
* @return 生成的token
|
||||
*/
|
||||
public default String createToken(String service, Object value, long timeout) {
|
||||
|
||||
// 生成 token
|
||||
String token = SaStrategy.me.createToken.apply(null, null);
|
||||
|
||||
// 持久化映射关系
|
||||
String key = splicingKeyTempToken(token);
|
||||
String key = splicingKeyTempToken(service, token);
|
||||
SaManager.getSaTokenDao().setObject(key, value, timeout);
|
||||
|
||||
// 返回
|
||||
@@ -31,53 +43,96 @@ public interface SaTempInterface {
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析token获取value
|
||||
* @param token 指定token
|
||||
* @return See Note
|
||||
* 解析 Token 获取 value
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public default Object parseToken(String token) {
|
||||
String key = splicingKeyTempToken(token);
|
||||
return parseToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token 获取 value
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public default Object parseToken(String service, String token) {
|
||||
String key = splicingKeyTempToken(service, token);
|
||||
return SaManager.getSaTokenDao().getObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析token获取value,并转换为指定类型
|
||||
* @param token 指定token
|
||||
* 解析 Token 获取 value,并转换为指定类型
|
||||
* @param token 指定 Token
|
||||
* @param cs 指定类型
|
||||
* @param <T> 默认值的类型
|
||||
* @return See Note
|
||||
* @return /
|
||||
*/
|
||||
public default<T> T parseToken(String token, Class<T> cs) {
|
||||
return SaFoxUtil.getValueByType(parseToken(token), cs);
|
||||
return parseToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token, cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token 获取 value,并转换为指定类型
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
* @param cs 指定类型
|
||||
* @param <T> 默认值的类型
|
||||
* @return /
|
||||
*/
|
||||
public default<T> T parseToken(String service, String token, Class<T> cs) {
|
||||
return SaFoxUtil.getValueByType(parseToken(service, token), cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 token 的剩余有效期,单位:秒
|
||||
* 获取指定 Token 的剩余有效期,单位:秒
|
||||
* <p> 返回值 -1 代表永久,-2 代表token无效
|
||||
* @param token see note
|
||||
* @return see note
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public default long getTimeout(String token) {
|
||||
String key = splicingKeyTempToken(token);
|
||||
return getTimeout(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定服务指定 Token 的剩余有效期,单位:秒
|
||||
* <p> 返回值 -1 代表永久,-2 代表token无效
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public default long getTimeout(String service, String token) {
|
||||
String key = splicingKeyTempToken(service, token);
|
||||
return SaManager.getSaTokenDao().getObjectTimeout(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 token
|
||||
* @param token 指定token
|
||||
* 删除一个 Token
|
||||
* @param token 指定 Token
|
||||
*/
|
||||
public default void deleteToken(String token) {
|
||||
String key = splicingKeyTempToken(token);
|
||||
deleteToken(SaTokenConsts.DEFAULT_TEMP_TOKEN_SERVICE, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 Token
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
*/
|
||||
public default void deleteToken(String service, String token) {
|
||||
String key = splicingKeyTempToken(service, token);
|
||||
SaManager.getSaTokenDao().deleteObject(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取映射关系的持久化key
|
||||
* @param service 服务标识
|
||||
* @param token token值
|
||||
* @return key
|
||||
*/
|
||||
public default String splicingKeyTempToken(String token) {
|
||||
return SaManager.getConfig().getTokenName() + ":temp-token:" + token;
|
||||
public default String splicingKeyTempToken(String service, String token) {
|
||||
return SaManager.getConfig().getTokenName() + ":temp-token:" + service + ":" + token;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,53 +11,106 @@ public class SaTempUtil {
|
||||
|
||||
private SaTempUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 根据value创建一个token
|
||||
* 为 指定值 创建一个临时 Token
|
||||
* @param value 指定值
|
||||
* @param timeout 有效期,单位:秒
|
||||
* @param timeout 有效期,单位:秒,-1代表永久有效
|
||||
* @return 生成的token
|
||||
*/
|
||||
public static String createToken(Object value, long timeout) {
|
||||
return SaManager.getSaTemp().createToken(value, timeout);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 解析token获取value
|
||||
* @param token 指定token
|
||||
* @return See Note
|
||||
* 为 指定服务 指定值 创建一个 Token
|
||||
* @param service 服务标识
|
||||
* @param value 指定值
|
||||
* @param timeout 有效期,单位:秒,-1代表永久有效
|
||||
* @return 生成的token
|
||||
*/
|
||||
public static String createToken(String service, Object value, long timeout) {
|
||||
return SaManager.getSaTemp().createToken(service, value, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token 获取 value
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public static Object parseToken(String token) {
|
||||
return SaManager.getSaTemp().parseToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析token获取value,并转换为指定类型
|
||||
* @param token 指定token
|
||||
* 解析 Token 获取 value
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public static Object parseToken(String service, String token) {
|
||||
return SaManager.getSaTemp().parseToken(service, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token 获取 value,并转换为指定类型
|
||||
* @param token 指定 Token
|
||||
* @param cs 指定类型
|
||||
* @param <T> 默认值的类型
|
||||
* @return See Note
|
||||
* @return /
|
||||
*/
|
||||
public static<T> T parseToken(String token, Class<T> cs) {
|
||||
return SaManager.getSaTemp().parseToken(token, cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 Token 获取 value,并转换为指定类型
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
* @param cs 指定类型
|
||||
* @param <T> 默认值的类型
|
||||
* @return /
|
||||
*/
|
||||
public static<T> T parseToken(String service, String token, Class<T> cs) {
|
||||
return SaManager.getSaTemp().parseToken(service, token, cs);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取指定 token 的剩余有效期,单位:秒
|
||||
* 获取指定 Token 的剩余有效期,单位:秒
|
||||
* <p> 返回值 -1 代表永久,-2 代表token无效
|
||||
* @param token see note
|
||||
* @return see note
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public static long getTimeout(String token) {
|
||||
return SaManager.getSaTemp().getTimeout(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 token
|
||||
* @param token 指定token
|
||||
* 获取指定服务指定 Token 的剩余有效期,单位:秒
|
||||
* <p> 返回值 -1 代表永久,-2 代表token无效
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
* @return /
|
||||
*/
|
||||
public static long getTimeout(String service, String token) {
|
||||
return SaManager.getSaTemp().getTimeout(service, token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 Token
|
||||
* @param token 指定 Token
|
||||
*/
|
||||
public static void deleteToken(String token) {
|
||||
SaManager.getSaTemp().deleteToken(token);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除一个 Token
|
||||
* @param service 服务标识
|
||||
* @param token 指定 Token
|
||||
*/
|
||||
public static void deleteToken(String service, String token) {
|
||||
SaManager.getSaTemp().deleteToken(service, token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,8 +22,8 @@ import cn.dev33.satoken.error.SaErrorCode;
|
||||
import cn.dev33.satoken.exception.SaTokenException;
|
||||
|
||||
/**
|
||||
* Sa-Token 内部工具类
|
||||
*
|
||||
* Sa-Token 内部工具类
|
||||
*
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@@ -31,20 +31,20 @@ public class SaFoxUtil {
|
||||
|
||||
private SaFoxUtil() {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 打印 Sa-Token 版本字符画
|
||||
*/
|
||||
public static void printSaToken() {
|
||||
String str = ""
|
||||
+ "____ ____ ___ ____ _ _ ____ _ _ \r\n"
|
||||
+ "____ ____ ___ ____ _ _ ____ _ _ \r\n"
|
||||
+ "[__ |__| __ | | | |_/ |___ |\\ | \r\n"
|
||||
+ "___] | | | |__| | \\_ |___ | \\| "
|
||||
// + SaTokenConsts.VERSION_NO
|
||||
// + "sa-token:"
|
||||
+ "___] | | | |__| | \\_ |___ | \\| "
|
||||
// + SaTokenConsts.VERSION_NO
|
||||
// + "sa-token:"
|
||||
// + "\r\n" + "DevDoc:" + SaTokenConsts.DEV_DOC_URL // + "\r\n";
|
||||
+ "\r\n" + SaTokenConsts.DEV_DOC_URL // + "\r\n";
|
||||
+ " (" + SaTokenConsts.VERSION_NO + ")"
|
||||
+ " (" + SaTokenConsts.VERSION_NO + ")"
|
||||
// + "\r\n" + "GitHub:" + SaTokenConsts.GITHUB_URL // + "\r\n";
|
||||
;
|
||||
System.out.println(str);
|
||||
@@ -52,13 +52,13 @@ public class SaFoxUtil {
|
||||
|
||||
/**
|
||||
* 生成指定长度的随机字符串
|
||||
*
|
||||
*
|
||||
* @param length 字符串的长度
|
||||
* @return 一个随机字符串
|
||||
*/
|
||||
public static String getRandomString(int length) {
|
||||
String str = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||
StringBuffer sb = new StringBuffer();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < length; i++) {
|
||||
int number = ThreadLocalRandom.current().nextInt(62);
|
||||
sb.append(str.charAt(number));
|
||||
@@ -68,7 +68,7 @@ public class SaFoxUtil {
|
||||
|
||||
/**
|
||||
* 指定元素是否为null或者空字符串
|
||||
* @param str 指定元素
|
||||
* @param str 指定元素
|
||||
* @return 是否为null或者空字符串
|
||||
*/
|
||||
public static boolean isEmpty(Object str) {
|
||||
@@ -77,7 +77,7 @@ public class SaFoxUtil {
|
||||
|
||||
/**
|
||||
* 指定元素是否不为 (null或者空字符串)
|
||||
* @param str 指定元素
|
||||
* @param str 指定元素
|
||||
* @return 是否为null或者空字符串
|
||||
*/
|
||||
public static boolean isNotEmpty(Object str) {
|
||||
@@ -95,18 +95,27 @@ public class SaFoxUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个对象是否相等
|
||||
* @param a 第一个对象
|
||||
* @param b 第二个对象
|
||||
* @return 两个对象是否相等
|
||||
* 比较两个对象是否相等
|
||||
* @param a 第一个对象
|
||||
* @param b 第二个对象
|
||||
* @return 两个对象是否相等
|
||||
*/
|
||||
public static boolean equals(Object a, Object b) {
|
||||
return (a == b) || (a != null && a.equals(b));
|
||||
}
|
||||
|
||||
/**
|
||||
* 比较两个对象是否不相等
|
||||
* @param a 第一个对象
|
||||
* @param b 第二个对象
|
||||
* @return 两个对象是否不相等
|
||||
*/
|
||||
public static boolean notEquals(Object a, Object b) {
|
||||
return !equals(a, b);
|
||||
}
|
||||
/**
|
||||
* 以当前时间戳和随机int数字拼接一个随机字符串
|
||||
*
|
||||
*
|
||||
* @return 随机字符串
|
||||
*/
|
||||
public static String getMarking28() {
|
||||
@@ -116,7 +125,7 @@ public class SaFoxUtil {
|
||||
/**
|
||||
* 将日期格式化 (yyyy-MM-dd HH:mm:ss)
|
||||
* @param date 日期
|
||||
* @return 格式化后的时间
|
||||
* @return 格式化后的时间
|
||||
*/
|
||||
public static String formatDate(Date date){
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
|
||||
@@ -141,17 +150,17 @@ public class SaFoxUtil {
|
||||
ZonedDateTime zonedDateTime = ZonedDateTime.ofInstant(instant, ZoneId.systemDefault());
|
||||
return formatDate(zonedDateTime);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从集合里查询数据
|
||||
*
|
||||
*
|
||||
* @param dataList 数据集合
|
||||
* @param prefix 前缀
|
||||
* @param keyword 关键字
|
||||
* @param start 起始位置 (-1代表查询所有)
|
||||
* @param size 获取条数
|
||||
* @param sortType 排序类型(true=正序,false=反序)
|
||||
*
|
||||
*
|
||||
* @return 符合条件的新数据集合
|
||||
*/
|
||||
public static List<String> searchList(Collection<String> dataList, String prefix, String keyword, int start, int size, boolean sortType) {
|
||||
@@ -176,34 +185,34 @@ public class SaFoxUtil {
|
||||
|
||||
/**
|
||||
* 从集合里查询数据
|
||||
*
|
||||
*
|
||||
* @param list 数据集合
|
||||
* @param start 起始位置
|
||||
* @param size 获取条数 (-1代表从start处一直取到末尾)
|
||||
* @param size 获取条数 (-1代表从start处一直取到末尾)
|
||||
* @param sortType 排序类型(true=正序,false=反序)
|
||||
*
|
||||
*
|
||||
* @return 符合条件的新数据集合
|
||||
*/
|
||||
public static List<String> searchList(List<String> list, int start, int size, boolean sortType) {
|
||||
// 如果是反序的话
|
||||
// 如果是反序的话
|
||||
if(sortType == false) {
|
||||
Collections.reverse(list);
|
||||
}
|
||||
// start 至少为0
|
||||
// start 至少为0
|
||||
if (start < 0) {
|
||||
start = 0;
|
||||
}
|
||||
// size为-1时,代表一直取到末尾,否则取到 start + size
|
||||
// size为-1时,代表一直取到末尾,否则取到 start + size
|
||||
int end;
|
||||
if(size == -1) {
|
||||
end = list.size();
|
||||
} else {
|
||||
end = start + size;
|
||||
}
|
||||
// 取出的数据放到新集合中
|
||||
// 取出的数据放到新集合中
|
||||
List<String> list2 = new ArrayList<String>();
|
||||
for (int i = start; i < end; i++) {
|
||||
// 如果已经取到list的末尾,则直接退出
|
||||
// 如果已经取到list的末尾,则直接退出
|
||||
if (i >= list.size()) {
|
||||
return list2;
|
||||
}
|
||||
@@ -211,62 +220,62 @@ public class SaFoxUtil {
|
||||
}
|
||||
return list2;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 字符串模糊匹配
|
||||
* <p>example:
|
||||
* <p> user* user-add -- true
|
||||
* <p> user* art-add -- false
|
||||
* @param patt 表达式
|
||||
* @param str 待匹配的字符串
|
||||
* @return 是否可以匹配
|
||||
* <p> user* user-add -- true
|
||||
* <p> user* art-add -- false
|
||||
* @param patt 表达式
|
||||
* @param str 待匹配的字符串
|
||||
* @return 是否可以匹配
|
||||
*/
|
||||
public static boolean vagueMatch(String patt, String str) {
|
||||
// 两者均为 null 时,直接返回 true
|
||||
// 两者均为 null 时,直接返回 true
|
||||
if(patt == null && str == null) {
|
||||
return true;
|
||||
}
|
||||
// 两者其一为 null 时,直接返回 false
|
||||
// 两者其一为 null 时,直接返回 false
|
||||
if(patt == null || str == null) {
|
||||
return false;
|
||||
}
|
||||
// 如果表达式不带有*号,则只需简单equals即可 (这样可以使速度提升200倍左右)
|
||||
// 如果表达式不带有*号,则只需简单equals即可 (这样可以使速度提升200倍左右)
|
||||
if(patt.indexOf("*") == -1) {
|
||||
return patt.equals(str);
|
||||
}
|
||||
// 正则匹配
|
||||
// 正则匹配
|
||||
return Pattern.matches(patt.replaceAll("\\*", ".*"), str);
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断类型是否为8大包装类型
|
||||
* @param cs /
|
||||
* @return /
|
||||
* 判断类型是否为8大包装类型
|
||||
* @param cs /
|
||||
* @return /
|
||||
*/
|
||||
public static boolean isWrapperType(Class<?> cs) {
|
||||
return cs == Integer.class || cs == Short.class || cs == Long.class || cs == Byte.class
|
||||
|| cs == Float.class || cs == Double.class || cs == Boolean.class || cs == Character.class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 判断类型是否为基础类型:8大基本数据类型、8大包装类、String
|
||||
* @param cs /
|
||||
* @return /
|
||||
* 判断类型是否为基础类型:8大基本数据类型、8大包装类、String
|
||||
* @param cs /
|
||||
* @return /
|
||||
*/
|
||||
public static boolean isBasicType(Class<?> cs) {
|
||||
return cs.isPrimitive() || isWrapperType(cs) || cs == String.class;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将指定值转化为指定类型
|
||||
* @param <T> 泛型
|
||||
* @param obj 值
|
||||
* @param cs 类型
|
||||
* @return 转换后的值
|
||||
* @return 转换后的值
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T getValueByType(Object obj, Class<T> cs) {
|
||||
// 如果 obj 为 null 或者本来就是 cs 类型
|
||||
// 如果 obj 为 null 或者本来就是 cs 类型
|
||||
if(obj == null || obj.getClass().equals(cs)) {
|
||||
return (T)obj;
|
||||
}
|
||||
@@ -296,15 +305,15 @@ public class SaFoxUtil {
|
||||
}
|
||||
return (T)obj3;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 在url上拼接上kv参数并返回
|
||||
* 在url上拼接上kv参数并返回
|
||||
* @param url url
|
||||
* @param parameStr 参数, 例如 id=1001
|
||||
* @return 拼接后的url字符串
|
||||
* @return 拼接后的url字符串
|
||||
*/
|
||||
public static String joinParam(String url, String parameStr) {
|
||||
// 如果参数为空, 直接返回
|
||||
// 如果参数为空, 直接返回
|
||||
if(parameStr == null || parameStr.length() == 0) {
|
||||
return url;
|
||||
}
|
||||
@@ -330,19 +339,19 @@ public class SaFoxUtil {
|
||||
return url + parameStr;
|
||||
}
|
||||
}
|
||||
// 正常情况下, 代码不可能执行到此
|
||||
// 正常情况下, 代码不可能执行到此
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在url上拼接上kv参数并返回
|
||||
* 在url上拼接上kv参数并返回
|
||||
* @param url url
|
||||
* @param key 参数名称
|
||||
* @param value 参数值
|
||||
* @return 拼接后的url字符串
|
||||
* @param value 参数值
|
||||
* @return 拼接后的url字符串
|
||||
*/
|
||||
public static String joinParam(String url, String key, Object value) {
|
||||
// 如果url或者key为空, 直接返回
|
||||
// 如果url或者key为空, 直接返回
|
||||
if(isEmpty(url) || isEmpty(key)) {
|
||||
return url;
|
||||
}
|
||||
@@ -350,13 +359,13 @@ public class SaFoxUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 在url上拼接锚参数
|
||||
* 在url上拼接锚参数
|
||||
* @param url url
|
||||
* @param parameStr 参数, 例如 id=1001
|
||||
* @return 拼接后的url字符串
|
||||
* @return 拼接后的url字符串
|
||||
*/
|
||||
public static String joinSharpParam(String url, String parameStr) {
|
||||
// 如果参数为空, 直接返回
|
||||
// 如果参数为空, 直接返回
|
||||
if(parameStr == null || parameStr.length() == 0) {
|
||||
return url;
|
||||
}
|
||||
@@ -382,19 +391,19 @@ public class SaFoxUtil {
|
||||
return url + parameStr;
|
||||
}
|
||||
}
|
||||
// 正常情况下, 代码不可能执行到此
|
||||
// 正常情况下, 代码不可能执行到此
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在url上拼接锚参数
|
||||
* 在url上拼接锚参数
|
||||
* @param url url
|
||||
* @param key 参数名称
|
||||
* @param value 参数值
|
||||
* @return 拼接后的url字符串
|
||||
* @param value 参数值
|
||||
* @return 拼接后的url字符串
|
||||
*/
|
||||
public static String joinSharpParam(String url, String key, Object value) {
|
||||
// 如果url或者key为空, 直接返回
|
||||
// 如果url或者key为空, 直接返回
|
||||
if(isEmpty(url) || isEmpty(key)) {
|
||||
return url;
|
||||
}
|
||||
@@ -402,31 +411,31 @@ public class SaFoxUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 拼接两个url
|
||||
* 拼接两个url
|
||||
* <p> 例如:url1=http://domain.cn,url2=/sso/auth,则返回:http://domain.cn/sso/auth </p>
|
||||
*
|
||||
* @param url1 第一个url
|
||||
* @param url2 第二个url
|
||||
* @return 拼接完成的url
|
||||
*
|
||||
* @param url1 第一个url
|
||||
* @param url2 第二个url
|
||||
* @return 拼接完成的url
|
||||
*/
|
||||
public static String spliceTwoUrl(String url1, String url2) {
|
||||
// q1、任意一个为空,则直接返回另一个
|
||||
// q1、任意一个为空,则直接返回另一个
|
||||
if(url1 == null) {
|
||||
return url2;
|
||||
}
|
||||
if(url2 == null) {
|
||||
return url1;
|
||||
}
|
||||
|
||||
// q2、如果 url2 以 http 开头,将其视为一个完整地址
|
||||
|
||||
// q2、如果 url2 以 http 开头,将其视为一个完整地址
|
||||
if(url2.startsWith("http")) {
|
||||
return url2;
|
||||
}
|
||||
|
||||
// q3、将两个地址拼接在一起
|
||||
|
||||
// q3、将两个地址拼接在一起
|
||||
return url1 + url2;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将数组的所有元素使用逗号拼接在一起
|
||||
* @param arr 数组
|
||||
@@ -445,16 +454,16 @@ public class SaFoxUtil {
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 验证URL的正则表达式
|
||||
* 验证URL的正则表达式
|
||||
*/
|
||||
public static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
|
||||
|
||||
public static final String URL_REGEX = "(https?|ftp|file)://[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]";
|
||||
|
||||
/**
|
||||
* 使用正则表达式判断一个字符串是否为URL
|
||||
* @param str 字符串
|
||||
* @return 拼接后的url字符串
|
||||
* @param str 字符串
|
||||
* @return 拼接后的url字符串
|
||||
*/
|
||||
public static boolean isUrl(String str) {
|
||||
if(isEmpty(str)) {
|
||||
@@ -462,11 +471,11 @@ public class SaFoxUtil {
|
||||
}
|
||||
return str.toLowerCase().matches(URL_REGEX);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* URL编码
|
||||
* @param url see note
|
||||
* @return see note
|
||||
* URL编码
|
||||
* @param url see note
|
||||
* @return see note
|
||||
*/
|
||||
public static String encodeUrl(String url) {
|
||||
try {
|
||||
@@ -477,9 +486,9 @@ public class SaFoxUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* URL解码
|
||||
* @param url see note
|
||||
* @return see note
|
||||
* URL解码
|
||||
* @param url see note
|
||||
* @return see note
|
||||
*/
|
||||
public static String decoderUrl(String url) {
|
||||
try {
|
||||
@@ -488,11 +497,11 @@ public class SaFoxUtil {
|
||||
throw new SaTokenException(e).setCode(SaErrorCode.CODE_12104);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将指定字符串按照逗号分隔符转化为字符串集合
|
||||
* 将指定字符串按照逗号分隔符转化为字符串集合
|
||||
* @param str 字符串
|
||||
* @return 分割后的字符串集合
|
||||
* @return 分割后的字符串集合
|
||||
*/
|
||||
public static List<String> convertStringToList(String str) {
|
||||
List<String> list = new ArrayList<String>();
|
||||
@@ -510,9 +519,9 @@ public class SaFoxUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 将指定集合按照逗号连接成一个字符串
|
||||
* @param list 集合
|
||||
* @return 字符串
|
||||
* 将指定集合按照逗号连接成一个字符串
|
||||
* @param list 集合
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String convertListToString(List<?> list) {
|
||||
if(list == null || list.size() == 0) {
|
||||
@@ -527,11 +536,11 @@ public class SaFoxUtil {
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* String 转 Array,按照逗号切割
|
||||
* @param str 字符串
|
||||
* @return 数组
|
||||
* String 转 Array,按照逗号切割
|
||||
* @param str 字符串
|
||||
* @return 数组
|
||||
*/
|
||||
public static String[] convertStringToArray(String str) {
|
||||
List<String> list = convertStringToList(str);
|
||||
@@ -539,9 +548,9 @@ public class SaFoxUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* Array 转 String,按照逗号连接
|
||||
* @param arr 数组
|
||||
* @return 字符串
|
||||
* Array 转 String,按照逗号连接
|
||||
* @param arr 数组
|
||||
* @return 字符串
|
||||
*/
|
||||
public static String convertArrayToString(String[] arr) {
|
||||
if(arr == null || arr.length == 0) {
|
||||
@@ -549,20 +558,20 @@ public class SaFoxUtil {
|
||||
}
|
||||
return String.join(",", arr);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回一个空集合
|
||||
* @param <T> 集合类型
|
||||
* @return 空集合
|
||||
* 返回一个空集合
|
||||
* @param <T> 集合类型
|
||||
* @return 空集合
|
||||
*/
|
||||
public static <T>List<T> emptyList() {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* String数组转集合
|
||||
* @param strs String数组
|
||||
* @return 集合
|
||||
* String数组转集合
|
||||
* @param strs String数组
|
||||
* @return 集合
|
||||
*/
|
||||
public static List<String> toList(String... strs) {
|
||||
List<String> list = new ArrayList<>();
|
||||
@@ -571,9 +580,9 @@ public class SaFoxUtil {
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
|
||||
public static List<String> logLevelList = Arrays.asList("", "trace", "debug", "info", "warn", "error", "fatal");
|
||||
|
||||
|
||||
/**
|
||||
* 将日志等级从 String 格式转化为 int 格式
|
||||
* @param level /
|
||||
@@ -598,5 +607,5 @@ public class SaFoxUtil {
|
||||
}
|
||||
return logLevelList.get(level);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ public class SaTokenConsts {
|
||||
/**
|
||||
* Sa-Token 当前版本号
|
||||
*/
|
||||
public static final String VERSION_NO = "v1.33.0";
|
||||
public static final String VERSION_NO = "v1.34.0";
|
||||
|
||||
/**
|
||||
* Sa-Token 开源地址 Gitee
|
||||
@@ -95,6 +95,11 @@ public class SaTokenConsts {
|
||||
*/
|
||||
public static final String DEFAULT_SAFE_AUTH_SERVICE = "important";
|
||||
|
||||
/**
|
||||
* 常量key标记: 临时 Token 认证模块,默认的业务类型
|
||||
*/
|
||||
public static final String DEFAULT_TEMP_TOKEN_SERVICE = "record";
|
||||
|
||||
|
||||
// =================== token-style 相关 ===================
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.idea/
|
||||
|
||||
.factorypath
|
||||
@@ -0,0 +1,71 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-alone-redis-cluster</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合Redis (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token插件:权限缓存与业务缓存分离 -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-alone-redis</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.pj;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
|
||||
/**
|
||||
* Sa-Token整合SpringBoot 示例
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenAloneRedisClusterApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenAloneRedisClusterApplication.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.pj.test;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
|
||||
/**
|
||||
* 测试专用Controller
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
@Autowired
|
||||
StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
// 测试Sa-Token缓存, 浏览器访问: http://localhost:8081/test/login
|
||||
@RequestMapping("login")
|
||||
public AjaxJson login(@RequestParam(defaultValue="10001") String id) {
|
||||
System.out.println("--------------- 测试Sa-Token缓存");
|
||||
StpUtil.login(id);
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
// 测试业务缓存 浏览器访问: http://localhost:8081/test/test
|
||||
@RequestMapping("test")
|
||||
public AjaxJson test() {
|
||||
System.out.println("--------------- 测试业务缓存");
|
||||
stringRedisTemplate.opsForValue().set("hello", "Hello World");
|
||||
return AjaxJson.getSuccess();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
# Sa-Token配置
|
||||
sa-token:
|
||||
# Token名称 (同时也是cookie名称)
|
||||
token-name: satoken
|
||||
# Token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# Token风格
|
||||
token-style: uuid
|
||||
# 配置Sa-Token单独使用的Redis连接
|
||||
alone-redis:
|
||||
# 普通集群
|
||||
pattern: cluster
|
||||
# Redis服务器连接用户名(默认为空)
|
||||
username:
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10s
|
||||
cluster:
|
||||
# Redis集群服务器节点地址
|
||||
nodes: 127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002
|
||||
# 最大重定向次数
|
||||
maxRedirects: 2
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
spring:
|
||||
# 配置业务使用的Redis连接
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 0
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
# Sa-Token配置
|
||||
sa-token:
|
||||
# Token名称 (同时也是cookie名称)
|
||||
token-name: satoken
|
||||
# Token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# Token风格
|
||||
token-style: uuid
|
||||
# 配置Sa-Token单独使用的Redis连接
|
||||
alone-redis:
|
||||
# 哨兵模式
|
||||
pattern: sentinel
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 2
|
||||
# Redis服务器连接用户名(默认为空)
|
||||
username:
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10s
|
||||
sentinel:
|
||||
#哨兵的名字
|
||||
master: master_name
|
||||
# Redis集群服务器节点地址
|
||||
nodes: 127.0.0.1:3000,127.0.0.1:3001,127.0.0.1:3002
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
spring:
|
||||
# 配置业务使用的Redis连接
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 0
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间(毫秒)
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -11,7 +11,9 @@ sa-token:
|
||||
# Token风格
|
||||
token-style: uuid
|
||||
# 配置Sa-Token单独使用的Redis连接
|
||||
alone-redis:
|
||||
alone-redis:
|
||||
# Redis模式(默认单体)
|
||||
# pattern: single
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 2
|
||||
# Redis服务器地址
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<!-- <version>1.5.9.RELEASE</version> -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,14 +9,15 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.3.RELEASE</version>
|
||||
<!--<version>2.3.3.RELEASE</version>-->
|
||||
<version>2.5.14</version>
|
||||
</parent>
|
||||
|
||||
<!-- 指定一些属性 -->
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,14 +9,15 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.3.RELEASE</version>
|
||||
<!--<version>2.3.3.RELEASE</version>-->
|
||||
<version>2.5.14</version>
|
||||
</parent>
|
||||
|
||||
<!-- 指定一些属性 -->
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<lombok.version>1.18.10</lombok.version>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.3.RELEASE</version>
|
||||
<version>2.5.14</version>
|
||||
</parent>
|
||||
|
||||
<!-- 指定一些属性 -->
|
||||
@@ -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.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.3.3.RELEASE</version>
|
||||
<version>2.5.14</version>
|
||||
</parent>
|
||||
|
||||
<!-- 指定一些属性 -->
|
||||
@@ -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.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ public class SaOAuth2TemplateImpl extends SaOAuth2Template {
|
||||
// 此为模拟数据,真实环境需要从数据库查询
|
||||
if("1001".equals(clientId)) {
|
||||
return new SaClientModel()
|
||||
.setClientId("10001")
|
||||
.setClientId("1001")
|
||||
.setClientSecret("aaaa-bbbb-cccc-dddd-eeee")
|
||||
.setAllowUrl("*")
|
||||
.setContractScope("userinfo")
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<!-- <version>2.6.0</version> -->
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
<solon.version>1.10.13</solon.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,14 +10,14 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<!-- <version>1.5.9.RELEASE</version> -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -17,13 +17,13 @@ sa-token:
|
||||
# token风格
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: false
|
||||
is-log: true
|
||||
|
||||
spring:
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 0
|
||||
database: 1
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.idea/
|
||||
|
||||
.factorypath
|
||||
@@ -0,0 +1,65 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-springboot-redisson</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.14</version>
|
||||
<!-- <version>1.5.9.RELEASE</version> -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token整合 Redisson (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redisson-jackson</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 无需提供Redis连接池 Redisson使用Netty管理 -->
|
||||
<!-- <dependency>-->
|
||||
<!-- <groupId>org.apache.commons</groupId>-->
|
||||
<!-- <artifactId>commons-pool2</artifactId>-->
|
||||
<!-- </dependency>-->
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.pj;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
|
||||
/**
|
||||
* Sa-Token整合SpringBoot 示例,整合redis
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenDemoApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenDemoApplication.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.pj.current;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.exception.DisableServiceException;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
// 打印堆栈,以供调试
|
||||
System.out.println("全局异常---------------");
|
||||
e.printStackTrace();
|
||||
|
||||
// 不同异常返回不同状态码
|
||||
AjaxJson aj = null;
|
||||
if (e instanceof NotLoginException) { // 如果是未登录异常
|
||||
NotLoginException ee = (NotLoginException) e;
|
||||
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
|
||||
}
|
||||
else if(e instanceof NotRoleException) { // 如果是角色异常
|
||||
NotRoleException ee = (NotRoleException) e;
|
||||
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
|
||||
}
|
||||
else if(e instanceof NotPermissionException) { // 如果是权限异常
|
||||
NotPermissionException ee = (NotPermissionException) e;
|
||||
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
|
||||
}
|
||||
else if(e instanceof DisableServiceException) { // 如果是被封禁异常
|
||||
DisableServiceException ee = (DisableServiceException) e;
|
||||
aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + "):" + ee.getDisableTime() + "秒后解封");
|
||||
}
|
||||
else { // 普通异常, 输出:500 + 异常信息
|
||||
aj = AjaxJson.getError(e.getMessage());
|
||||
}
|
||||
|
||||
// 返回给前端
|
||||
return aj;
|
||||
}
|
||||
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.pj.current;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 处理 404
|
||||
* @author kong
|
||||
*/
|
||||
@RestController
|
||||
public class NotFoundHandle implements ErrorController {
|
||||
|
||||
@RequestMapping("/error")
|
||||
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
response.setStatus(200);
|
||||
return SaResult.get(404, "not found", null);
|
||||
}
|
||||
|
||||
}
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
package com.pj.redisson;
|
||||
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* redisson 配置
|
||||
*
|
||||
* @author 疯狂的狮子Li
|
||||
*/
|
||||
@Configuration
|
||||
@EnableConfigurationProperties(RedissonProperties.class)
|
||||
public class RedissonConfig {
|
||||
|
||||
@Autowired
|
||||
private RedissonProperties redissonProperties;
|
||||
|
||||
/**
|
||||
* 自定义Redisson配置注入器 被RedissonAutoConfiguration调用执行
|
||||
* 具体参考 {@link org.redisson.spring.starter.RedissonAutoConfiguration}
|
||||
* <p/>
|
||||
* 使用自定义配置类手动注入配置数据
|
||||
* 也可根据redisson官网使用properties文件配置
|
||||
*/
|
||||
@Bean
|
||||
public RedissonAutoConfigurationCustomizer redissonCustomizer() {
|
||||
return config -> {
|
||||
config.setThreads(redissonProperties.getThreads());
|
||||
config.setNettyThreads(redissonProperties.getNettyThreads());
|
||||
SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
|
||||
if (singleServerConfig != null) {
|
||||
// 使用单机模式
|
||||
config.useSingleServer()
|
||||
.setTimeout(singleServerConfig.getTimeout())
|
||||
.setClientName(singleServerConfig.getClientName())
|
||||
.setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
|
||||
.setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
|
||||
.setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
|
||||
.setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.pj.redisson;
|
||||
|
||||
import org.redisson.config.SingleServerConfig;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Redisson 配置属性
|
||||
*
|
||||
* @author 疯狂的狮子Li
|
||||
*/
|
||||
@Component
|
||||
@ConfigurationProperties(prefix = "redisson")
|
||||
public class RedissonProperties {
|
||||
|
||||
/**
|
||||
* 线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int threads;
|
||||
|
||||
/**
|
||||
* Netty线程池数量,默认值 = 当前处理核数量 * 2
|
||||
*/
|
||||
private int nettyThreads;
|
||||
|
||||
/**
|
||||
* 单机服务配置
|
||||
*/
|
||||
@NestedConfigurationProperty
|
||||
private SingleServerConfig singleServerConfig;
|
||||
|
||||
public int getThreads() {
|
||||
return threads;
|
||||
}
|
||||
|
||||
public void setThreads(int threads) {
|
||||
this.threads = threads;
|
||||
}
|
||||
|
||||
public int getNettyThreads() {
|
||||
return nettyThreads;
|
||||
}
|
||||
|
||||
public void setNettyThreads(int nettyThreads) {
|
||||
this.nettyThreads = nettyThreads;
|
||||
}
|
||||
|
||||
public SingleServerConfig getSingleServerConfig() {
|
||||
return singleServerConfig;
|
||||
}
|
||||
|
||||
public void setSingleServerConfig(SingleServerConfig singleServerConfig) {
|
||||
this.singleServerConfig = singleServerConfig;
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**")// .addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
|
||||
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行
|
||||
.setBeforeAuth(r -> {
|
||||
// ---------- 设置一些安全响应头 ----------
|
||||
SaHolder.getResponse()
|
||||
// 服务器名称
|
||||
.setServer("sa-server")
|
||||
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
|
||||
.setHeader("X-Frame-Options", "SAMEORIGIN")
|
||||
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
|
||||
.setHeader("X-XSS-Protection", "1; mode=block")
|
||||
// 禁用浏览器内容嗅探
|
||||
.setHeader("X-Content-Type-Options", "nosniff")
|
||||
;
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
|
||||
/**
|
||||
* 自定义权限验证接口扩展
|
||||
*/
|
||||
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
|
||||
public class StpInterfaceImpl implements StpInterface {
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的权限码集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("101");
|
||||
list.add("user-add");
|
||||
list.add("user-delete");
|
||||
list.add("user-update");
|
||||
list.add("user-get");
|
||||
list.add("article-get");
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的角色标识集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("admin");
|
||||
list.add("super-admin");
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckBasic;
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.dev33.satoken.annotation.SaCheckSafe;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 注解鉴权测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/at/")
|
||||
public class AtController {
|
||||
|
||||
// 登录认证,登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin
|
||||
@SaCheckLogin
|
||||
@RequestMapping("checkLogin")
|
||||
public SaResult checkLogin() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission
|
||||
@SaCheckPermission("user-add")
|
||||
@RequestMapping("checkPermission")
|
||||
public SaResult checkPermission() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd
|
||||
@SaCheckPermission({"user-add", "user-delete", "user-update"})
|
||||
@RequestMapping("checkPermissionAnd")
|
||||
public SaResult checkPermissionAnd() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr
|
||||
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
|
||||
@RequestMapping("checkPermissionOr")
|
||||
public SaResult checkPermissionOr() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole
|
||||
@SaCheckRole("admin")
|
||||
@RequestMapping("checkRole")
|
||||
public SaResult checkRole() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 完成二级认证 ---- http://localhost:8081/at/openSafe
|
||||
@RequestMapping("openSafe")
|
||||
public SaResult openSafe() {
|
||||
StpUtil.openSafe(200); // 打开二级认证,有效期为200秒
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe
|
||||
@SaCheckSafe
|
||||
@RequestMapping("checkSafe")
|
||||
public SaResult checkSafe() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
|
||||
@SaCheckBasic(account = "sa:123456")
|
||||
@RequestMapping("checkBasic")
|
||||
public SaResult checkBasic() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/acc/")
|
||||
public class LoginController {
|
||||
|
||||
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
|
||||
@RequestMapping("doLogin")
|
||||
public SaResult doLogin(String name, String pwd) {
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if("zhang".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功");
|
||||
}
|
||||
return SaResult.error("登录失败");
|
||||
}
|
||||
|
||||
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
return SaResult.ok("是否登录:" + StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
|
||||
@RequestMapping("tokenInfo")
|
||||
public SaResult tokenInfo() {
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
}
|
||||
|
||||
// 测试注销 ---- http://localhost:8081/acc/logout
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.pj.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.pj.util.Ttime;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 压力测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/s-test/")
|
||||
public class StressTestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/s-test/login
|
||||
// 测试前,请先将 is-read-cookie 配置为 false
|
||||
@RequestMapping("login")
|
||||
public SaResult login() {
|
||||
// StpUtil.getTokenSession().logout();
|
||||
// StpUtil.logoutByLoginId(10001);
|
||||
|
||||
int count = 10; // 循环多少轮
|
||||
int loginCount = 10000; // 每轮循环多少次
|
||||
|
||||
// 循环10次 取平均时间
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (int i = 1; i <= count; i++) {
|
||||
System.out.println("\n---------------------第" + i + "轮---------------------");
|
||||
Ttime t = new Ttime().start();
|
||||
// 每次登录的次数
|
||||
for (int j = 1; j <= loginCount; j++) {
|
||||
StpUtil.login("1000" + j, "PC-" + j);
|
||||
if(j % 1000 == 0) {
|
||||
System.out.println("已登录:" + j);
|
||||
}
|
||||
}
|
||||
t.end();
|
||||
list.add((t.returnMs() + 0.0) / 1000);
|
||||
System.out.println("第" + i + "轮" + "用时:" + t.toString());
|
||||
}
|
||||
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
|
||||
|
||||
System.out.println("\n---------------------测试结果---------------------");
|
||||
System.out.println(list.size() + "次测试: " + list);
|
||||
double ss = 0;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ss += list.get(i);
|
||||
}
|
||||
System.out.println("平均用时: " + ss / list.size());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 测试专用Controller
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test
|
||||
@RequestMapping("test")
|
||||
public SaResult test() {
|
||||
System.out.println("------------进来了");
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test2
|
||||
@RequestMapping("test2")
|
||||
public SaResult test2() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.pj.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.pj.util;
|
||||
|
||||
|
||||
/**
|
||||
* 用于测试用时
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class Ttime {
|
||||
|
||||
private long start=0; //开始时间
|
||||
private long end=0; //结束时间
|
||||
|
||||
public static Ttime t = new Ttime(); //static快捷使用
|
||||
|
||||
/**
|
||||
* 开始计时
|
||||
* @return
|
||||
*/
|
||||
public Ttime start() {
|
||||
start=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 结束计时
|
||||
*/
|
||||
public Ttime end() {
|
||||
end=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回所用毫秒数
|
||||
*/
|
||||
public long returnMs() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出结果
|
||||
*/
|
||||
public void outTime() {
|
||||
System.out.println(this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束并格式化输出结果
|
||||
*/
|
||||
public void endOutTime() {
|
||||
this.end().outTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
# sa-token配置
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: satoken
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
activity-timeout: -1
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: true
|
||||
# token风格
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
spring:
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 1
|
||||
# Redis服务器地址
|
||||
host: localhost
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码 为空需注释掉
|
||||
# password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
|
||||
redisson:
|
||||
# 线程池数量
|
||||
threads: 8
|
||||
# Netty线程池数量
|
||||
nettyThreads: 32
|
||||
# 单节点配置
|
||||
singleServerConfig:
|
||||
# 客户端名称
|
||||
clientName: test
|
||||
# 最小空闲连接数
|
||||
connectionMinimumIdleSize: 8
|
||||
# 连接池大小
|
||||
connectionPoolSize: 32
|
||||
# 连接空闲超时,单位:毫秒
|
||||
idleConnectionTimeout: 10000
|
||||
# 命令等待超时,单位:毫秒
|
||||
timeout: 3000
|
||||
# 如果尝试在此限制之内发送成功,则开始启用 timeout 计时。
|
||||
retryAttempts: 3
|
||||
# 命令重试发送时间间隔,单位:毫秒
|
||||
retryInterval: 1500
|
||||
# 发布和订阅连接池大小
|
||||
subscriptionConnectionPoolSize: 50
|
||||
@@ -10,14 +10,14 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<!-- <version>1.5.9.RELEASE</version> -->
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.idea/
|
||||
|
||||
.factorypath
|
||||
@@ -0,0 +1,71 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-springboot3-redis</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-aop</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-spring-boot3-starter</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 整合 Redis (使用jdk默认序列化方式) -->
|
||||
<!-- <dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency> -->
|
||||
|
||||
<!-- Sa-Token整合 Redis (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redis-jackson</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 提供Redis连接池 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-pool2</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- @ConfigurationProperties -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
package com.pj;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
import cn.dev33.satoken.SaManager;
|
||||
|
||||
/**
|
||||
* Sa-Token 整合 SpringBoot3 示例,整合redis
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@SpringBootApplication
|
||||
public class SaTokenSpringBoot3Application {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(SaTokenSpringBoot3Application.class, args);
|
||||
System.out.println("\n启动成功:Sa-Token配置如下:" + SaManager.getConfig());
|
||||
}
|
||||
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
package com.pj.current;
|
||||
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
import com.pj.util.AjaxJson;
|
||||
|
||||
import cn.dev33.satoken.exception.DisableServiceException;
|
||||
import cn.dev33.satoken.exception.NotLoginException;
|
||||
import cn.dev33.satoken.exception.NotPermissionException;
|
||||
import cn.dev33.satoken.exception.NotRoleException;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
*/
|
||||
@RestControllerAdvice
|
||||
public class GlobalException {
|
||||
|
||||
// 全局异常拦截(拦截项目中的所有异常)
|
||||
@ExceptionHandler
|
||||
public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
// 打印堆栈,以供调试
|
||||
System.out.println("全局异常---------------");
|
||||
e.printStackTrace();
|
||||
|
||||
// 不同异常返回不同状态码
|
||||
AjaxJson aj = null;
|
||||
if (e instanceof NotLoginException) { // 如果是未登录异常
|
||||
NotLoginException ee = (NotLoginException) e;
|
||||
aj = AjaxJson.getNotLogin().setMsg(ee.getMessage());
|
||||
}
|
||||
else if(e instanceof NotRoleException) { // 如果是角色异常
|
||||
NotRoleException ee = (NotRoleException) e;
|
||||
aj = AjaxJson.getNotJur("无此角色:" + ee.getRole());
|
||||
}
|
||||
else if(e instanceof NotPermissionException) { // 如果是权限异常
|
||||
NotPermissionException ee = (NotPermissionException) e;
|
||||
aj = AjaxJson.getNotJur("无此权限:" + ee.getPermission());
|
||||
}
|
||||
else if(e instanceof DisableServiceException) { // 如果是被封禁异常
|
||||
DisableServiceException ee = (DisableServiceException) e;
|
||||
aj = AjaxJson.getNotJur("当前账号 " + ee.getService() + " 服务已被封禁 (level=" + ee.getLevel() + "):" + ee.getDisableTime() + "秒后解封");
|
||||
}
|
||||
else { // 普通异常, 输出:500 + 异常信息
|
||||
aj = AjaxJson.getError(e.getMessage());
|
||||
}
|
||||
|
||||
// 返回给前端
|
||||
return aj;
|
||||
}
|
||||
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
package com.pj.current;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.boot.web.servlet.error.ErrorController;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
|
||||
/**
|
||||
* 处理 404
|
||||
* @author kong
|
||||
*/
|
||||
@RestController
|
||||
public class NotFoundHandle implements ErrorController {
|
||||
|
||||
@RequestMapping("/error")
|
||||
public Object error(HttpServletRequest request, HttpServletResponse response) throws IOException {
|
||||
response.setStatus(200);
|
||||
return SaResult.get(404, "not found", null);
|
||||
}
|
||||
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
import cn.dev33.satoken.context.SaHolder;
|
||||
import cn.dev33.satoken.filter.SaServletFilter;
|
||||
import cn.dev33.satoken.interceptor.SaInterceptor;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
|
||||
/**
|
||||
* [Sa-Token 权限认证] 配置类
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Configuration
|
||||
public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
|
||||
/**
|
||||
* 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
*/
|
||||
@Override
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
// 注册 Sa-Token 拦截器打开注解鉴权功能
|
||||
registry.addInterceptor(new SaInterceptor()).addPathPatterns("/**");
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册 [Sa-Token 全局过滤器]
|
||||
*/
|
||||
@Bean
|
||||
public SaServletFilter getSaServletFilter() {
|
||||
return new SaServletFilter()
|
||||
|
||||
// 指定 [拦截路由] 与 [放行路由]
|
||||
.addInclude("/**")// .addExclude("/favicon.ico")
|
||||
|
||||
// 认证函数: 每次请求执行
|
||||
.setAuth(obj -> {
|
||||
// System.out.println("---------- sa全局认证 " + SaHolder.getRequest().getRequestPath());
|
||||
|
||||
})
|
||||
|
||||
// 异常处理函数:每次认证函数发生异常时执行此函数
|
||||
.setError(e -> {
|
||||
System.out.println("---------- sa全局异常 ");
|
||||
e.printStackTrace();
|
||||
return SaResult.error(e.getMessage());
|
||||
})
|
||||
|
||||
// 前置函数:在每次认证函数之前执行
|
||||
.setBeforeAuth(r -> {
|
||||
// ---------- 设置一些安全响应头 ----------
|
||||
SaHolder.getResponse()
|
||||
// 服务器名称
|
||||
.setServer("sa-server")
|
||||
// 是否可以在iframe显示视图: DENY=不可以 | SAMEORIGIN=同域下可以 | ALLOW-FROM uri=指定域名下可以
|
||||
.setHeader("X-Frame-Options", "SAMEORIGIN")
|
||||
// 是否启用浏览器默认XSS防护: 0=禁用 | 1=启用 | 1; mode=block 启用, 并在检查到XSS攻击时,停止渲染页面
|
||||
.setHeader("X-XSS-Protection", "1; mode=block")
|
||||
// 禁用浏览器内容嗅探
|
||||
.setHeader("X-Content-Type-Options", "nosniff")
|
||||
;
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
package com.pj.satoken;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import cn.dev33.satoken.stp.StpInterface;
|
||||
|
||||
/**
|
||||
* 自定义权限验证接口扩展
|
||||
*/
|
||||
@Component // 打开此注解,保证此类被springboot扫描,即可完成sa-token的自定义权限验证扩展
|
||||
public class StpInterfaceImpl implements StpInterface {
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的权限码集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getPermissionList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询权限
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("101");
|
||||
list.add("user-add");
|
||||
list.add("user-delete");
|
||||
list.add("user-update");
|
||||
list.add("user-get");
|
||||
list.add("article-get");
|
||||
return list;
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回一个账号所拥有的角色标识集合
|
||||
*/
|
||||
@Override
|
||||
public List<String> getRoleList(Object loginId, String loginType) {
|
||||
// 本list仅做模拟,实际项目中要根据具体业务逻辑来查询角色
|
||||
List<String> list = new ArrayList<String>();
|
||||
list.add("admin");
|
||||
list.add("super-admin");
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
+80
@@ -0,0 +1,80 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckBasic;
|
||||
import cn.dev33.satoken.annotation.SaCheckLogin;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.dev33.satoken.annotation.SaCheckRole;
|
||||
import cn.dev33.satoken.annotation.SaCheckSafe;
|
||||
import cn.dev33.satoken.annotation.SaMode;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 注解鉴权测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/at/")
|
||||
public class AtController {
|
||||
|
||||
// 登录认证,登录之后才可以进入方法 ---- http://localhost:8081/at/checkLogin
|
||||
@SaCheckLogin
|
||||
@RequestMapping("checkLogin")
|
||||
public SaResult checkLogin() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,具备user-add权限才可以进入方法 ---- http://localhost:8081/at/checkPermission
|
||||
@SaCheckPermission("user-add")
|
||||
@RequestMapping("checkPermission")
|
||||
public SaResult checkPermission() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,同时具备所有权限才可以进入 ---- http://localhost:8081/at/checkPermissionAnd
|
||||
@SaCheckPermission({"user-add", "user-delete", "user-update"})
|
||||
@RequestMapping("checkPermissionAnd")
|
||||
public SaResult checkPermissionAnd() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 权限认证,只要具备其中一个就可以进入 ---- http://localhost:8081/at/checkPermissionOr
|
||||
@SaCheckPermission(value = {"user-add", "user-delete", "user-update"}, mode = SaMode.OR)
|
||||
@RequestMapping("checkPermissionOr")
|
||||
public SaResult checkPermissionOr() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 角色认证,只有具备admin角色才可以进入 ---- http://localhost:8081/at/checkRole
|
||||
@SaCheckRole("admin")
|
||||
@RequestMapping("checkRole")
|
||||
public SaResult checkRole() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 完成二级认证 ---- http://localhost:8081/at/openSafe
|
||||
@RequestMapping("openSafe")
|
||||
public SaResult openSafe() {
|
||||
StpUtil.openSafe(200); // 打开二级认证,有效期为200秒
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过二级认证后才可以进入 ---- http://localhost:8081/at/checkSafe
|
||||
@SaCheckSafe
|
||||
@RequestMapping("checkSafe")
|
||||
public SaResult checkSafe() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 通过Basic认证后才可以进入 ---- http://localhost:8081/at/checkBasic
|
||||
@SaCheckBasic(account = "sa:123456")
|
||||
@RequestMapping("checkBasic")
|
||||
public SaResult checkBasic() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 登录测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/acc/")
|
||||
public class LoginController {
|
||||
|
||||
// 测试登录 ---- http://localhost:8081/acc/doLogin?name=zhang&pwd=123456
|
||||
@RequestMapping("doLogin")
|
||||
public SaResult doLogin(String name, String pwd) {
|
||||
// 此处仅作模拟示例,真实项目需要从数据库中查询数据进行比对
|
||||
if("zhang".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功");
|
||||
}
|
||||
return SaResult.error("登录失败");
|
||||
}
|
||||
|
||||
// 查询登录状态 ---- http://localhost:8081/acc/isLogin
|
||||
@RequestMapping("isLogin")
|
||||
public SaResult isLogin() {
|
||||
return SaResult.ok("是否登录:" + StpUtil.isLogin());
|
||||
}
|
||||
|
||||
// 查询 Token 信息 ---- http://localhost:8081/acc/tokenInfo
|
||||
@RequestMapping("tokenInfo")
|
||||
public SaResult tokenInfo() {
|
||||
return SaResult.data(StpUtil.getTokenInfo());
|
||||
}
|
||||
|
||||
// 测试注销 ---- http://localhost:8081/acc/logout
|
||||
@RequestMapping("logout")
|
||||
public SaResult logout() {
|
||||
StpUtil.logout();
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
package com.pj.test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import com.pj.util.Ttime;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 压力测试
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/s-test/")
|
||||
public class StressTestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/s-test/login
|
||||
// 测试前,请先将 is-read-cookie 配置为 false
|
||||
@RequestMapping("login")
|
||||
public SaResult login() {
|
||||
// StpUtil.getTokenSession().logout();
|
||||
// StpUtil.logoutByLoginId(10001);
|
||||
|
||||
int count = 10; // 循环多少轮
|
||||
int loginCount = 10000; // 每轮循环多少次
|
||||
|
||||
// 循环10次 取平均时间
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (int i = 1; i <= count; i++) {
|
||||
System.out.println("\n---------------------第" + i + "轮---------------------");
|
||||
Ttime t = new Ttime().start();
|
||||
// 每次登录的次数
|
||||
for (int j = 1; j <= loginCount; j++) {
|
||||
StpUtil.login("1000" + j, "PC-" + j);
|
||||
if(j % 1000 == 0) {
|
||||
System.out.println("已登录:" + j);
|
||||
}
|
||||
}
|
||||
t.end();
|
||||
list.add((t.returnMs() + 0.0) / 1000);
|
||||
System.out.println("第" + i + "轮" + "用时:" + t.toString());
|
||||
}
|
||||
// System.out.println(((SaTokenDaoDefaultImpl)SaTokenManager.getSaTokenDao()).dataMap.size());
|
||||
|
||||
System.out.println("\n---------------------测试结果---------------------");
|
||||
System.out.println(list.size() + "次测试: " + list);
|
||||
double ss = 0;
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
ss += list.get(i);
|
||||
}
|
||||
System.out.println("平均用时: " + ss / list.size());
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
package com.pj.test;
|
||||
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
|
||||
/**
|
||||
* 测试专用Controller
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/test/")
|
||||
public class TestController {
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test
|
||||
@RequestMapping("test")
|
||||
public SaResult test() {
|
||||
System.out.println("------------进来了");
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
// 测试 浏览器访问: http://localhost:8081/test/test2
|
||||
@RequestMapping("test2")
|
||||
public SaResult test2() {
|
||||
return SaResult.ok();
|
||||
}
|
||||
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
package com.pj.util;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* ajax请求返回Json格式数据的封装
|
||||
*/
|
||||
public class AjaxJson implements Serializable{
|
||||
|
||||
private static final long serialVersionUID = 1L; // 序列化版本号
|
||||
|
||||
public static final int CODE_SUCCESS = 200; // 成功状态码
|
||||
public static final int CODE_ERROR = 500; // 错误状态码
|
||||
public static final int CODE_WARNING = 501; // 警告状态码
|
||||
public static final int CODE_NOT_JUR = 403; // 无权限状态码
|
||||
public static final int CODE_NOT_LOGIN = 401; // 未登录状态码
|
||||
public static final int CODE_INVALID_REQUEST = 400; // 无效请求状态码
|
||||
|
||||
public int code; // 状态码
|
||||
public String msg; // 描述信息
|
||||
public Object data; // 携带对象
|
||||
public Long dataCount; // 数据总数,用于分页
|
||||
|
||||
/**
|
||||
* 返回code
|
||||
* @return
|
||||
*/
|
||||
public int getCode() {
|
||||
return this.code;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给msg赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setMsg(String msg) {
|
||||
this.msg = msg;
|
||||
return this;
|
||||
}
|
||||
public String getMsg() {
|
||||
return this.msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* 给data赋值,连缀风格
|
||||
*/
|
||||
public AjaxJson setData(Object data) {
|
||||
this.data = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将data还原为指定类型并返回
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(Class<T> cs) {
|
||||
return (T) data;
|
||||
}
|
||||
|
||||
// ============================ 构建 ==================================
|
||||
|
||||
public AjaxJson(int code, String msg, Object data, Long dataCount) {
|
||||
this.code = code;
|
||||
this.msg = msg;
|
||||
this.data = data;
|
||||
this.dataCount = dataCount;
|
||||
}
|
||||
|
||||
// 返回成功
|
||||
public static AjaxJson getSuccess() {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, null, null);
|
||||
}
|
||||
public static AjaxJson getSuccess(String msg, Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, msg, data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessData(Object data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
public static AjaxJson getSuccessArray(Object... data) {
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, null);
|
||||
}
|
||||
|
||||
// 返回失败
|
||||
public static AjaxJson getError() {
|
||||
return new AjaxJson(CODE_ERROR, "error", null, null);
|
||||
}
|
||||
public static AjaxJson getError(String msg) {
|
||||
return new AjaxJson(CODE_ERROR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回警告
|
||||
public static AjaxJson getWarning() {
|
||||
return new AjaxJson(CODE_ERROR, "warning", null, null);
|
||||
}
|
||||
public static AjaxJson getWarning(String msg) {
|
||||
return new AjaxJson(CODE_WARNING, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回未登录
|
||||
public static AjaxJson getNotLogin() {
|
||||
return new AjaxJson(CODE_NOT_LOGIN, "未登录,请登录后再次访问", null, null);
|
||||
}
|
||||
|
||||
// 返回没有权限的
|
||||
public static AjaxJson getNotJur(String msg) {
|
||||
return new AjaxJson(CODE_NOT_JUR, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回一个自定义状态码的
|
||||
public static AjaxJson get(int code, String msg){
|
||||
return new AjaxJson(code, msg, null, null);
|
||||
}
|
||||
|
||||
// 返回分页和数据的
|
||||
public static AjaxJson getPageData(Long dataCount, Object data){
|
||||
return new AjaxJson(CODE_SUCCESS, "ok", data, dataCount);
|
||||
}
|
||||
|
||||
// 返回,根据受影响行数的(大于0=ok,小于0=error)
|
||||
public static AjaxJson getByLine(int line){
|
||||
if(line > 0){
|
||||
return getSuccess("ok", line);
|
||||
}
|
||||
return getError("error").setData(line);
|
||||
}
|
||||
|
||||
// 返回,根据布尔值来确定最终结果的 (true=ok,false=error)
|
||||
public static AjaxJson getByBoolean(boolean b){
|
||||
return b ? getSuccess("ok") : getError("error");
|
||||
}
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
@Override
|
||||
public String toString() {
|
||||
String data_string = null;
|
||||
if(data == null){
|
||||
|
||||
} else if(data instanceof List){
|
||||
data_string = "List(length=" + ((List)data).size() + ")";
|
||||
} else {
|
||||
data_string = data.toString();
|
||||
}
|
||||
return "{"
|
||||
+ "\"code\": " + this.getCode()
|
||||
+ ", \"msg\": \"" + this.getMsg() + "\""
|
||||
+ ", \"data\": " + data_string
|
||||
+ ", \"dataCount\": " + dataCount
|
||||
+ "}";
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
package com.pj.util;
|
||||
|
||||
|
||||
/**
|
||||
* 用于测试用时
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
public class Ttime {
|
||||
|
||||
private long start=0; //开始时间
|
||||
private long end=0; //结束时间
|
||||
|
||||
public static Ttime t = new Ttime(); //static快捷使用
|
||||
|
||||
/**
|
||||
* 开始计时
|
||||
* @return
|
||||
*/
|
||||
public Ttime start() {
|
||||
start=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 结束计时
|
||||
*/
|
||||
public Ttime end() {
|
||||
end=System.currentTimeMillis();
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 返回所用毫秒数
|
||||
*/
|
||||
public long returnMs() {
|
||||
return end-start;
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化输出结果
|
||||
*/
|
||||
public void outTime() {
|
||||
System.out.println(this.toString());
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束并格式化输出结果
|
||||
*/
|
||||
public void endOutTime() {
|
||||
this.end().outTime();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return (returnMs() + 0.0) / 1000 + "s"; // 格式化为:0.01s
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 8081
|
||||
|
||||
# sa-token配置
|
||||
sa-token:
|
||||
# token名称 (同时也是cookie名称)
|
||||
token-name: satoken
|
||||
# token有效期,单位s 默认30天, -1代表永不过期
|
||||
timeout: 2592000
|
||||
# token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒
|
||||
activity-timeout: -1
|
||||
# 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录)
|
||||
is-concurrent: true
|
||||
# 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token)
|
||||
is-share: true
|
||||
# token风格
|
||||
token-style: uuid
|
||||
# 是否输出操作日志
|
||||
is-log: true
|
||||
|
||||
spring:
|
||||
data:
|
||||
# redis配置
|
||||
redis:
|
||||
# Redis数据库索引(默认为0)
|
||||
database: 1
|
||||
# Redis服务器地址
|
||||
host: 127.0.0.1
|
||||
# Redis服务器连接端口
|
||||
port: 6379
|
||||
# Redis服务器连接密码(默认为空)
|
||||
password:
|
||||
# 连接超时时间
|
||||
timeout: 10s
|
||||
lettuce:
|
||||
pool:
|
||||
# 连接池最大连接数
|
||||
max-active: 200
|
||||
# 连接池最大阻塞等待时间(使用负值表示没有限制)
|
||||
max-wait: -1ms
|
||||
# 连接池中的最大空闲连接
|
||||
max-idle: 10
|
||||
# 连接池中的最小空闲连接
|
||||
min-idle: 0
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
+6180
-14088
File diff suppressed because it is too large
Load Diff
@@ -14,9 +14,9 @@
|
||||
"vue-router": "^3.6.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~4.4.6",
|
||||
"@vue/cli-plugin-eslint": "~4.4.6",
|
||||
"@vue/cli-service": "~4.4.6",
|
||||
"@vue/cli-plugin-babel": "~5.0.8",
|
||||
"@vue/cli-plugin-eslint": "~5.0.8",
|
||||
"@vue/cli-service": "~5.0.8",
|
||||
"babel-eslint": "^10.1.0",
|
||||
"eslint": "^6.7.2",
|
||||
"eslint-plugin-vue": "^6.2.2",
|
||||
|
||||
@@ -42,7 +42,12 @@ sa.ajax = function(url, data, successFn) {
|
||||
// ----------------------------------- 相关事件 -----------------------------------
|
||||
|
||||
// 检查当前是否已经登录,如果已登录则直接开始跳转,如果未登录则等待用户输入账号密码
|
||||
sa.ajax("/sso/getRedirectUrl", {redirect: getParam('redirect', ''), mode: getParam('mode', '')}, function(res) {
|
||||
var pData = {
|
||||
client: getParam('client', ''),
|
||||
redirect: getParam('redirect', ''),
|
||||
mode: getParam('mode', '')
|
||||
};
|
||||
sa.ajax("/sso/getRedirectUrl", pData, function(res) {
|
||||
if(res.code == 200) {
|
||||
// 已登录,并且redirect地址有效,开始跳转
|
||||
location.href = decodeURIComponent(res.data);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
target/
|
||||
|
||||
node_modules/
|
||||
bin/
|
||||
.settings/
|
||||
unpackage/
|
||||
.classpath
|
||||
.project
|
||||
|
||||
.idea/
|
||||
|
||||
.factorypath
|
||||
@@ -0,0 +1,68 @@
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-demo-sso-server-solon</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
|
||||
<!-- SpringBoot -->
|
||||
<parent>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-parent</artifactId>
|
||||
<version>1.12.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- SpringBoot Web依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 权限认证, 在线文档:https://sa-token.cc/ -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-solon-plugin</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 插件:整合SSO -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-sso</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Sa-Token 插件:整合redis (使用jackson序列化方式) -->
|
||||
<dependency>
|
||||
<groupId>cn.dev33</groupId>
|
||||
<artifactId>sa-token-dao-redisx</artifactId>
|
||||
<version>${sa-token.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 视图引擎(在前后端不分离模式下提供视图支持) -->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>solon.view.thymeleaf</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Http请求工具(在模式三的单点注销功能下用到,如不需要可以注释掉) -->
|
||||
<dependency>
|
||||
<groupId>org.noear</groupId>
|
||||
<artifactId>forest-solon-plugin</artifactId>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.pj;
|
||||
|
||||
|
||||
import org.noear.solon.Solon;
|
||||
|
||||
public class SaSsoServerApp {
|
||||
|
||||
public static void main(String[] args) {
|
||||
Solon.start(SaSsoServerApp.class, args);
|
||||
System.out.println("\n------ Sa-Token-SSO 统一认证中心启动成功 ");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.pj.h5;
|
||||
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.handle.FilterChain;
|
||||
|
||||
|
||||
/**
|
||||
* 跨域过滤器
|
||||
* @author kong
|
||||
*/
|
||||
@Component(index = -200)
|
||||
public class CorsFilter implements Filter {
|
||||
|
||||
static final String OPTIONS = "OPTIONS";
|
||||
|
||||
@Override
|
||||
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
|
||||
// 允许指定域访问跨域资源
|
||||
ctx.headerSet("Access-Control-Allow-Origin", "*");
|
||||
// 允许所有请求方式
|
||||
ctx.headerSet("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
|
||||
// 有效时间
|
||||
ctx.headerSet("Access-Control-Max-Age", "3600");
|
||||
// 允许的header参数
|
||||
ctx.headerSet("Access-Control-Allow-Headers", "x-requested-with,satoken");
|
||||
|
||||
// 如果是预检请求,直接返回
|
||||
if (OPTIONS.equals(ctx.method())) {
|
||||
System.out.println("=======================浏览器发来了OPTIONS预检请求==========");
|
||||
ctx.output("");
|
||||
return;
|
||||
}
|
||||
|
||||
// System.out.println("*********************************过滤器被使用**************************");
|
||||
chain.doFilter(ctx);
|
||||
}
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
package com.pj.h5;
|
||||
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoConsts;
|
||||
import cn.dev33.satoken.sso.SaSsoUtil;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaFoxUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Render;
|
||||
|
||||
/**
|
||||
* 前后台分离架构下集成SSO所需的代码 (SSO-Server端)
|
||||
* <p>(注:如果不需要前后端分离架构下集成SSO,可删除此包下所有代码)</p>
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class H5Controller implements Render {
|
||||
|
||||
/**
|
||||
* 获取 redirectUrl
|
||||
*/
|
||||
@Mapping("/sso/getRedirectUrl")
|
||||
private Object getRedirectUrl(String redirect, String mode, String client) {
|
||||
// 未登录情况下,返回 code=401
|
||||
if (StpUtil.isLogin() == false) {
|
||||
return SaResult.code(401);
|
||||
}
|
||||
// 已登录情况下,构建 redirectUrl
|
||||
if (SaSsoConsts.MODE_SIMPLE.equals(mode)) {
|
||||
// 模式一
|
||||
SaSsoUtil.checkRedirectUrl(SaFoxUtil.decoderUrl(redirect));
|
||||
return SaResult.data(redirect);
|
||||
} else {
|
||||
// 模式二或模式三
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
|
||||
return SaResult.data(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 控制当前类的异常
|
||||
*/
|
||||
@Override
|
||||
public void render(Object data, Context ctx) throws Throwable {
|
||||
if (data instanceof Throwable) {
|
||||
Throwable e = (Throwable) data;
|
||||
e.printStackTrace();
|
||||
ctx.render(SaResult.error(e.getMessage()));
|
||||
} else {
|
||||
ctx.render(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package com.pj.sso;
|
||||
|
||||
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import org.noear.solon.annotation.Component;
|
||||
import org.noear.solon.core.handle.Context;
|
||||
import org.noear.solon.core.handle.Filter;
|
||||
import org.noear.solon.core.handle.FilterChain;
|
||||
|
||||
/**
|
||||
* 全局异常处理
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Component
|
||||
public class GlobalExceptionFilter implements Filter {
|
||||
|
||||
@Override
|
||||
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
|
||||
try {
|
||||
chain.doFilter(ctx);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
||||
ctx.render(SaResult.error(e.getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.pj.sso;
|
||||
|
||||
import cn.dev33.satoken.config.SaSsoConfig;
|
||||
import cn.dev33.satoken.dao.SaTokenDao;
|
||||
import cn.dev33.satoken.dao.SaTokenDaoOfRedis;
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import cn.dev33.satoken.util.SaResult;
|
||||
import com.dtflys.forest.Forest;
|
||||
import org.noear.solon.annotation.Bean;
|
||||
import org.noear.solon.annotation.Configuration;
|
||||
import org.noear.solon.annotation.Inject;
|
||||
import org.noear.solon.core.handle.ModelAndView;
|
||||
|
||||
/**
|
||||
* @author noear 2023/1/3 created
|
||||
*/
|
||||
@Configuration
|
||||
public class SsoConfig {
|
||||
/**
|
||||
* 构建 SaSsoConfig bean
|
||||
* */
|
||||
@Bean
|
||||
public SaSsoConfig getSaSsoConfig(@Inject("${sa-token.sso}") SaSsoConfig ssoConfig) {
|
||||
return ssoConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建建 SaToken redis dao(如果不需要 redis;可以注释掉)
|
||||
* */
|
||||
@Bean
|
||||
public SaTokenDao saTokenDaoInit(@Inject("${sa-token-dao.redis}") SaTokenDaoOfRedis saTokenDao) {
|
||||
return saTokenDao;
|
||||
}
|
||||
|
||||
// 配置SSO相关参数
|
||||
@Bean
|
||||
public void configSso(SaSsoConfig sso) {
|
||||
|
||||
// 配置:未登录时返回的View
|
||||
sso.setNotLoginView(() -> {
|
||||
return new ModelAndView("sa-login.html");
|
||||
});
|
||||
|
||||
// 配置:登录处理函数
|
||||
sso.setDoLoginHandle((name, pwd) -> {
|
||||
// 此处仅做模拟登录,真实环境应该查询数据进行登录
|
||||
if("sa".equals(name) && "123456".equals(pwd)) {
|
||||
StpUtil.login(10001);
|
||||
return SaResult.ok("登录成功!").setData(StpUtil.getTokenValue());
|
||||
}
|
||||
return SaResult.error("登录失败!");
|
||||
});
|
||||
|
||||
// 配置 Http 请求处理器 (在模式三的单点注销功能下用到,如不需要可以注释掉)
|
||||
sso.setSendHttp(url -> {
|
||||
try {
|
||||
// 发起 http 请求
|
||||
System.out.println("------ 发起请求:" + url);
|
||||
return Forest.get(url).executeAsString();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package com.pj.sso;
|
||||
|
||||
|
||||
import cn.dev33.satoken.sso.SaSsoProcessor;
|
||||
import org.noear.solon.annotation.Controller;
|
||||
import org.noear.solon.annotation.Mapping;
|
||||
|
||||
/**
|
||||
* Sa-Token-SSO Server端 Controller
|
||||
* @author kong
|
||||
*
|
||||
*/
|
||||
@Controller
|
||||
public class SsoServerController {
|
||||
|
||||
/*
|
||||
* SSO-Server端:处理所有SSO相关请求
|
||||
* http://{host}:{port}/sso/auth -- 单点登录授权地址,接受参数:redirect=授权重定向地址
|
||||
* http://{host}:{port}/sso/doLogin -- 账号密码登录接口,接受参数:name、pwd
|
||||
* http://{host}:{port}/sso/checkTicket -- Ticket校验接口(isHttp=true时打开),接受参数:ticket=ticket码、ssoLogoutCall=单点注销回调地址 [可选]
|
||||
* http://{host}:{port}/sso/signout -- 单点注销地址(isSlo=true时打开),接受参数:loginId=账号id、secretkey=接口调用秘钥
|
||||
*/
|
||||
@Mapping("/sso/*")
|
||||
public Object ssoRequest() {
|
||||
return SaSsoProcessor.instance.serverDister();
|
||||
}
|
||||
}
|
||||
Vendored
+2
File diff suppressed because one or more lines are too long
+2
File diff suppressed because one or more lines are too long
+2
@@ -0,0 +1,2 @@
|
||||
/*! layer mobile-v2.0.0 Web弹层组件 MIT License http://layer.layui.com/mobile By 贤心 */
|
||||
;!function(e){"use strict";var t=document,n="querySelectorAll",i="getElementsByClassName",a=function(e){return t[n](e)},s={type:0,shade:!0,shadeClose:!0,fixed:!0,anim:"scale"},l={extend:function(e){var t=JSON.parse(JSON.stringify(s));for(var n in e)t[n]=e[n];return t},timer:{},end:{}};l.touch=function(e,t){e.addEventListener("click",function(e){t.call(this,e)},!1)};var r=0,o=["layui-m-layer"],c=function(e){var t=this;t.config=l.extend(e),t.view()};c.prototype.view=function(){var e=this,n=e.config,s=t.createElement("div");e.id=s.id=o[0]+r,s.setAttribute("class",o[0]+" "+o[0]+(n.type||0)),s.setAttribute("index",r);var l=function(){var e="object"==typeof n.title;return n.title?'<h3 style="'+(e?n.title[1]:"")+'">'+(e?n.title[0]:n.title)+"</h3>":""}(),c=function(){"string"==typeof n.btn&&(n.btn=[n.btn]);var e,t=(n.btn||[]).length;return 0!==t&&n.btn?(e='<span yes type="1">'+n.btn[0]+"</span>",2===t&&(e='<span no type="0">'+n.btn[1]+"</span>"+e),'<div class="layui-m-layerbtn">'+e+"</div>"):""}();if(n.fixed||(n.top=n.hasOwnProperty("top")?n.top:100,n.style=n.style||"",n.style+=" top:"+(t.body.scrollTop+n.top)+"px"),2===n.type&&(n.content='<i></i><i class="layui-m-layerload"></i><i></i><p>'+(n.content||"")+"</p>"),n.skin&&(n.anim="up"),"msg"===n.skin&&(n.shade=!1),s.innerHTML=(n.shade?"<div "+("string"==typeof n.shade?'style="'+n.shade+'"':"")+' class="layui-m-layershade"></div>':"")+'<div class="layui-m-layermain" '+(n.fixed?"":'style="position:static;"')+'><div class="layui-m-layersection"><div class="layui-m-layerchild '+(n.skin?"layui-m-layer-"+n.skin+" ":"")+(n.className?n.className:"")+" "+(n.anim?"layui-m-anim-"+n.anim:"")+'" '+(n.style?'style="'+n.style+'"':"")+">"+l+'<div class="layui-m-layercont">'+n.content+"</div>"+c+"</div></div></div>",!n.type||2===n.type){var d=t[i](o[0]+n.type),y=d.length;y>=1&&layer.close(d[0].getAttribute("index"))}document.body.appendChild(s);var u=e.elem=a("#"+e.id)[0];n.success&&n.success(u),e.index=r++,e.action(n,u)},c.prototype.action=function(e,t){var n=this;e.time&&(l.timer[n.index]=setTimeout(function(){layer.close(n.index)},1e3*e.time));var a=function(){var t=this.getAttribute("type");0==t?(e.no&&e.no(),layer.close(n.index)):e.yes?e.yes(n.index):layer.close(n.index)};if(e.btn)for(var s=t[i]("layui-m-layerbtn")[0].children,r=s.length,o=0;o<r;o++)l.touch(s[o],a);if(e.shade&&e.shadeClose){var c=t[i]("layui-m-layershade")[0];l.touch(c,function(){layer.close(n.index,e.end)})}e.end&&(l.end[n.index]=e.end)},e.layer={v:"2.0",index:r,open:function(e){var t=new c(e||{});return t.index},close:function(e){var n=a("#"+o[0]+e)[0];n&&(n.innerHTML="",t.body.removeChild(n),clearTimeout(l.timer[e]),delete l.timer[e],"function"==typeof l.end[e]&&l.end[e](),delete l.end[e])},closeAll:function(){for(var e=t[i](o[0]),n=0,a=e.length;n<a;n++)layer.close(0|e[0].getAttribute("index"))}},"function"==typeof define?define(function(){return layer}):function(){var e=document.scripts,n=e[e.length-1],i=n.src,a=i.substring(0,i.lastIndexOf("/")+1);n.getAttribute("merge")||document.head.appendChild(function(){var e=t.createElement("link");return e.href=a+"need/layer.css?2.0",e.type="text/css",e.rel="styleSheet",e.id="layermcss",e}())}()}(window);
|
||||
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.8 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 11 KiB |
+1
File diff suppressed because one or more lines are too long
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 701 B |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.7 KiB |
+59
@@ -0,0 +1,59 @@
|
||||
*{margin: 0; padding: 0;}
|
||||
body{font-family: Helvetica Neue,Helvetica,PingFang SC,Tahoma,Arial,sans-serif;}
|
||||
::-webkit-input-placeholder{color: #ccc;}
|
||||
|
||||
/* 视图盒子 */
|
||||
.view-box{position: relative; width: 100vw; height: 100vh; overflow: hidden;}
|
||||
/* 背景 EAEFF3 */
|
||||
.bg-1{height: 50%; background: linear-gradient(to bottom right, #0466c5, #3496F5);}
|
||||
.bg-2{height: 50%; background-color: #EAEFF3;}
|
||||
|
||||
/* 渐变背景 */
|
||||
/*.bg-1{
|
||||
background-size: 500%;
|
||||
background-image: linear-gradient(125deg,#0466c5,#3496F5,#0466c5,#3496F5,#0466c5,#2496F5);
|
||||
animation: bganimation 30s infinite;
|
||||
}
|
||||
@keyframes bganimation{
|
||||
0%{background-position: 0% 50%;}
|
||||
50%{background-position: 100% 50%;}
|
||||
100%{background-position: 0% 50%;}
|
||||
} */
|
||||
/* 背景 */
|
||||
.bg-1{background: #101C34;}
|
||||
.bg-2{background: #101C34;}
|
||||
/* .bg-1{height: 100%; background-image: url(./login-bg.png); background-size: 100% 100%;} */
|
||||
|
||||
|
||||
/* 内容盒子 */
|
||||
.content-box{position: absolute; width: 100vw; height: 100vh; top: 0px;}
|
||||
|
||||
/* 登录盒子 */
|
||||
/* .login-box{width: 400px; height: 400px; position: absolute; left: calc(50% - 200px); top: calc(50% - 200px); max-width: 90%; } */
|
||||
.login-box{width: 400px; margin: auto; max-width: 90%; height: 100%;}
|
||||
.login-box{display: flex; align-items: center; text-align: center;}
|
||||
|
||||
/* 表单 */
|
||||
.from-box{flex: 1; padding: 20px 50px; background-color: #FFF;}
|
||||
.from-box{border-radius: 1px; box-shadow: 1px 1px 20px #666;}
|
||||
.from-title{margin-top: 20px; margin-bottom: 30px; text-align: center;}
|
||||
|
||||
/* 输入框 */
|
||||
.from-item{border: 0px #000 solid; margin-bottom: 15px;}
|
||||
.s-input{width: 100%; line-height: 32px; height: 32px; text-indent: 1em; outline: 0; border: 1px #ccc solid; border-radius: 3px; transition: all 0.2s;}
|
||||
.s-input{font-size: 12px;}
|
||||
.s-input:focus{border-color: #409eff}
|
||||
|
||||
/* 登录按钮 */
|
||||
.s-btn{ text-indent: 0; cursor: pointer; background-color: #409EFF; border-color: #409EFF; color: #FFF;}
|
||||
.s-btn:hover{background-color: #50aEFF;}
|
||||
|
||||
/* 重置按钮 */
|
||||
.reset-box{text-align: left; font-size: 12px;}
|
||||
.reset-box a{text-decoration: none;}
|
||||
.reset-box a:hover{text-decoration: underline;}
|
||||
|
||||
/* loading框样式 */
|
||||
.ajax-layer-load.layui-layer-dialog{min-width: 0px !important; background-color: rgba(0,0,0,0.85);}
|
||||
.ajax-layer-load.layui-layer-dialog .layui-layer-content{padding: 10px 20px 10px 40px; color: #FFF;}
|
||||
.ajax-layer-load.layui-layer-dialog .layui-layer-content .layui-layer-ico{width: 20px; height: 20px; background-size: 20px 20px; top: 12px; }
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
// sa
|
||||
var sa = {};
|
||||
|
||||
// 打开loading
|
||||
sa.loading = function(msg) {
|
||||
layer.closeAll(); // 开始前先把所有弹窗关了
|
||||
return layer.msg(msg, {icon: 16, shade: 0.3, time: 1000 * 20, skin: 'ajax-layer-load' });
|
||||
};
|
||||
|
||||
// 隐藏loading
|
||||
sa.hideLoading = function() {
|
||||
layer.closeAll();
|
||||
};
|
||||
|
||||
|
||||
// ----------------------------------- 登录事件 -----------------------------------
|
||||
|
||||
$('.login-btn').click(function(){
|
||||
sa.loading("正在登录...");
|
||||
// 开始登录
|
||||
setTimeout(function() {
|
||||
$.ajax({
|
||||
url: "sso/doLogin",
|
||||
type: "post",
|
||||
data: {
|
||||
name: $('[name=name]').val(),
|
||||
pwd: $('[name=pwd]').val()
|
||||
},
|
||||
dataType: 'json',
|
||||
success: function(res){
|
||||
console.log('返回数据:', res);
|
||||
sa.hideLoading();
|
||||
if(res.code == 200) {
|
||||
layer.msg('登录成功', {anim: 0, icon: 6 });
|
||||
setTimeout(function() {
|
||||
location.reload();
|
||||
}, 800)
|
||||
} else {
|
||||
layer.msg(res.msg, {anim: 6, icon: 2 });
|
||||
}
|
||||
},
|
||||
error: function(xhr, type, errorThrown){
|
||||
sa.hideLoading();
|
||||
if(xhr.status == 0){
|
||||
return layer.alert('无法连接到服务器,请检查网络');
|
||||
}
|
||||
return layer.alert("异常:" + JSON.stringify(xhr));
|
||||
}
|
||||
});
|
||||
}, 400);
|
||||
});
|
||||
|
||||
// 绑定回车事件
|
||||
$('[name=name],[name=pwd]').bind('keypress', function(event){
|
||||
if(event.keyCode == "13") {
|
||||
$('.login-btn').click();
|
||||
}
|
||||
});
|
||||
|
||||
// 输入框获取焦点
|
||||
$("[name=name]").focus();
|
||||
|
||||
// 打印信息
|
||||
var str = "This page is provided by Sa-Token, Please refer to: " + "https://sa-token.cc/";
|
||||
console.log(str);
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<title>Sa-SSO-Server 认证中心-登录</title>
|
||||
<meta charset="utf-8">
|
||||
<base th:href="@{/static}" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<link rel="stylesheet" href="./sa-res/login.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="view-box">
|
||||
<div class="bg-1"></div>
|
||||
<div class="bg-2"></div>
|
||||
<div class="content-box">
|
||||
<div class="login-box">
|
||||
<div class="from-box">
|
||||
<h2 class="from-title">Sa-SSO-Server 认证中心</h2>
|
||||
<div class="from-item">
|
||||
<input class="s-input" name="name" placeholder="请输入账号" />
|
||||
</div>
|
||||
<div class="from-item">
|
||||
<input class="s-input" name="pwd" type="password" placeholder="请输入密码" />
|
||||
</div>
|
||||
<div class="from-item">
|
||||
<button class="s-input s-btn login-btn">登录</button>
|
||||
</div>
|
||||
<div class="from-item reset-box">
|
||||
<a href="javascript: location.reload();" >刷新</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 底部 版权 -->
|
||||
<div style="position: absolute; bottom: 40px; width: 100%; text-align: center; color: #666;">
|
||||
This page is provided by Sa-Token-SSO
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- scripts -->
|
||||
<script src="./sa-res/jquery.min.js"></script>
|
||||
<script src="./sa-res/layer/layer.js"></script>
|
||||
<script src="./sa-res/login.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,41 @@
|
||||
# 端口
|
||||
server:
|
||||
port: 9000
|
||||
|
||||
# Sa-Token 配置
|
||||
sa-token:
|
||||
# ------- SSO-模式一相关配置 (非模式一不需要配置)
|
||||
# cookie:
|
||||
# 配置 Cookie 作用域
|
||||
# domain: stp.com
|
||||
|
||||
# ------- SSO-模式二相关配置
|
||||
sso:
|
||||
# Ticket有效期 (单位: 秒),默认五分钟
|
||||
ticket-timeout: 300
|
||||
# 所有允许的授权回调地址
|
||||
allow-url: "*"
|
||||
# 是否打开单点注销功能
|
||||
is-slo: true
|
||||
|
||||
# ------- SSO-模式三相关配置 (下面的配置在SSO模式三并且 is-slo=true 时打开)
|
||||
# 是否打开模式三
|
||||
isHttp: true
|
||||
# 接口调用秘钥(用于SSO模式三的单点注销功能)
|
||||
secretkey: kQwIOrYvnXmSDkwEiFngrKidMcdrgKor
|
||||
# ---- 除了以上配置项,你还需要为 Sa-Token 配置http请求处理器(文档有步骤说明)
|
||||
|
||||
sa-token-dao: #名字可以随意取
|
||||
redis:
|
||||
server: "localhost:6379"
|
||||
password: 123456
|
||||
db: 1
|
||||
maxTotal: 200
|
||||
|
||||
forest:
|
||||
# 关闭 forest 请求日志打印
|
||||
log-enabled: false
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -23,7 +23,7 @@ public class H5Controller {
|
||||
* 获取 redirectUrl
|
||||
*/
|
||||
@RequestMapping("/sso/getRedirectUrl")
|
||||
private Object getRedirectUrl(String redirect, String mode) {
|
||||
private Object getRedirectUrl(String redirect, String mode, String client) {
|
||||
// 未登录情况下,返回 code=401
|
||||
if(StpUtil.isLogin() == false) {
|
||||
return SaResult.code(401);
|
||||
@@ -35,7 +35,7 @@ public class H5Controller {
|
||||
return SaResult.data(redirect);
|
||||
} else {
|
||||
// 模式二或模式三
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), redirect);
|
||||
String redirectUrl = SaSsoUtil.buildRedirectUrl(StpUtil.getLoginId(), client, redirect);
|
||||
return SaResult.data(redirectUrl);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,13 +10,13 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<!-- 定义 Sa-Token 版本号 -->
|
||||
<properties>
|
||||
<sa-token.version>1.33.0</sa-token.version>
|
||||
<sa-token.version>1.34.0</sa-token.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.5.12</version>
|
||||
<version>2.5.14</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user