This commit is contained in:
shengzhang 2021-04-23 19:46:37 +08:00
parent 9a2e14d147
commit 79d7967e0d
35 changed files with 516 additions and 196 deletions

View File

@ -23,21 +23,21 @@ import cn.dev33.satoken.util.SaFoxUtil;
* @author kong
*
*/
public class SaTokenManager {
public class SaManager {
/**
* 配置文件 Bean
*/
private static SaTokenConfig config;
public static void setConfig(SaTokenConfig config) {
SaTokenManager.config = config;
SaManager.config = config;
if(config.getIsV()) {
SaFoxUtil.printSaToken();
}
}
public static SaTokenConfig getConfig() {
if (config == null) {
synchronized (SaTokenManager.class) {
synchronized (SaManager.class) {
if (config == null) {
setConfig(SaTokenConfigFactory.createConfig());
}
@ -51,14 +51,14 @@ public class SaTokenManager {
*/
private static SaTokenDao saTokenDao;
public static void setSaTokenDao(SaTokenDao saTokenDao) {
if(SaTokenManager.saTokenDao != null && (SaTokenManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) {
((SaTokenDaoDefaultImpl)SaTokenManager.saTokenDao).endRefreshThread();
if(SaManager.saTokenDao != null && (SaManager.saTokenDao instanceof SaTokenDaoDefaultImpl)) {
((SaTokenDaoDefaultImpl)SaManager.saTokenDao).endRefreshThread();
}
SaTokenManager.saTokenDao = saTokenDao;
SaManager.saTokenDao = saTokenDao;
}
public static SaTokenDao getSaTokenDao() {
if (saTokenDao == null) {
synchronized (SaTokenManager.class) {
synchronized (SaManager.class) {
if (saTokenDao == null) {
setSaTokenDao(new SaTokenDaoDefaultImpl());
}
@ -72,11 +72,11 @@ public class SaTokenManager {
*/
private static StpInterface stpInterface;
public static void setStpInterface(StpInterface stpInterface) {
SaTokenManager.stpInterface = stpInterface;
SaManager.stpInterface = stpInterface;
}
public static StpInterface getStpInterface() {
if (stpInterface == null) {
synchronized (SaTokenManager.class) {
synchronized (SaManager.class) {
if (stpInterface == null) {
setStpInterface(new StpInterfaceDefaultImpl());
}
@ -90,11 +90,11 @@ public class SaTokenManager {
*/
private static SaTokenAction saTokenAction;
public static void setSaTokenAction(SaTokenAction saTokenAction) {
SaTokenManager.saTokenAction = saTokenAction;
SaManager.saTokenAction = saTokenAction;
}
public static SaTokenAction getSaTokenAction() {
if (saTokenAction == null) {
synchronized (SaTokenManager.class) {
synchronized (SaManager.class) {
if (saTokenAction == null) {
setSaTokenAction(new SaTokenActionDefaultImpl());
}
@ -108,11 +108,11 @@ public class SaTokenManager {
*/
private static SaTokenContext saTokenContext;
public static void setSaTokenContext(SaTokenContext saTokenContext) {
SaTokenManager.saTokenContext = saTokenContext;
SaManager.saTokenContext = saTokenContext;
}
public static SaTokenContext getSaTokenContext() {
if (saTokenContext == null) {
synchronized (SaTokenManager.class) {
synchronized (SaManager.class) {
if (saTokenContext == null) {
setSaTokenContext(new SaTokenContextDefaultImpl());
}
@ -126,11 +126,11 @@ public class SaTokenManager {
*/
private static SaTokenListener saTokenListener;
public static void setSaTokenListener(SaTokenListener saTokenListener) {
SaTokenManager.saTokenListener = saTokenListener;
SaManager.saTokenListener = saTokenListener;
}
public static SaTokenListener getSaTokenListener() {
if (saTokenListener == null) {
synchronized (SaTokenManager.class) {
synchronized (SaManager.class) {
if (saTokenListener == null) {
setSaTokenListener(new SaTokenListenerDefaultImpl());
}

View File

@ -3,7 +3,7 @@ package cn.dev33.satoken.action;
import java.util.List;
import java.util.UUID;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.util.SaTokenConsts;
import cn.dev33.satoken.util.SaFoxUtil;
@ -21,7 +21,7 @@ public class SaTokenActionDefaultImpl implements SaTokenAction {
@Override
public String createToken(Object loginId, String loginKey) {
// 根据配置的tokenStyle生成不同风格的token
String tokenStyle = SaTokenManager.getConfig().getTokenStyle();
String tokenStyle = SaManager.getConfig().getTokenStyle();
// uuid
if(SaTokenConsts.TOKEN_STYLE_UUID.equals(tokenStyle)) {
return UUID.randomUUID().toString();

View File

@ -0,0 +1,42 @@
package cn.dev33.satoken.context;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
/**
* Sa-Token 上下文持有类
* @author kong
*
*/
public class SaHolder {
/**
* 获取当前请求的 [Request] 对象
*
* @return see note
*/
public static SaRequest getRequest() {
return SaManager.getSaTokenContext().getRequest();
}
/**
* 获取当前请求的 [Response] 对象
*
* @return see note
*/
public static SaResponse getResponse() {
return SaManager.getSaTokenContext().getResponse();
}
/**
* 获取当前请求的 [存储器] 对象
*
* @return see note
*/
public static SaStorage getStorage() {
return SaManager.getSaTokenContext().getStorage();
}
}

View File

@ -35,9 +35,15 @@ public interface SaRequest {
public String getCookieValue(String name);
/**
* 返回当前请求的URL
* 返回当前请求path (不包括上下文名称)
* @return see note
*/
public String getRequestURI();
public String getRequestPath();
/**
* 返回当前请求的类型
* @return see note
*/
public String getMethod();
}

View File

@ -29,4 +29,21 @@ public interface SaResponse {
*/
public void addCookie(String name, String value, String path, String domain, int timeout);
/**
* 在响应头里写入一个值
* @param name 名字
* @param value
* @return 对象自身
*/
public SaResponse setHeader(String name, String value);
/**
* 在响应头写入 [Server] 服务器名称
* @param value 服务器名称
* @return 对象自身
*/
public default SaResponse setServer(String value) {
return this.setHeader("Server", value);
}
}

View File

@ -6,7 +6,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.util.SaFoxUtil;
/**
@ -189,7 +189,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
public void initRefreshThread() {
// 如果配置了<=0的值则不启动定时清理
if(SaTokenManager.getConfig().getDataRefreshPeriod() <= 0) {
if(SaManager.getConfig().getDataRefreshPeriod() <= 0) {
return;
}
// 启动定时刷新
@ -208,7 +208,7 @@ public class SaTokenDaoDefaultImpl implements SaTokenDao {
e.printStackTrace();
}
// 休眠N秒
int dataRefreshPeriod = SaTokenManager.getConfig().getDataRefreshPeriod();
int dataRefreshPeriod = SaManager.getConfig().getDataRefreshPeriod();
if(dataRefreshPeriod <= 0) {
dataRefreshPeriod = 1;
}

View File

@ -2,7 +2,7 @@ package cn.dev33.satoken.listener;
import java.util.Date;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.util.SaFoxUtil;
@ -88,7 +88,7 @@ public class SaTokenListenerDefaultImpl implements SaTokenListener {
* @param str 字符串
*/
public void println(String str) {
if(SaTokenManager.getConfig().getIsLog()) {
if(SaManager.getConfig().getIsLog()) {
System.out.println(LOG_PREFIX + str);
}
}

View File

@ -3,7 +3,8 @@ package cn.dev33.satoken.router;
import java.util.Arrays;
import java.util.List;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.fun.IsRunFunction;
import cn.dev33.satoken.fun.SaFunction;
@ -23,7 +24,7 @@ public class SaRouterUtil {
* @return 是否匹配成功
*/
public static boolean isMatch(String pattern, String path) {
return SaTokenManager.getSaTokenContext().matchPath(pattern, path);
return SaManager.getSaTokenContext().matchPath(pattern, path);
}
/**
@ -47,7 +48,7 @@ public class SaRouterUtil {
* @return 是否匹配成功
*/
public static boolean isMatchCurrURI(String pattern) {
return isMatch(pattern, SaTokenManager.getSaTokenContext().getRequest().getRequestURI());
return isMatch(pattern, SaHolder.getRequest().getRequestPath());
}
/**
@ -56,7 +57,7 @@ public class SaRouterUtil {
* @return 是否匹配成功
*/
public static boolean isMatchCurrURI(List<String> patterns) {
return isMatch(patterns, SaTokenManager.getSaTokenContext().getRequest().getRequestURI());
return isMatch(patterns, SaHolder.getRequest().getRequestPath());
}
@ -119,7 +120,7 @@ public class SaRouterUtil {
* @return 匹配结果包装对象
*/
public static IsRunFunction match(String... patterns) {
boolean matchResult = isMatch(Arrays.asList(patterns), SaTokenManager.getSaTokenContext().getRequest().getRequestURI());
boolean matchResult = isMatch(Arrays.asList(patterns), SaHolder.getRequest().getRequestPath());
return new IsRunFunction(matchResult);
}

View File

@ -7,7 +7,7 @@ import java.util.Set;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
/**
* Session Model
@ -45,7 +45,7 @@ public class SaSession implements Serializable {
this.id = id;
this.createTime = System.currentTimeMillis();
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doCreateSession(id);
SaManager.getSaTokenListener().doCreateSession(id);
}
/**
@ -246,14 +246,14 @@ public class SaSession implements Serializable {
* 更新Session从持久库更新刷新一下
*/
public void update() {
SaTokenManager.getSaTokenDao().updateSession(this);
SaManager.getSaTokenDao().updateSession(this);
}
/** 注销Session (从持久库删除) */
public void logout() {
SaTokenManager.getSaTokenDao().deleteSession(this.id);
SaManager.getSaTokenDao().deleteSession(this.id);
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doLogoutSession(id);
SaManager.getSaTokenListener().doLogoutSession(id);
}
/** 当Session上的tokenSign数量为零时注销会话 */
@ -268,7 +268,7 @@ public class SaSession implements Serializable {
* @return 此Session的剩余存活时间 (单位: )
*/
public long getTimeout() {
return SaTokenManager.getSaTokenDao().getSessionTimeout(this.id);
return SaManager.getSaTokenDao().getSessionTimeout(this.id);
}
/**
@ -276,7 +276,7 @@ public class SaSession implements Serializable {
* @param timeout 过期时间 (单位: )
*/
public void updateTimeout(long timeout) {
SaTokenManager.getSaTokenDao().updateSessionTimeout(this.id, timeout);
SaManager.getSaTokenDao().updateSessionTimeout(this.id, timeout);
}
/**
@ -285,7 +285,7 @@ public class SaSession implements Serializable {
*/
public void updateMinTimeout(long minTimeout) {
if(getTimeout() < minTimeout) {
SaTokenManager.getSaTokenDao().updateSessionTimeout(this.id, minTimeout);
SaManager.getSaTokenDao().updateSessionTimeout(this.id, minTimeout);
}
}
@ -295,7 +295,7 @@ public class SaSession implements Serializable {
*/
public void updateMaxTimeout(long maxTimeout) {
if(getTimeout() > maxTimeout) {
SaTokenManager.getSaTokenDao().updateSessionTimeout(this.id, maxTimeout);
SaManager.getSaTokenDao().updateSessionTimeout(this.id, maxTimeout);
}
}

View File

@ -1,6 +1,6 @@
package cn.dev33.satoken.session;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
/**
* 自定义Session工具类
@ -22,7 +22,7 @@ public class SaSessionCustomUtil {
* @return sessionId
*/
public static String splicingSessionKey(String sessionId) {
return SaTokenManager.getConfig().getTokenName() + ":" + sessionKey + ":session:" + sessionId;
return SaManager.getConfig().getTokenName() + ":" + sessionKey + ":session:" + sessionId;
}
/**
@ -32,7 +32,7 @@ public class SaSessionCustomUtil {
* @return 是否存在
*/
public static boolean isExists(String sessionId) {
return SaTokenManager.getSaTokenDao().getSession(splicingSessionKey(sessionId)) != null;
return SaManager.getSaTokenDao().getSession(splicingSessionKey(sessionId)) != null;
}
/**
@ -43,10 +43,10 @@ public class SaSessionCustomUtil {
* @return SaSession
*/
public static SaSession getSessionById(String sessionId, boolean isCreate) {
SaSession session = SaTokenManager.getSaTokenDao().getSession(splicingSessionKey(sessionId));
SaSession session = SaManager.getSaTokenDao().getSession(splicingSessionKey(sessionId));
if (session == null && isCreate) {
session = SaTokenManager.getSaTokenAction().createSession(sessionId);
SaTokenManager.getSaTokenDao().setSession(session, SaTokenManager.getConfig().getTimeout());
session = SaManager.getSaTokenAction().createSession(sessionId);
SaManager.getSaTokenDao().setSession(session, SaManager.getConfig().getTimeout());
}
return session;
}
@ -67,7 +67,7 @@ public class SaSessionCustomUtil {
* @param sessionId 指定key
*/
public static void deleteSessionById(String sessionId) {
SaTokenManager.getSaTokenDao().deleteSession(splicingSessionKey(sessionId));
SaManager.getSaTokenDao().deleteSession(splicingSessionKey(sessionId));
}
}

View File

@ -1,6 +1,6 @@
package cn.dev33.satoken.stp;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.dao.SaTokenDao;
import cn.dev33.satoken.util.SaTokenConsts;
@ -97,7 +97,7 @@ public class SaLoginModel {
* @return 对象自身
*/
public SaLoginModel build() {
return build(SaTokenManager.getConfig());
return build(SaManager.getConfig());
}
/**

View File

@ -6,12 +6,13 @@ import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.annotation.SaCheckLogin;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.dev33.satoken.annotation.SaCheckRole;
import cn.dev33.satoken.annotation.SaMode;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.context.model.SaRequest;
import cn.dev33.satoken.context.model.SaResponse;
import cn.dev33.satoken.context.model.SaStorage;
@ -46,7 +47,7 @@ public class StpLogic {
public StpLogic(String loginKey) {
this.loginKey = loginKey;
// SaTokenManager 中记录下此 StpLogic以便根据 LoginKey 进行查找此对象
SaTokenManager.putStpLogic(this);
SaManager.putStpLogic(this);
}
/**
@ -84,7 +85,7 @@ public class StpLogic {
* @return 生成的tokenValue
*/
public String createTokenValue(Object loginId) {
return SaTokenManager.getSaTokenAction().createToken(loginId, loginKey);
return SaManager.getSaTokenAction().createToken(loginId, loginKey);
}
/**
@ -95,7 +96,7 @@ public class StpLogic {
public void setTokenValue(String tokenValue, int cookieTimeout){
SaTokenConfig config = getConfig();
// 将token保存到[存储器]
SaStorage storage = SaTokenManager.getSaTokenContext().getStorage();
SaStorage storage = SaHolder.getStorage();
// 判断是否配置了token前缀
String tokenPrefix = config.getTokenPrefix();
if(SaFoxUtil.isEmpty(tokenPrefix)) {
@ -107,7 +108,7 @@ public class StpLogic {
// 注入Cookie
if(config.getIsReadCookie() == true){
SaResponse response = SaTokenManager.getSaTokenContext().getResponse();
SaResponse response = SaHolder.getResponse();
response.addCookie(getTokenName(), tokenValue, "/", config.getCookieDomain(), cookieTimeout);
}
}
@ -118,8 +119,8 @@ public class StpLogic {
*/
public String getTokenValue(){
// 0. 获取相应对象
SaStorage storage = SaTokenManager.getSaTokenContext().getStorage();
SaRequest request = SaTokenManager.getSaTokenContext().getRequest();
SaStorage storage = SaHolder.getStorage();
SaRequest request = SaHolder.getRequest();
SaTokenConfig config = getConfig();
String keyTokenName = getTokenName();
String tokenValue = null;
@ -220,7 +221,7 @@ public class StpLogic {
// ------ 1获取相应对象
SaTokenConfig config = getConfig();
SaTokenDao dao = SaTokenManager.getSaTokenDao();
SaTokenDao dao = SaManager.getSaTokenDao();
loginModel.build(config);
// ------ 2生成一个token
@ -246,7 +247,7 @@ public class StpLogic {
// 3. 清理user-session上的token签名记录
session.removeTokenSign(tokenSign.getValue());
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doReplaced(loginKey, loginId, tokenSign.getValue(), tokenSign.getDevice());
SaManager.getSaTokenListener().doReplaced(loginKey, loginId, tokenSign.getValue(), tokenSign.getDevice());
}
}
}
@ -277,7 +278,7 @@ public class StpLogic {
setTokenValue(tokenValue, loginModel.getCookieTimeout());
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doLogin(loginKey, loginId, loginModel);
SaManager.getSaTokenListener().doLogin(loginKey, loginId, loginModel);
}
/**
@ -291,7 +292,7 @@ public class StpLogic {
}
// 如果打开了cookie模式第一步先把cookie清除掉
if(getConfig().getIsReadCookie() == true){
SaTokenManager.getSaTokenContext().getResponse().deleteCookie(getTokenName());
SaHolder.getResponse().deleteCookie(getTokenName());
}
logoutByTokenValue(tokenValue);
}
@ -303,28 +304,28 @@ public class StpLogic {
public void logoutByTokenValue(String tokenValue) {
// 1. 清理掉[token-最后操作时间]
clearLastActivity(tokenValue);
// 2. 清理Token-Session
SaManager.getSaTokenDao().delete(splicingKeyTokenSession(tokenValue));
// 2. 尝试清除token-id键值对 (先从db中获取loginId值如果根本查不到loginId那么无需继续操作 )
// 3. 尝试清除token-id键值对 (先从db中获取loginId值如果根本查不到loginId那么无需继续操作 )
String loginId = getLoginIdNotHandle(tokenValue);
if(loginId == null || NotLoginException.ABNORMAL_LIST.contains(loginId)) {
return;
}
SaTokenManager.getSaTokenDao().delete(splicingKeyTokenValue(tokenValue));
SaManager.getSaTokenDao().delete(splicingKeyTokenValue(tokenValue));
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doLogout(loginKey, loginId, tokenValue);
SaManager.getSaTokenListener().doLogout(loginKey, loginId, tokenValue);
// 清理token-session
SaTokenManager.getSaTokenDao().delete(splicingKeyTokenSession(tokenValue));
// 3. 尝试清理账号session上的token签名 (如果为null或已被标记为异常, 那么无需继续执行 )
// 4. 尝试清理User-Session上的token签名 (如果为null或已被标记为异常, 那么无需继续执行 )
SaSession session = getSessionByLoginId(loginId, false);
if(session == null) {
return;
}
session.removeTokenSign(tokenValue);
// 4. 尝试注销session
// 5. 尝试注销User-Session
session.logoutByTokenSignCountToZero();
}
@ -359,11 +360,11 @@ public class StpLogic {
// 2. 清理掉[token-最后操作时间]
clearLastActivity(tokenValue);
// 3. 标记已被踢下线
SaTokenManager.getSaTokenDao().update(splicingKeyTokenValue(tokenValue), NotLoginException.KICK_OUT);
SaManager.getSaTokenDao().update(splicingKeyTokenValue(tokenValue), NotLoginException.KICK_OUT);
// 4. 清理账号session上的token签名
session.removeTokenSign(tokenValue);
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doLogoutByLoginId(loginKey, loginId, tokenValue, tokenSign.getDevice());
SaManager.getSaTokenListener().doLogoutByLoginId(loginKey, loginId, tokenValue, tokenSign.getDevice());
}
}
// 3. 尝试注销session
@ -378,10 +379,10 @@ public class StpLogic {
*/
public void disable(Object loginId, long disableTime) {
// 标注为已被封禁
SaTokenManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime);
SaManager.getSaTokenDao().set(splicingKeyDisable(loginId), DisableLoginException.BE_VALUE, disableTime);
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doDisable(loginKey, loginId, disableTime);
SaManager.getSaTokenListener().doDisable(loginKey, loginId, disableTime);
}
/**
@ -390,7 +391,7 @@ public class StpLogic {
* @return see note
*/
public boolean isDisable(Object loginId) {
return SaTokenManager.getSaTokenDao().get(splicingKeyDisable(loginId)) != null;
return SaManager.getSaTokenDao().get(splicingKeyDisable(loginId)) != null;
}
/**
@ -399,7 +400,7 @@ public class StpLogic {
* @return see note
*/
public long getDisableTime(Object loginId) {
return SaTokenManager.getSaTokenDao().getTimeout(splicingKeyDisable(loginId));
return SaManager.getSaTokenDao().getTimeout(splicingKeyDisable(loginId));
}
/**
@ -407,10 +408,10 @@ public class StpLogic {
* @param loginId 账号id
*/
public void untieDisable(Object loginId) {
SaTokenManager.getSaTokenDao().delete(splicingKeyDisable(loginId));
SaManager.getSaTokenDao().delete(splicingKeyDisable(loginId));
// $$ 通知监听器
SaTokenManager.getSaTokenListener().doUntieDisable(loginKey, loginId);
SaManager.getSaTokenListener().doUntieDisable(loginKey, loginId);
}
@ -463,9 +464,10 @@ public class StpLogic {
if(loginId.equals(NotLoginException.KICK_OUT)) {
throw NotLoginException.newInstance(loginKey, NotLoginException.KICK_OUT);
}
// 如果配置了自动续签, : 检查是否已经 [临时过期]同时更新[最后操作时间]
// 检查是否已经 [临时过期]
checkActivityTimeout(tokenValue);
// 如果配置了自动续签, : 更新[最后操作时间]
if(getConfig().getAutoRenew()) {
checkActivityTimeout(tokenValue);
updateLastActivityToNow(tokenValue);
}
// 至此返回loginId
@ -567,7 +569,7 @@ public class StpLogic {
* @return loginId
*/
public String getLoginIdNotHandle(String tokenValue) {
return SaTokenManager.getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
return SaManager.getSaTokenDao().get(splicingKeyTokenValue(tokenValue));
}
@ -580,10 +582,10 @@ public class StpLogic {
* @return session对象
*/
public SaSession getSessionBySessionId(String sessionId, boolean isCreate) {
SaSession session = SaTokenManager.getSaTokenDao().getSession(sessionId);
SaSession session = SaManager.getSaTokenDao().getSession(sessionId);
if(session == null && isCreate) {
session = SaTokenManager.getSaTokenAction().createSession(sessionId);
SaTokenManager.getSaTokenDao().setSession(session, getConfig().getTimeout());
session = SaManager.getSaTokenAction().createSession(sessionId);
SaManager.getSaTokenDao().setSession(session, getConfig().getTimeout());
}
return session;
}
@ -702,7 +704,7 @@ public class StpLogic {
return;
}
// [最后操作时间]标记为当前时间戳
SaTokenManager.getSaTokenDao().set(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout());
SaManager.getSaTokenDao().set(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()), getConfig().getTimeout());
}
/**
@ -715,9 +717,9 @@ public class StpLogic {
return;
}
// 删除[最后操作时间]
SaTokenManager.getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue));
SaManager.getSaTokenDao().delete(splicingKeyLastActivityTime(tokenValue));
// 清除标记
SaTokenManager.getSaTokenContext().getStorage().delete((SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY));
SaHolder.getStorage().delete((SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY));
}
/**
@ -730,7 +732,7 @@ public class StpLogic {
return;
}
// 如果本次请求已经有了[检查标记], 则立即返回
SaStorage storage = SaTokenManager.getSaTokenContext().getStorage();
SaStorage storage = SaHolder.getStorage();
if(storage.get(SaTokenConsts.TOKEN_ACTIVITY_TIMEOUT_CHECKED_KEY) != null) {
return;
}
@ -767,7 +769,7 @@ public class StpLogic {
if(tokenValue == null || getConfig().getActivityTimeout() == SaTokenDao.NEVER_EXPIRE) {
return;
}
SaTokenManager.getSaTokenDao().update(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()));
SaManager.getSaTokenDao().update(splicingKeyLastActivityTime(tokenValue), String.valueOf(System.currentTimeMillis()));
}
/**
@ -787,7 +789,7 @@ public class StpLogic {
* @return token剩余有效时间
*/
public long getTokenTimeout() {
return SaTokenManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue()));
return SaManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValue()));
}
/**
@ -796,7 +798,7 @@ public class StpLogic {
* @return token剩余有效时间
*/
public long getTokenTimeoutByLoginId(Object loginId) {
return SaTokenManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValueByLoginId(loginId)));
return SaManager.getSaTokenDao().getTimeout(splicingKeyTokenValue(getTokenValueByLoginId(loginId)));
}
/**
@ -813,7 +815,7 @@ public class StpLogic {
* @return token剩余有效时间
*/
public long getSessionTimeoutByLoginId(Object loginId) {
return SaTokenManager.getSaTokenDao().getSessionTimeout(splicingKeySession(loginId));
return SaManager.getSaTokenDao().getSessionTimeout(splicingKeySession(loginId));
}
/**
@ -830,7 +832,7 @@ public class StpLogic {
* @return token剩余有效时间
*/
public long getTokenSessionTimeoutByTokenValue(String tokenValue) {
return SaTokenManager.getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue));
return SaManager.getSaTokenDao().getSessionTimeout(splicingKeyTokenSession(tokenValue));
}
/**
@ -858,7 +860,7 @@ public class StpLogic {
// ------ 开始查询
// 获取相关数据
String keyLastActivityTime = splicingKeyLastActivityTime(tokenValue);
String lastActivityTimeString = SaTokenManager.getSaTokenDao().get(keyLastActivityTime);
String lastActivityTimeString = SaManager.getSaTokenDao().get(keyLastActivityTime);
// 查不到返回-2
if(lastActivityTimeString == null) {
return SaTokenDao.NOT_VALUE_EXPIRE;
@ -884,8 +886,8 @@ public class StpLogic {
* @return 是否含有指定角色标识
*/
public boolean hasRole(Object loginId, String role) {
List<String> roleList = SaTokenManager.getStpInterface().getRoleList(loginId, loginKey);
return SaTokenManager.getSaTokenAction().hasElement(roleList, role);
List<String> roleList = SaManager.getStpInterface().getRoleList(loginId, loginKey);
return SaManager.getSaTokenAction().hasElement(roleList, role);
// return !(roleList == null || roleList.contains(role) == false);
}
@ -914,9 +916,9 @@ public class StpLogic {
*/
public void checkRoleAnd(String... roleArray){
Object loginId = getLoginId();
List<String> roleList = SaTokenManager.getStpInterface().getRoleList(loginId, loginKey);
List<String> roleList = SaManager.getStpInterface().getRoleList(loginId, loginKey);
for (String role : roleArray) {
if(SaTokenManager.getSaTokenAction().hasElement(roleList, role) == false) {
if(SaManager.getSaTokenAction().hasElement(roleList, role) == false) {
throw new NotRoleException(role, this.loginKey);
}
}
@ -928,9 +930,9 @@ public class StpLogic {
*/
public void checkRoleOr(String... roleArray){
Object loginId = getLoginId();
List<String> roleList = SaTokenManager.getStpInterface().getRoleList(loginId, loginKey);
List<String> roleList = SaManager.getStpInterface().getRoleList(loginId, loginKey);
for (String role : roleArray) {
if(SaTokenManager.getSaTokenAction().hasElement(roleList, role) == true) {
if(SaManager.getSaTokenAction().hasElement(roleList, role) == true) {
// 有的话提前退出
return;
}
@ -950,8 +952,8 @@ public class StpLogic {
* @return 是否含有指定权限
*/
public boolean hasPermission(Object loginId, String permission) {
List<String> permissionList = SaTokenManager.getStpInterface().getPermissionList(loginId, loginKey);
return SaTokenManager.getSaTokenAction().hasElement(permissionList, permission);
List<String> permissionList = SaManager.getStpInterface().getPermissionList(loginId, loginKey);
return SaManager.getSaTokenAction().hasElement(permissionList, permission);
// return !(permissionList == null || permissionList.contains(permission) == false);
}
@ -980,9 +982,9 @@ public class StpLogic {
*/
public void checkPermissionAnd(String... permissionArray){
Object loginId = getLoginId();
List<String> permissionList = SaTokenManager.getStpInterface().getPermissionList(loginId, loginKey);
List<String> permissionList = SaManager.getStpInterface().getPermissionList(loginId, loginKey);
for (String permission : permissionArray) {
if(SaTokenManager.getSaTokenAction().hasElement(permissionList, permission) == false) {
if(SaManager.getSaTokenAction().hasElement(permissionList, permission) == false) {
throw new NotPermissionException(permission, this.loginKey);
}
}
@ -994,9 +996,9 @@ public class StpLogic {
*/
public void checkPermissionOr(String... permissionArray){
Object loginId = getLoginId();
List<String> permissionList = SaTokenManager.getStpInterface().getPermissionList(loginId, loginKey);
List<String> permissionList = SaManager.getStpInterface().getPermissionList(loginId, loginKey);
for (String permission : permissionArray) {
if(SaTokenManager.getSaTokenAction().hasElement(permissionList, permission) == true) {
if(SaManager.getSaTokenAction().hasElement(permissionList, permission) == true) {
// 有的话提前退出
return;
}
@ -1105,7 +1107,7 @@ public class StpLogic {
* @return token集合
*/
public List<String> searchTokenValue(String keyword, int start, int size) {
return SaTokenManager.getSaTokenDao().searchData(splicingKeyTokenValue(""), keyword, start, size);
return SaManager.getSaTokenDao().searchData(splicingKeyTokenValue(""), keyword, start, size);
}
/**
@ -1116,7 +1118,7 @@ public class StpLogic {
* @return sessionId集合
*/
public List<String> searchSessionId(String keyword, int start, int size) {
return SaTokenManager.getSaTokenDao().searchData(splicingKeySession(""), keyword, start, size);
return SaManager.getSaTokenDao().searchData(splicingKeySession(""), keyword, start, size);
}
/**
@ -1127,7 +1129,7 @@ public class StpLogic {
* @return sessionId集合
*/
public List<String> searchTokenSessionId(String keyword, int start, int size) {
return SaTokenManager.getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size);
return SaManager.getSaTokenDao().searchData(splicingKeyTokenSession(""), keyword, start, size);
}
@ -1210,7 +1212,7 @@ public class StpLogic {
*/
public SaTokenConfig getConfig() {
// 为什么再次代理一层? 为某些极端业务场景下[需要不同StpLogic不同配置]提供便利
return SaTokenManager.getConfig();
return SaManager.getConfig();
}
@ -1282,14 +1284,14 @@ public class StpLogic {
* @param loginId 指定loginId
*/
public void switchTo(Object loginId) {
SaTokenManager.getSaTokenContext().getStorage().set(splicingKeySwitch(), loginId);
SaHolder.getStorage().set(splicingKeySwitch(), loginId);
}
/**
* 结束临时切换身份
*/
public void endSwitch() {
SaTokenManager.getSaTokenContext().getStorage().delete(splicingKeySwitch());
SaHolder.getStorage().delete(splicingKeySwitch());
}
/**
@ -1297,7 +1299,7 @@ public class StpLogic {
* @return 是否正处于[身份临时切换]
*/
public boolean isSwitch() {
return SaTokenManager.getSaTokenContext().getStorage().get(splicingKeySwitch()) != null;
return SaHolder.getStorage().get(splicingKeySwitch()) != null;
}
/**
@ -1305,7 +1307,7 @@ public class StpLogic {
* @return 返回[身份临时切换]的loginId
*/
public Object getSwitchLoginId() {
return SaTokenManager.getSaTokenContext().getStorage().get(splicingKeySwitch());
return SaHolder.getStorage().get(splicingKeySwitch());
}
/**

View File

@ -3,14 +3,14 @@ package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
@SpringBootApplication
public class SaTokenJwtDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenJwtDemoApplication.class, args);
System.out.println("\n启动成功sa-token配置如下" + SaTokenManager.getConfig());
System.out.println("\n启动成功sa-token配置如下" + SaManager.getConfig());
}
}

View File

@ -4,7 +4,7 @@ import java.util.Date;
import org.springframework.stereotype.Component;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.model.SaStorage;
import cn.dev33.satoken.dao.SaTokenDao;
@ -124,13 +124,13 @@ public class SaTokenJwtUtil {
@Override
public void setLoginId(Object loginId, SaLoginModel loginModel) {
// ------ 1获取相应对象
SaStorage storage = SaTokenManager.getSaTokenContext().getStorage();
SaStorage storage = SaManager.getSaTokenContext().getStorage();
SaTokenConfig config = getConfig();
// ------ 2生成一个token
String tokenValue = createTokenValue(loginId);
storage.set(splicingKeyJustCreatedSave(), tokenValue); // 将token保存到本次request里
if(config.getIsReadCookie() == true){ // cookie注入
SaTokenManager.getSaTokenContext().getResponse().addCookie(getTokenName(), tokenValue, "/", config.getCookieDomain(), (int)config.getTimeout());
SaManager.getSaTokenContext().getResponse().addCookie(getTokenName(), tokenValue, "/", config.getCookieDomain(), (int)config.getTimeout());
}
}
@ -154,7 +154,7 @@ public class SaTokenJwtUtil {
}
// 如果打开了cookie模式把cookie清除掉
if(getConfig().getIsReadCookie() == true){
SaTokenManager.getSaTokenContext().getResponse().deleteCookie(getTokenName());
SaManager.getSaTokenContext().getResponse().deleteCookie(getTokenName());
}
}

View File

@ -3,7 +3,7 @@ package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
/**
* sa-token整合SpringBoot 示例
@ -15,7 +15,7 @@ public class SaTokenDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenDemoApplication.class, args);
System.out.println("\n启动成功sa-token配置如下" + SaTokenManager.getConfig());
System.out.println("\n启动成功sa-token配置如下" + SaManager.getConfig());
}
}

View File

@ -1,10 +1,16 @@
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 com.pj.util.AjaxJson;
import cn.dev33.satoken.context.SaHolder;
import cn.dev33.satoken.filter.SaServletFilter;
import cn.dev33.satoken.interceptor.SaAnnotationInterceptor;
import cn.dev33.satoken.router.SaRouterUtil;
/**
@ -24,27 +30,44 @@ public class SaTokenConfigure implements WebMvcConfigurer {
registry.addInterceptor(new SaAnnotationInterceptor()).addPathPatterns("/**").excludePathPatterns("");
}
// /**
// * 注册 [sa-token全局过滤器]
// */
// @Bean
// public SaServletFilter getSaReactorFilter() {
// return new SaServletFilter()
// // 指定 [拦截路由]
// .addInclude("/**")
// // 指定 [放行路由]
// .addExclude("/favicon.ico")
// // 指定[认证函数]: 每次请求执行
// .setAuth(r -> {
// System.out.println("---------- sa全局认证");
// SaRouterUtil.match("/test/test", () -> StpUtil.checkLogin());
// })
// // 指定[异常处理函数]每次[认证函数]发生异常时执行此函数
// .setError(e -> {
// System.out.println("---------- sa全局异常 ");
// return AjaxJson.getError(e.getMessage());
// })
// ;
// }
/**
* 注册 [sa-token全局过滤器]
*/
@Bean
public SaServletFilter getSaReactorFilter() {
return new SaServletFilter()
// 指定 [拦截路由] [放行路由]
.addInclude("/**").addExclude("/favicon.ico")
// 认证函数: 每次请求执行
.setAuth(r -> {
System.out.println("---------- sa全局认证");
SaRouterUtil.match("/test/test", () -> new Object());
})
// 异常处理函数每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- sa全局异常 ");
return AjaxJson.getError(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-Frame-Options", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
;
})
;
}
}

View File

@ -20,7 +20,6 @@ spring:
# 是否输出操作日志
is-log: false
# redis配置
redis:
# Redis数据库索引默认为0
@ -44,4 +43,12 @@ spring:
# 连接池中的最小空闲连接
min-idle: 0
# 静态文件路径映射
resources:
static-locations: file:E:\work\project-yun\sa-admin

View File

@ -14,7 +14,7 @@ package com.pj;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
/**
* sa-token整合webflux 示例
@ -26,7 +26,7 @@ public class SaTokenWebfluxDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SaTokenWebfluxDemoApplication.class, args);
System.out.println("\n启动成功sa-token配置如下" + SaTokenManager.getConfig());
System.out.println("\n启动成功sa-token配置如下" + SaManager.getConfig());
}
}

View File

@ -45,6 +45,7 @@
- [Session模型详解](/fun/session-model)
- [TokenInfo参数详解](/fun/token-info)
- [框架源码所有技术栈](/fun/tech-stack)
- [Web开发常见漏洞防护](/fun/web-loophole)
<!-- - [Sa-Token大事记](/fun/timeline) -->

View File

@ -0,0 +1,112 @@
# Web开发常见漏洞防护
本章介绍web开发时常见漏洞的防护方法
### 点击劫持
简而言之,点击劫持就是攻击者会自己搭建一个网页,这个网页分为两层:
- 上层是被iframe导入的被攻击网站具有一些敏感性操作比如转账、点赞等按钮这一层会完全透明
- 下层是攻击者自己精心制作的网页,它会准备一个按钮诱导你进行点击(比如点击领取红包等),当你点击这个按钮时,响应你的将不会是这个按钮,而是上层的转账页面,在不知情的情况下造成财产损失
详情介绍参考:[https://blog.csdn.net/qq_32523587/article/details/79613768](https://blog.csdn.net/qq_32523587/article/details/79613768)
防范点击劫持的最有效做法就是增加响应头:`X-Frame-Options: DENY`, 告诉浏览器我们的网站不可以通过 iframe 进行展示(浏览器收到此响应头的指示之后,会拒绝渲染页面),
从根本上杜绝了点击劫持发生的可能,在 [全局过滤器](/use/global-filter) 章节的示例中,我们已经展示了如何在过滤器中增加安全响应头
响应头`X-Frame-Options`有三个取值:
- `DENY`: 任何时候都不可以通过 iframe 展示视图
- `SAMEORIGIN`: 在同域下可以通过 iframe 展示视图
- `ALLOW-FROM uri`: 指定地址来源的访问下可以通过 iframe 展示视图
除了后端的响应头,我们还可以在前端使用如下方式进行判断:
``` js
// 判断当前页面是否在顶层视口打开
if(top != window) {
// 跳入一个安全页面比如404页
location.href="xxx";
}
```
除了跳转页面你还可以用其它方法防御点击劫持比如加载一个全局遮罩来隔离用户的点击操作或者在非顶层窗口下拒绝提交token使用户保持未登录状态
### XSS攻击
XSS跨站脚本攻击就是指攻击者在一段正常的内容中嵌入恶意脚本使得访问网页的用户自动运行一些破坏性的代码
例如一个论坛具有发帖功能,攻击者在发帖时故意插入一段如下内容:
``` java
<img src="xxx" onerror="alert('xss攻击')" />
```
如果论坛服务端没有对此进行任何防护,那么用户每次访问这个帖子的时候都会被强制弹窗,
事实上真正的`XSS攻击`绝不只是弹窗骚扰一下那么简单它可以完成窃取Cookie自动转账等破坏性操作
防范XSS攻击
1. 首先安全响应头给安排上:`X-Frame-Options: 1; mode=block`, 这是浏览器默认提供的XSS防护机制
2. 有条件的情况下尽量使用前后台分离架构传统服务端渲染视图时几乎每一个变量都可能成为XSS注入点而前后台分离下一般只有富文本渲染才会有机会XSS注入
3. 对所有的用户输入必须XSS过滤特别是字符串型参数
4. 可以使用一些自动扫描工具寻找潜在的 XSS 漏洞
### CSRF攻击
CSRF跨站请求伪造又称 XSRF攻击流程如下
1. 用户登陆站点`a.com`身份令牌被写入Cookie中
2. 攻击者搭建站点`b.com`,引诱用户访问
3. 用户访问`b.com`时自动执行了攻击者准备的js代码调用`a.com`的转账接口
4. 用户在不知情的情况下,造成了财产损失
仅仅访问一个不安全的站点就让我们造成了财产损失事实上真正的CSRF攻击并没有这么简单挡在攻击者第一道门槛便是`CORS同源策略`
同源策略限制了js在`b.com`中只能操作`b.com`的数据(`Cookie`、`localStorage`、`DOM`等),而无法直接操作`a.com`的数据。
这就导致一个结果:虽然在`b.com`可以调用`a.com`的转账接口,但是却无法携带用户储存在`a.com`的授权Cookie对于`a.com`来讲,即使收到了浏览器发来的请求,
也因为请求中没有携带token令牌而无法识别调用者具体是谁只能将其视为一次无效调用。
但是,请注意!这个小小的限制,虽然为用户提供了安全保护,却也为我们开发者造成了不小的困扰,特别是在前后端分离架构下,我们经常会遇到这个错误:
![https://oss.dev33.cn/sa-token/doc/kuayu.png](https://oss.dev33.cn/sa-token/doc/kuayu.png)
这就是导致无数前端后端互相撕逼的 —— 跨域!
浏览器在检测到你身处`b.com`却想要调用`a.com`的接口时,会率先发送一个`OPTIONS`预检请求,目的是为了询问`a.com`是否同意`b.com`发起的请求,
在默认情况下,`a.com`收到一个陌生的第三方网站发来的请求,做出的回应肯定是:`不允许`, 浏览器收到回应就识相的关闭了请求,跨域请求失败
但是,有些开发者为了省事,直接设置了响应头: `Access-Control-Allow-Origin: *`,允许任何第三方网站的请求,这就给`CSRF攻击`留下了可乘之机
假设我们设置了只允许指定的网站跨域请求就万事大吉了吗并没有攻击者仍可以通过抓包等手段得到我们的转账url
在`b.com`里,通过`open(url)`直接打开一个新的窗口调用转账接口,由于是属于打开新页面,浏览器连`OPTIONS`预检请求都不会发送,而是直接调用接口成功
此招无解吗?并不。由于它是属于打开新页面,这就导致调用接口时只能发送`get请求`,我们在设计接口时只需要遵守一个准则:`敏感接口一律post禁止get调用`即可。
究其原因,导致`CSRF攻击`频频发生的原因是什么?是我们在跨域请求时,浏览器总是“自作聪明”的自动提交主站`Cookie`
在浏览器的不断更新中Cookie的跨域规则变得愈发复杂新手开发者及其容易绕的晕头转向而同时`w3c`对`Cookie`规范的各种修修补补又总是解决一个问题的同时暴露出其他的N多问题
既然Cookie机制如此难以驾驭我们何不果断的放弃Cookie机制改用`localStorage`机制存储会话token这种方式轻松避免了`自动提交`带来的各种安全问题。
事实上,`localStorage存储` + `header请求头提交`也是前后台分离趋势下的常见会话处理方案
篇幅有限,我们总结一下防范`CSRF攻击`要点
**在Cookie模式下**
1. 仅靠`CORS同源策略`无法彻底防范`CSRF攻击`,我们必须在后台建立`第三方域名白名单`,只有在白名单中的第三方域名才可以跨域调用我们的接口
2. 涉及到数据增删改类型的接口,必须是`post`模式(或其它),严禁`get`模式,查询接口可以`get`
3. 敏感操作增加验证码或者二次验证密码,减小被攻击的概率
**在 localStorage存储 + header请求头提交 模式下**
1. 在配置文件中,配置`is-read-cookie: false`关闭Cookie模式防止Sa-Token在登录时注入Cookie
2. 同上,敏感操作增加验证码或者二次验证密码,减小被攻击的概率
有关CSRF攻防讲解的比较通透的一篇文章[https://juejin.cn/post/6844903689702866952](https://juejin.cn/post/6844903689702866952)
### Token泄露
Token泄露一般发生在客户端比如用户连接了不安全的WiFi导致通讯被监听虽然此情形下token泄露的责任在于用户但是我们还是有必要采取一定的措施使其损失降到最低
1. 有条件上https的话一律https
2. token有效期一定不能设置为永久而且要尽量的短but为了不影响用户体验又不能设置特别的短so: 7-30天是个比较合适的范围
3. 对敏感操作接口,增加密码二次校验(或手机验证码等)
4. 用户更改密码后使其历史会话直接过期
5. 有条件的情况下后台管理增加踢人下线功能对已经泄露token的账号可以及时清理下线
<br><br>
更多类型漏洞连载中.... 欢迎提交pr

View File

@ -15,7 +15,7 @@
<a href="/">
<div class="logo-box">
<img src="logo.png" title="logo" />
<h1 class="logo-text">sa-token</h1>
<h1 class="logo-text">Sa-Token</h1>
</div>
</a>
<nav>
@ -47,7 +47,7 @@
</div>
<script>
var name = '<img style="width: 60px; height: 60px; vertical-align: middle;" src="logo.png" alt="logo" /> ';
name += '<b style="font-size: 28px; vertical-align: middle;">sa-token</b> <sub>v1.17.0</sub>'
name += '<b style="font-size: 28px; vertical-align: middle;">Sa-Token</b> <sub>v1.17.0</sub>'
window.$docsify = {
name: name, // 名字
repo: 'https://github.com/dromara/sa-token', // github地址

View File

@ -82,7 +82,7 @@ body{font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu
}
/* 代码行号盒子样式 */
.code-line-box {list-style-type: none; border-right: 1px solid #000; position: absolute; top: 0; left: 0; width: 40px;}
.code-line-box {list-style-type: none; border-right: 1px solid #000; position: absolute; top: 0; left: 0; width: 40px; user-select: none;}
.code-line-box {padding: calc(1.5em + 1px) 0px !important; padding-bottom: calc(1.5em + 20px) !important; margin: 0px !important;}
.code-line-box {line-height: inherit !important; background-color: #111; color: #aaa;font-weight: 400;font-size: 0.85em;text-align: center;}
.code-line-box {font-family: source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace;}

View File

@ -7,6 +7,11 @@
<table class="gzh-table" style="text-align: center;">
<tr>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=MzA4MDMyODg4OQ==&mid=2649482871&idx=2&sn=b376585faaf814d9072af539efda68fe&send_time="/>
<b>大侠学JAVA</b>
<span>道阻且长行则将至专注分享JAVA领域的干货</span>
</td>
<td>
<img src="https://mp.weixin.qq.com/mp/qrcode?scene=10000005&size=102&__biz=Mzg2MDIxNjAzNg==&mid=2247485810&idx=1&sn=afd46d5924afbc1030a87b5d56265fdf&send_time="/>
<b>Java大厂面试官</b>
@ -19,7 +24,6 @@
</td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>

View File

@ -1,7 +1,7 @@
# Spring WebFlux 集成 Sa-Token 示例
WebFlux基于Reactor响应式模型开发有着与标准ServletAPI完全不同的底层架构因此要适配WebFlux, 必须提供与Reactor相关的整合实现
本篇将以WebFlux为例展示sa-token与Reactor响应式模型web框架相整合的示例, **你可以用同样方式去对接其它Reactor模型Web框架**
本篇将以WebFlux为例展示sa-token与Reactor响应式模型web框架相整合的示例, **你可以用同样方式去对接其它Reactor模型Web框架**(Netty、Soul、Gateway等)
整合示例在官方仓库的`/sa-token-demo-webflux`文件夹下,如遇到难点可结合源码进行测试学习

View File

@ -35,13 +35,10 @@ public class SaTokenConfigure {
public SaServletFilter getSaReactorFilter() {
return new SaServletFilter()
// 指定 [拦截路由]
.addInclude("/**")
// 指定 拦截路由 与 放行路由
.addInclude("/**").addExclude("/favicon.ico")
// 指定 [放行路由]
.addExclude("/favicon.ico")
// 指定[认证函数]: 每次请求执行
// 认证函数: 每次请求执行
.setAuth(r -> {
System.out.println("---------- 进入sa-token全局认证 -----------");
@ -51,11 +48,26 @@ public class SaTokenConfigure {
// 更多拦截处理方式,请参考“路由拦截式鉴权”章节
})
// 指定[异常处理函数]:每次[认证函数]发生异常时执行此函数
// 异常处理函数:每次认证函数发生异常时执行此函数
.setError(e -> {
System.out.println("---------- 进入sa-token异常处理 -----------");
return AjaxJson.getError(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-Frame-Options", "1; mode=block")
// 禁用浏览器内容嗅探
.setHeader("X-Content-Type-Options", "nosniff")
;
})
;
}

View File

@ -117,6 +117,29 @@ StpUtil.checkRoleOr("super-admin", "shop-admin");
你可以创建一个全局异常拦截器,统一返回给前端的格式,参考:[码云GlobalException.java](https://gitee.com/dromara/sa-token/blob/master/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java)
### 权限通配符
Sa-Token允许你根据通配符指定泛权限例如当一个账号拥有`user*`的权限时,`user-add`、`user-delete`、`user-update`都将匹配通过
``` java
// 当拥有 user* 权限时
StpUtil.hasPermission("user-add"); // true
StpUtil.hasPermission("user-update"); // true
StpUtil.hasPermission("art-add"); // false
// 当拥有 *-delete 权限时
StpUtil.hasPermission("user-add"); // false
StpUtil.hasPermission("user-delete"); // true
StpUtil.hasPermission("art-delete"); // true
// 当拥有 *.js 权限时
StpUtil.hasPermission("index.js"); // true
StpUtil.hasPermission("index.css"); // false
StpUtil.hasPermission("index.html"); // false
```
上帝权限:当一个账号拥有 `"*"` 权限时,他可以验证通过任何权限码 (角色认证同理)
### 如何把权限精确搭到按钮级?
权限精确到按钮级的意思就是指:**权限范围可以控制到页面上的每一个按钮是否显示**

View File

@ -5,7 +5,7 @@ import java.net.URL;
import java.util.Arrays;
import java.util.List;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.exception.SaTokenException;
import cn.dev33.satoken.oauth2.SaOAuth2Manager;
import cn.dev33.satoken.oauth2.model.AccessTokenModel;
@ -261,15 +261,15 @@ public interface SaOAuth2Interface {
// 将此授权码保存到DB
long codeTimeout = SaOAuth2Manager.getConfig().getCodeTimeout();
SaTokenManager.getSaTokenDao().setObject(getKeyCodeModel(code), codeModel, codeTimeout);
SaManager.getSaTokenDao().setObject(getKeyCodeModel(code), codeModel, codeTimeout);
// 如果此[Client&账号]已经有code正在存储则先删除它
String key = getKeyClientLoginId(loginId, clientId);
SaTokenManager.getSaTokenDao().delete(key);
SaManager.getSaTokenDao().delete(key);
// 将此[Client&账号]的最新授权码保存到DB中
// 以便于完成授权码覆盖操作: 保证每次只有最新的授权码有效
SaTokenManager.getSaTokenDao().set(key, code, codeTimeout);
SaManager.getSaTokenDao().set(key, code, codeTimeout);
// 返回
return codeModel;
@ -281,7 +281,7 @@ public interface SaOAuth2Interface {
* @return 授权码Model
*/
public default CodeModel getCode(String code) {
return (CodeModel)SaTokenManager.getSaTokenDao().getObject(getKeyCodeModel(code));
return (CodeModel)SaManager.getSaTokenDao().getObject(getKeyCodeModel(code));
}
/**
@ -290,7 +290,7 @@ public interface SaOAuth2Interface {
* @param codeModel 授权码Model
*/
public default void updateCode(String code, CodeModel codeModel) {
SaTokenManager.getSaTokenDao().updateObject(getKeyCodeModel(code), codeModel);
SaManager.getSaTokenDao().updateObject(getKeyCodeModel(code), codeModel);
}
/**
@ -314,7 +314,7 @@ public interface SaOAuth2Interface {
* @param code 授权码
*/
public default void deleteCode(String code) {
SaTokenManager.getSaTokenDao().deleteObject(getKeyCodeModel(code));
SaManager.getSaTokenDao().deleteObject(getKeyCodeModel(code));
}
@ -337,10 +337,10 @@ public interface SaOAuth2Interface {
// 获取 TokenModel 并保存
AccessTokenModel tokenModel = converCodeToAccessToken(codeModel);
SaTokenManager.getSaTokenDao().setObject(getKeyAccessToken(tokenModel.getAccessToken()), tokenModel, SaOAuth2Manager.getConfig().getAccessTokenTimeout());
SaManager.getSaTokenDao().setObject(getKeyAccessToken(tokenModel.getAccessToken()), tokenModel, SaOAuth2Manager.getConfig().getAccessTokenTimeout());
// 将此 CodeModel 当做 refresh_token 保存下来
SaTokenManager.getSaTokenDao().setObject(getKeyRefreshToken(tokenModel.getRefreshToken()), codeModel, SaOAuth2Manager.getConfig().getRefreshTokenTimeout());
SaManager.getSaTokenDao().setObject(getKeyRefreshToken(tokenModel.getRefreshToken()), codeModel, SaOAuth2Manager.getConfig().getRefreshTokenTimeout());
// 返回
return tokenModel;
@ -352,7 +352,7 @@ public interface SaOAuth2Interface {
* @return AccessTokenModel (授权码Model)
*/
public default AccessTokenModel getAccessToken(String accessToken) {
return (AccessTokenModel)SaTokenManager.getSaTokenDao().getObject(getKeyAccessToken(accessToken));
return (AccessTokenModel)SaManager.getSaTokenDao().getObject(getKeyAccessToken(accessToken));
}
/**
@ -369,7 +369,7 @@ public interface SaOAuth2Interface {
// 获取新的 AccessToken 并保存
AccessTokenModel tokenModel = converCodeToAccessToken(codeModel);
tokenModel.setRefreshToken(refreshToken);
SaTokenManager.getSaTokenDao().setObject(getKeyAccessToken(tokenModel.getAccessToken()), tokenModel, SaOAuth2Manager.getConfig().getAccessTokenTimeout());
SaManager.getSaTokenDao().setObject(getKeyAccessToken(tokenModel.getAccessToken()), tokenModel, SaOAuth2Manager.getConfig().getAccessTokenTimeout());
// 返回
return tokenModel;
@ -381,7 +381,7 @@ public interface SaOAuth2Interface {
* @return RefreshToken (授权码Model)
*/
public default CodeModel getRefreshToken(String refreshToken) {
return (CodeModel)SaTokenManager.getSaTokenDao().getObject(getKeyRefreshToken(refreshToken));
return (CodeModel)SaManager.getSaTokenDao().getObject(getKeyRefreshToken(refreshToken));
}
/**
@ -390,7 +390,7 @@ public interface SaOAuth2Interface {
* @return 有效期
*/
public default long getAccessTokenExpiresIn(String accessToken) {
return SaTokenManager.getSaTokenDao().getObjectTimeout(getKeyAccessToken(accessToken));
return SaManager.getSaTokenDao().getObjectTimeout(getKeyAccessToken(accessToken));
}
/**
@ -399,7 +399,7 @@ public interface SaOAuth2Interface {
* @return 有效期
*/
public default long getRefreshTokenExpiresIn(String refreshToken) {
return SaTokenManager.getSaTokenDao().getObjectTimeout(getKeyRefreshToken(refreshToken));
return SaManager.getSaTokenDao().getObjectTimeout(getKeyRefreshToken(refreshToken));
}
/**
@ -519,7 +519,7 @@ public interface SaOAuth2Interface {
* @return key
*/
public default String getKeyCodeModel(String code) {
return SaTokenManager.getConfig().getTokenName() + ":oauth2:code:" + code;
return SaManager.getConfig().getTokenName() + ":oauth2:code:" + code;
}
/**
@ -529,7 +529,7 @@ public interface SaOAuth2Interface {
* @return key
*/
public default String getKeyClientLoginId(Object loginId, String clientId) {
return SaTokenManager.getConfig().getTokenName() + ":oauth2:newest-code:" + clientId + ":" + loginId;
return SaManager.getConfig().getTokenName() + ":oauth2:newest-code:" + clientId + ":" + loginId;
}
/**
@ -538,7 +538,7 @@ public interface SaOAuth2Interface {
* @return key
*/
public default String getKeyRefreshToken(String refreshToken) {
return SaTokenManager.getConfig().getTokenName() + ":oauth2:refresh-token:" + refreshToken;
return SaManager.getConfig().getTokenName() + ":oauth2:refresh-token:" + refreshToken;
}
/**
@ -547,7 +547,7 @@ public interface SaOAuth2Interface {
* @return key
*/
public default String getKeyAccessToken(String accessToken) {
return SaTokenManager.getConfig().getTokenName() + ":oauth2:access-token:" + accessToken;
return SaManager.getConfig().getTokenName() + ":oauth2:access-token:" + accessToken;
}

View File

@ -95,7 +95,7 @@ public class SaReactorFilter implements WebFilter {
}
// ------------------------ 执行函数
// ------------------------ 钩子函数
/**
* 认证函数每次请求执行
@ -109,6 +109,11 @@ public class SaReactorFilter implements WebFilter {
throw new SaTokenException(e);
};
/**
* 前置函数在每次[认证函数]之前执行
*/
public SaFilterAuthStrategy beforeAuth = r -> {};
/**
* 写入[认证函数]: 每次请求执行
* @param auth see note
@ -129,6 +134,16 @@ public class SaReactorFilter implements WebFilter {
return this;
}
/**
* 写入[前置函数]在每次[认证函数]之前执行
* @param auth see note
* @return 对象自身
*/
public SaReactorFilter setBeforeAuth(SaFilterAuthStrategy beforeAuth) {
this.beforeAuth = beforeAuth;
return this;
}
// ------------------------ filter
@ -140,7 +155,10 @@ public class SaReactorFilter implements WebFilter {
SaReactorSyncHolder.setContent(exchange);
// 执行全局过滤器
SaRouterUtil.match(includeList, excludeList, () -> auth.run(null));
SaRouterUtil.match(includeList, excludeList, () -> {
beforeAuth.run(null);
auth.run(null);
});
} catch (Throwable e) {
// 1. 获取异常处理策略结果

View File

@ -63,11 +63,18 @@ public class SaRequestForReactor implements SaRequest {
}
/**
* 返回当前请求的URL
* 返回当前请求path (不包括上下文名称)
*/
@Override
public String getRequestURI() {
public String getRequestPath() {
return request.getURI().getPath();
}
/**
* 返回当前请求的类型
*/
@Override
public String getMethod() {
return request.getMethodValue();
}
}

View File

@ -71,4 +71,13 @@ public class SaResponseForReactor implements SaResponse {
response.addCookie(builder.build());
}
/**
* 在响应头里写入一个值
*/
@Override
public SaResponse setHeader(String name, String value) {
response.getHeaders().set(name, value);
return this;
}
}

View File

@ -6,7 +6,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
@ -42,7 +42,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired
public void setConfig(SaTokenConfig saTokenConfig) {
SaTokenManager.setConfig(saTokenConfig);
SaManager.setConfig(saTokenConfig);
}
/**
@ -52,7 +52,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setSaTokenDao(SaTokenDao saTokenDao) {
SaTokenManager.setSaTokenDao(saTokenDao);
SaManager.setSaTokenDao(saTokenDao);
}
/**
@ -62,7 +62,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setStpInterface(StpInterface stpInterface) {
SaTokenManager.setStpInterface(stpInterface);
SaManager.setStpInterface(stpInterface);
}
/**
@ -72,7 +72,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setSaTokenAction(SaTokenAction saTokenAction) {
SaTokenManager.setSaTokenAction(saTokenAction);
SaManager.setSaTokenAction(saTokenAction);
}
/**
@ -100,7 +100,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired
public void setSaTokenContext(SaTokenContext saTokenContext) {
SaTokenManager.setSaTokenContext(saTokenContext);
SaManager.setSaTokenContext(saTokenContext);
}
/**
@ -110,7 +110,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setSaTokenListener(SaTokenListener saTokenListener) {
SaTokenManager.setSaTokenListener(saTokenListener);
SaManager.setSaTokenListener(saTokenListener);
}
/**

View File

@ -66,11 +66,19 @@ public class SaRequestForServlet implements SaRequest {
}
/**
* 返回当前请求的URL
* 返回当前请求path (不包括上下文名称)
*/
@Override
public String getRequestURI() {
return request.getRequestURI();
public String getRequestPath() {
return request.getServletPath();
}
/**
* 返回当前请求的类型
*/
@Override
public String getMethod() {
return request.getMethod();
}
}

View File

@ -59,4 +59,14 @@ public class SaResponseForServlet implements SaResponse {
response.addCookie(cookie);
}
/**
* 在响应头里写入一个值
*/
@Override
public SaResponse setHeader(String name, String value) {
response.setHeader(name, value);
return this;
}
}

View File

@ -95,7 +95,7 @@ public class SaServletFilter implements Filter {
}
// ------------------------ 执行函数
// ------------------------ 钩子函数
/**
* 认证函数每次请求执行
@ -109,6 +109,11 @@ public class SaServletFilter implements Filter {
throw new SaTokenException(e);
};
/**
* 前置函数在每次[认证函数]之前执行
*/
public SaFilterAuthStrategy beforeAuth = r -> {};
/**
* 写入[认证函数]: 每次请求执行
* @param auth see note
@ -129,6 +134,16 @@ public class SaServletFilter implements Filter {
return this;
}
/**
* 写入[前置函数]在每次[认证函数]之前执行
* @param auth see note
* @return 对象自身
*/
public SaServletFilter setBeforeAuth(SaFilterAuthStrategy beforeAuth) {
this.beforeAuth = beforeAuth;
return this;
}
// ------------------------ doFilter
@ -138,7 +153,10 @@ public class SaServletFilter implements Filter {
try {
// 执行全局过滤器
SaRouterUtil.match(includeList, excludeList, () -> auth.run(null));
SaRouterUtil.match(includeList, excludeList, () -> {
beforeAuth.run(null);
auth.run(null);
});
} catch (Throwable e) {
// 1. 获取异常处理策略结果

View File

@ -6,7 +6,7 @@ import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.util.PathMatcher;
import cn.dev33.satoken.SaTokenManager;
import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.action.SaTokenAction;
import cn.dev33.satoken.config.SaTokenConfig;
import cn.dev33.satoken.context.SaTokenContext;
@ -41,7 +41,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired
public void setConfig(SaTokenConfig saTokenConfig) {
SaTokenManager.setConfig(saTokenConfig);
SaManager.setConfig(saTokenConfig);
}
/**
@ -51,7 +51,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setSaTokenDao(SaTokenDao saTokenDao) {
SaTokenManager.setSaTokenDao(saTokenDao);
SaManager.setSaTokenDao(saTokenDao);
}
/**
@ -61,7 +61,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setStpInterface(StpInterface stpInterface) {
SaTokenManager.setStpInterface(stpInterface);
SaManager.setStpInterface(stpInterface);
}
/**
@ -71,7 +71,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setSaTokenAction(SaTokenAction saTokenAction) {
SaTokenManager.setSaTokenAction(saTokenAction);
SaManager.setSaTokenAction(saTokenAction);
}
/**
@ -91,7 +91,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired
public void setSaTokenContext(SaTokenContext saTokenContext) {
SaTokenManager.setSaTokenContext(saTokenContext);
SaManager.setSaTokenContext(saTokenContext);
}
/**
@ -101,7 +101,7 @@ public class SaTokenSpringAutowired {
*/
@Autowired(required = false)
public void setSaTokenListener(SaTokenListener saTokenListener) {
SaTokenManager.setSaTokenListener(saTokenListener);
SaManager.setSaTokenListener(saTokenListener);
}
/**