diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java b/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java index dbe90b4f..f57b82f6 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/SaTokenManager.java @@ -160,12 +160,7 @@ public class SaTokenManager { * @return 对应的StpLogic */ public static StpLogic getStpLogic(String loginKey) { - for (String key : stpLogicMap.keySet()) { - if(key.equals(loginKey)) { - return stpLogicMap.get(key); - } - } - return null; + return stpLogicMap.get(loginKey); } diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteFunction.java b/sa-token-core/src/main/java/cn/dev33/satoken/router/SaRouteFunction.java similarity index 91% rename from sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteFunction.java rename to sa-token-core/src/main/java/cn/dev33/satoken/router/SaRouteFunction.java index a34554a7..c4f2af40 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteFunction.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/router/SaRouteFunction.java @@ -1,4 +1,4 @@ -package cn.dev33.satoken.interceptor; +package cn.dev33.satoken.router; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouterUtil.java b/sa-token-core/src/main/java/cn/dev33/satoken/router/SaRouterUtil.java similarity index 80% rename from sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouterUtil.java rename to sa-token-core/src/main/java/cn/dev33/satoken/router/SaRouterUtil.java index e52b545e..78a99142 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouterUtil.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/router/SaRouterUtil.java @@ -1,13 +1,9 @@ -package cn.dev33.satoken.interceptor; +package cn.dev33.satoken.router; import java.util.Arrays; import java.util.List; -import org.springframework.util.AntPathMatcher; -import org.springframework.util.PathMatcher; - import cn.dev33.satoken.SaTokenManager; -import cn.dev33.satoken.autowired.SaTokenSpringAutowired; import cn.dev33.satoken.fun.IsRunFunction; import cn.dev33.satoken.fun.SaFunction; @@ -18,32 +14,6 @@ import cn.dev33.satoken.fun.SaFunction; */ public class SaRouterUtil { - /** - * 在进行路由匹配时所使用的 PathMatcher 对象 - */ - private static PathMatcher pathMatcher; - - /** - * @return 在进行路由匹配时所使用的的 PathMatcher 对象 - */ - public static PathMatcher getPathMatcher() { - if(pathMatcher == null) { - pathMatcher = SaTokenSpringAutowired.pathMatcher; - if(pathMatcher == null) { - pathMatcher = new AntPathMatcher(); - } - } - return pathMatcher; - } - - /** - * @param pathMatcher 写入: 在进行路由匹配时所使用的的 PathMatcher 对象 - */ - public static void setPathMatcher(PathMatcher pathMatcher) { - SaRouterUtil.pathMatcher = pathMatcher; - } - - // -------------------- 路由匹配相关 -------------------- /** @@ -53,10 +23,7 @@ public class SaRouterUtil { * @return 是否匹配成功 */ public static boolean isMatch(String pattern, String path) { - if(getPathMatcher().match(pattern, path)) { - return true; - } - return false; + return SaTokenManager.getSaTokenServlet().matchPath(pattern, path); } /** diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServlet.java b/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServlet.java index a169c5d9..4e1e8851 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServlet.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServlet.java @@ -25,4 +25,12 @@ public interface SaTokenServlet { */ public HttpServletResponse getResponse(); + /** + * 校验指定路由匹配符是否可以匹配成功指定路径 + * @param pattern 路由匹配符 + * @param path 需要匹配的路径 + * @return 是否匹配成功 + */ + public boolean matchPath(String pattern, String path); + } diff --git a/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServletDefaultImpl.java b/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServletDefaultImpl.java index f6fce841..be816bd2 100644 --- a/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServletDefaultImpl.java +++ b/sa-token-core/src/main/java/cn/dev33/satoken/servlet/SaTokenServletDefaultImpl.java @@ -18,7 +18,7 @@ public class SaTokenServletDefaultImpl implements SaTokenServlet { */ @Override public HttpServletRequest getRequest() { - throw new SaTokenException("请实现SaTokenServlet接口后进行Servlet相关操作"); + throw new SaTokenException("SaTokenServlet接口未实现"); } /** @@ -26,7 +26,16 @@ public class SaTokenServletDefaultImpl implements SaTokenServlet { */ @Override public HttpServletResponse getResponse() { - throw new SaTokenException("请实现SaTokenServlet接口后进行Servlet相关操作"); + throw new SaTokenException("SaTokenServlet接口未实现"); + } + + + /** + * 校验指定路由匹配符是否可以匹配成功指定路径 + */ + @Override + public boolean matchPath(String pattern, String path) { + throw new SaTokenException("SaTokenServlet接口未实现"); } } diff --git a/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java b/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java index 2592f35b..ef779dad 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/satoken/MySaTokenConfig.java @@ -4,7 +4,6 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; -import cn.dev33.satoken.config.SaTokenConfig; import cn.dev33.satoken.interceptor.SaAnnotationInterceptor; /** @@ -15,20 +14,6 @@ import cn.dev33.satoken.interceptor.SaAnnotationInterceptor; @Configuration public class MySaTokenConfig implements WebMvcConfigurer { - // 获取配置Bean (以代码的方式配置sa-token, 此配置会覆盖yml中的配置 ) -// @Primary -// @Bean(name="MySaTokenConfig") - public SaTokenConfig getSaTokenConfig() { - SaTokenConfig config = new SaTokenConfig(); - config.setTokenName("satoken"); // token名称 (同时也是cookie名称) - config.setTimeout(30 * 24 * 60 * 60); // token有效期,单位s 默认30天 - config.setActivityTimeout(-1); // token临时有效期 (指定时间内无操作就视为token过期) 单位: 秒 - config.setAllowConcurrentLogin(true); // 是否允许同一账号并发登录 (为true时允许一起登录, 为false时新登录挤掉旧登录) - config.setIsShare(true); // 在多人登录同一账号时,是否共用一个token (为true时所有登录共用一个token, 为false时每次登录新建一个token) - config.setTokenStyle("uuid"); // token风格 - return config; - } - // 注册sa-token的拦截器,打开注解式鉴权功能 @Override public void addInterceptors(InterceptorRegistry registry) { diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java b/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java index ed89978f..be3ccabf 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/GlobalException.java @@ -5,9 +5,10 @@ import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.bind.annotation.ResponseBody; import com.pj.util.AjaxJson; @@ -18,7 +19,7 @@ import cn.dev33.satoken.exception.NotRoleException; /** * 全局异常处理 */ -@RestControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin") +@ControllerAdvice // 可指定包前缀,比如:(basePackages = "com.pj.admin") public class GlobalException { // 在当前类每个方法进入之前触发的操作 @@ -28,14 +29,14 @@ public class GlobalException { } - - // 全局异常拦截(拦截项目中的所有异常) + @ResponseBody @ExceptionHandler public AjaxJson handlerException(Exception e, HttpServletRequest request, HttpServletResponse response) throws Exception { // 打印堆栈,以供调试 + System.out.println("全局异常---------------"); e.printStackTrace(); // 不同异常返回不同状态码 diff --git a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java index ce27d2b5..229332f8 100644 --- a/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java +++ b/sa-token-demo-springboot/src/main/java/com/pj/test/TestController.java @@ -3,8 +3,6 @@ package com.pj.test; import java.util.Date; import java.util.List; -import javax.servlet.http.HttpServletResponse; - import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -241,7 +239,7 @@ public class TestController { // 测试 浏览器访问: http://localhost:8081/test/test @RequestMapping("test") - public AjaxJson test(HttpServletResponse response) { + public AjaxJson test() { System.out.println("进来了"); // StpUtil.setLoginId(10001, new SaLoginModel() // .setDevice("PC") // 此次登录的客户端设备标识, 用于[同端互斥登录]时指定此次登录的设备名称 diff --git a/sa-token-doc/doc/_sidebar.md b/sa-token-doc/doc/_sidebar.md index eaad48cf..71c8fea7 100644 --- a/sa-token-doc/doc/_sidebar.md +++ b/sa-token-doc/doc/_sidebar.md @@ -17,6 +17,7 @@ - [注解式鉴权](/use/at-check) - [路由拦截式鉴权](/use/route-check) - [花式token](/use/token-style) + - [Token前缀](/use/token-prefix) - [框架配置](/use/config) - [会话治理](/use/search-session) - [记住我模式](/use/remember-me) diff --git a/sa-token-doc/doc/use/config.md b/sa-token-doc/doc/use/config.md index 3e7044cc..6e539475 100644 --- a/sa-token-doc/doc/use/config.md +++ b/sa-token-doc/doc/use/config.md @@ -71,4 +71,5 @@ public class MySaTokenConfig { | dataRefreshPeriod | int | 30 | 默认dao层实现类中,每次清理过期数据间隔的时间 (单位: 秒) ,默认值30秒,设置为-1代表不启动定时清理 | | tokenSessionCheckLogin | Boolean | true | 获取token专属session时是否必须登录 (如果配置为true,会在每次获取token专属session时校验是否登录) | | autoRenew | Boolean | true | 是否打开自动续签 (如果此值为true, 框架会在每次直接或间接调用getLoginId()时进行一次过期检查与续签操作) | +| tokenPrefix | Boolean | true | token前缀, 格式样例(satoken: Bearer xxxx-xxxx-xxxx-xxxx) [参考:token前缀](/use/token-prefix) | | isV | Boolean | true | 是否在初始化配置时打印版本字符画 | diff --git a/sa-token-doc/doc/use/route-check.md b/sa-token-doc/doc/use/route-check.md index e336a696..e9d87b0d 100644 --- a/sa-token-doc/doc/use/route-check.md +++ b/sa-token-doc/doc/use/route-check.md @@ -99,8 +99,18 @@ public class MySaTokenConfig implements WebMvcConfigurer { +## 注意事项 +在`v1.14`及以前版本下,路由拦截器提供了提供了封装式写法,该方法代码比较冗余,在`v1.15`版本已移除,解决方案如下: +``` java +// 原写法 +registry.addInterceptor(SaRouteInterceptor.createPermissionVal("user")).addPathPatterns("/user/**"); - - - +// 改为以下方式,效果同上 +registry.addInterceptor(new SaRouteInterceptor((request, response, handler) -> { + SaRouterUtil.match("/user/**", () -> StpUtil.checkPermission("user")); +})).addPathPatterns("/**"); +``` + + + \ No newline at end of file diff --git a/sa-token-doc/doc/use/token-prefix.md b/sa-token-doc/doc/use/token-prefix.md new file mode 100644 index 00000000..f1313331 --- /dev/null +++ b/sa-token-doc/doc/use/token-prefix.md @@ -0,0 +1,32 @@ +# Token前缀 + +### 需求场景 + +在某些系统中,前端提交token时会在前面加个固定的前缀,例如: + +``` js +{ + "satoken": "Bearer xxxx-xxxx-xxxx-xxxx" +} +``` + +此时后端如果不做任何特殊处理,框架将会把`Bearer `视为token的一部分,无法正常读取token信息,导致鉴权失败 + +为此,我们需要在yml中添加如下配置: +``` java +spring: + # sa-token配置 + sa-token: + # token前缀 + tokenPrefix: Bearer +``` + +此时 sa-token 便可在读取token时裁剪掉 `Bearer`,成功获取`xxxx-xxxx-xxxx-xxxx` + + +### 注意点 + +1. `token前缀` 与 `token值` 之间必须有一个空格 +2. 一旦配置了`token前缀`,则前端提交token时,必须带有前缀,否则会导致框架无法读取token +3. 由于`Cookie`中无法存储空格字符,也就意味配置token前缀后,`Cookie`鉴权方式将会失效,此时只能将token提交到`header`里进行传输 + diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/autowired/SaTokenSpringAutowired.java b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/autowired/SaTokenSpringAutowired.java index e6102713..67cf9740 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/autowired/SaTokenSpringAutowired.java +++ b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/autowired/SaTokenSpringAutowired.java @@ -105,19 +105,15 @@ public class SaTokenSpringAutowired { SaTokenManager.setSaTokenServlet(saTokenServlet); } - /** - * 路由匹配器 - */ - public static PathMatcher pathMatcher; - /** * 利用自动匹配特性,获取SpringMVC框架内部使用的路由匹配器 * * @param pathMatcher 要设置的 pathMatcher */ @Autowired(required = false) - public static void setPathMatcher(PathMatcher pathMatcher) { - SaTokenSpringAutowired.pathMatcher = pathMatcher; + public void setPathMatcher(PathMatcher pathMatcher) { + SaTokenServletSpringImpl.setPathMatcher(pathMatcher); } + } diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java index 5de0466a..49545d23 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java +++ b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/interceptor/SaRouteInterceptor.java @@ -5,8 +5,7 @@ import javax.servlet.http.HttpServletResponse; import org.springframework.web.servlet.HandlerInterceptor; -import cn.dev33.satoken.annotation.SaMode; -import cn.dev33.satoken.stp.StpLogic; +import cn.dev33.satoken.router.SaRouteFunction; import cn.dev33.satoken.stp.StpUtil; /** @@ -15,204 +14,25 @@ import cn.dev33.satoken.stp.StpUtil; */ public class SaRouteInterceptor implements HandlerInterceptor { - - // ----------------- 属性 ----------------- - - /** - * 底层的 StpLogic 对象 - */ - private StpLogic stpLogic; - - /** - * 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证) - */ - private int type; - - /** - * 验证模式 AND | OR - */ - private SaMode mode; - - /** - * 标识码数组 - */ - private String[] codes; - /** * 自定义模式下的执行函数 */ - private SaRouteFunction function; - - - - /** - * 表示登录验证 - */ - public static final int LOGIN = 1; - - /** - * 表示角色验证 - */ - public static final int ROLE = 2; - - /** - * 表示权限验证 - */ - public static final int PERMISSION = 3; - - /** - * 表示自定义验证 - */ - public static final int CUSTOM = 4; - - - /** - * @return 底层的 StpLogic 对象 - */ - public StpLogic getStpLogic() { - if(stpLogic == null) { - stpLogic = StpUtil.stpLogic; - } - return stpLogic; - } - - /** - * @param stpLogic 底层的 StpLogic 对象 - * @return 拦截器自身 - */ - public SaRouteInterceptor setStpLogic(StpLogic stpLogic) { - this.stpLogic = stpLogic; - return this; - } - - /** - * @return 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证) - */ - public int getType() { - return type; - } - - /** - * @param type 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证) - * @return 拦截器自身 - */ - public SaRouteInterceptor setType(int type) { - this.type = type; - return this; - } - - /** - * @return 验证模式 AND | OR - */ - public SaMode getMode() { - return mode; - } - - /** - * @param mode 验证模式 AND | OR - * @return 拦截器自身 - */ - public SaRouteInterceptor setMode(SaMode mode) { - this.mode = mode; - return this; - } - - /** - * @return 标识码数组 - */ - public String[] getCodes() { - return codes; - } - - /** - * @param codes 标识码数组 - * @return 拦截器自身 - */ - public SaRouteInterceptor setCodes(String... codes) { - this.codes = codes; - return this; - } - - /** - * @return 自定义模式下的执行函数 - */ - public SaRouteFunction getFunction() { - return function; - } - - /** - * @param function 设置自定义模式下的执行函数 - * @return 拦截器自身 - */ - public SaRouteInterceptor setFunction(SaRouteFunction function) { - this.type = SaRouteInterceptor.CUSTOM; - this.function = function; - return this; - } - - // ----------------- 构建相关 ----------------- - - /** - * 创建 (全参数) - * @param type 验证类型 (1=登录验证, 2=角色验证, 3=权限验证, 4=自定义验证) - * @param mode 验证模式 AND | OR - * @param codes 标识码数组 - */ - public SaRouteInterceptor(int type, SaMode mode, String... codes) { - super(); - this.type = type; - this.mode = mode; - this.codes = codes; - } - - /** - * 创建 (默认为登录验证) - */ - public SaRouteInterceptor() { - this(SaRouteInterceptor.LOGIN, null, new String[0]); - } + public SaRouteFunction function; /** * 创建 (默认为自定义认证) * @param function 自定义模式下的执行函数 */ public SaRouteInterceptor(SaRouteFunction function) { - this(SaRouteInterceptor.CUSTOM, null, new String[0]); - setFunction(function); + this.function = function; } /** - * 构建一个模式为登录认证的sa路由拦截器 - * @return sa拦截器 - */ - public static SaRouteInterceptor createLoginVal() { - return new SaRouteInterceptor(); - } - - /** - * 构建一个模式为角色认证的sa路由拦截器 - * @param roles 需要验证的角色标识列表 - * @return sa拦截器 - */ - public static SaRouteInterceptor createRoleVal(String... roles) { - return new SaRouteInterceptor(SaRouteInterceptor.ROLE, SaMode.AND, roles); - } - - /** - * 构建一个模式为权限认证的sa路由拦截器 - * @param permissions 需要验证的权限列表 - * @return sa拦截器 - */ - public static SaRouteInterceptor createPermissionVal(String... permissions) { - return new SaRouteInterceptor(SaRouteInterceptor.PERMISSION, SaMode.AND, permissions); - } - - /** - * 创建一个模式为自定义认证的sa路由拦截器 + * 静态方法快速构建一个 * @param function 自定义模式下的执行函数 - * @return sa拦截器 + * @return sa路由拦截器 */ - public static SaRouteInterceptor createCustomVal(SaRouteFunction function) { + public static SaRouteInterceptor newInstance(SaRouteFunction function) { return new SaRouteInterceptor(function); } @@ -226,22 +46,10 @@ public class SaRouteInterceptor implements HandlerInterceptor { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { - // 根据模式进行验证 - if(this.type == SaRouteInterceptor.LOGIN) { - getStpLogic().checkLogin(); - } else if(this.type == SaRouteInterceptor.ROLE) { - if(mode == SaMode.AND) { - getStpLogic().checkRoleAnd(codes); - } else { - getStpLogic().checkRoleOr(codes); - } - } else if(this.type == SaRouteInterceptor.PERMISSION) { - if(mode == SaMode.AND) { - getStpLogic().checkPermissionAnd(codes); - } else { - getStpLogic().checkPermissionOr(codes); - } - } else if(this.type == SaRouteInterceptor.CUSTOM) { + // 如果未提供function,默认进行登录验证 + if(function == null) { + StpUtil.checkLogin(); + } else { function.run(request, response, handler); } @@ -250,6 +58,4 @@ public class SaRouteInterceptor implements HandlerInterceptor { } - - } diff --git a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenServletSpringImpl.java b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenServletSpringImpl.java index e9e7ca70..111026d3 100644 --- a/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenServletSpringImpl.java +++ b/sa-token-spring-boot-starter/src/main/java/cn/dev33/satoken/spring/SaTokenServletSpringImpl.java @@ -3,10 +3,13 @@ package cn.dev33.satoken.spring; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; + import cn.dev33.satoken.servlet.SaTokenServlet; /** - * sa-token 对cookie的相关操作 接口实现类 + * sa-token 对Cookie的相关操作 接口实现类 * * @author kong * @@ -29,4 +32,37 @@ public class SaTokenServletSpringImpl implements SaTokenServlet { return SpringMVCUtil.getResponse(); } + + /** + * 路由匹配器 + */ + private static PathMatcher pathMatcher; + + /** + * 获取路由匹配器 + * @param pathMatcher 路由匹配器 + */ + public static PathMatcher getPathMatcher() { + if(pathMatcher == null) { + pathMatcher = new AntPathMatcher(); + } + return pathMatcher; + } + + /** + * 写入路由匹配器 + * @param pathMatcher 路由匹配器 + */ + public static void setPathMatcher(PathMatcher pathMatcher) { + SaTokenServletSpringImpl.pathMatcher = pathMatcher; + } + + /** + * 校验指定路由匹配符是否可以匹配成功指定路径 + */ + @Override + public boolean matchPath(String pattern, String path) { + return getPathMatcher().match(pattern, path); + } + }