
1.前言
由于之前在项目中使用了sa-token,在使用的过程中发现@SaIgnore注解失效,之前以为是aop切面失效了,不知道失效的真正原因,那本文分分享其失效的真正原因及真确姿势,之前分享文章导读:
https://mp.weixin.qq.com/s/SREjXoyL9s1JfddQnU38yAhttps://blog.csdn.net/qq_34905631/article/details/137821489?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522391dd644a9de17d7bb113aeb20931c55%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=391dd644a9de17d7bb113aeb20931c55&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-3-137821489-null-null.nonecase&utm_term=sa-token&spm=1018.2226.3001.4450https://mp.weixin.qq.com/s/96WbWL28T5_-xzyCfJ7Stghttps://blog.csdn.net/qq_34905631/article/details/140233780?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522391dd644a9de17d7bb113aeb20931c55%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=391dd644a9de17d7bb113aeb20931c55&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-140233780-null-null.nonecase&utm_term=sa-token&spm=1018.2226.3001.4450https://mp.weixin.qq.com/s/bSS4vmKlKM7ov_CUkjxkBghttps://blog.csdn.net/qq_34905631/article/details/140277487?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522391dd644a9de17d7bb113aeb20931c55%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=391dd644a9de17d7bb113aeb20931c55&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-2-140277487-null-null.nonecase&utm_term=sa-token&spm=1018.2226.3001.4450
2.原因
2.1原理




2.2sa-token相关源码
2.2.1 SaServletFilter
该类在:sa-token-core包中,此包只要引入了sa-token的相关包,会自动引入
/** Copyright 2020-2099 sa-token.cc** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package cn.dev33.satoken.filter;import cn.dev33.satoken.error.SaSpringBootErrorCode;import cn.dev33.satoken.exception.BackResultException;import cn.dev33.satoken.exception.SaTokenException;import cn.dev33.satoken.exception.StopMatchException;import cn.dev33.satoken.router.SaRouter;import cn.dev33.satoken.util.SaTokenConsts;import org.springframework.core.annotation.Order;import javax.servlet.*;import java.io.IOException;import java.util.ArrayList;import java.util.Arrays;import java.util.List;/*** Servlet 全局鉴权过滤器** 默认优先级为 -100,尽量保证在其它过滤器之前执行* </p>** @author click33* @since 1.19.0*/@Order(SaTokenConsts.ASSEMBLY_ORDER)public class SaServletFilter implements SaFilter, Filter {// ------------------------ 设置此过滤器 拦截 & 放行 的路由/*** 拦截路由*/public ListincludeList = new ArrayList<>(); /*** 放行路由*/public ListexcludeList = new ArrayList<>(); @Overridepublic SaServletFilter addInclude(String... paths) {includeList.addAll(Arrays.asList(paths));return this;}@Overridepublic SaServletFilter addExclude(String... paths) {excludeList.addAll(Arrays.asList(paths));return this;}@Overridepublic SaServletFilter setIncludeList(ListpathList) { includeList = pathList;return this;}@Overridepublic SaServletFilter setExcludeList(ListpathList) { excludeList = pathList;return this;}// ------------------------ 钩子函数/*** 认证函数:每次请求执行*/public SaFilterAuthStrategy auth = r -> {};/*** 异常处理函数:每次[认证函数]发生异常时执行此函数*/public SaFilterErrorStrategy error = e -> {throw new SaTokenException(e).setCode(SaSpringBootErrorCode.CODE_20105);};/*** 前置函数:在每次[认证函数]之前执行* 注意点:前置认证函数将不受 includeList 与 excludeList 的限制,所有路由的请求都会进入 beforeAuth</b>*/public SaFilterAuthStrategy beforeAuth = r -> {};@Overridepublic SaServletFilter setAuth(SaFilterAuthStrategy auth) {this.auth = auth;return this;}@Overridepublic SaServletFilter setError(SaFilterErrorStrategy error) {this.error = error;return this;}@Overridepublic SaServletFilter setBeforeAuth(SaFilterAuthStrategy beforeAuth) {this.beforeAuth = beforeAuth;return this;}// ------------------------ doFilter@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {try {// 执行全局过滤器beforeAuth.run(null);SaRouter.match(includeList).notMatch(excludeList).check(r -> {auth.run(null);});} catch (StopMatchException e) {// StopMatchException 异常代表:停止匹配,进入Controller} catch (Throwable e) {// 1. 获取异常处理策略结果String result = (e instanceof BackResultException) ? e.getMessage() : String.valueOf(error.run(e));// 2. 写入输出流// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 return 前自行设置 Content-Type 为 application/json// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");if(response.getContentType() == null) {response.setContentType(SaTokenConsts.CONTENT_TYPE_TEXT_PLAIN);}response.getWriter().print(result);return;}// 执行chain.doFilter(request, response);}@Overridepublic void init(FilterConfig filterConfig) {}@Overridepublic void destroy() {}}
2.2.2 SaInterceptor
该类在sa-tonekn-spring-boot-starter依赖中
/** Copyright 2020-2099 sa-token.cc** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package cn.dev33.satoken.interceptor;import cn.dev33.satoken.annotation.SaIgnore;import cn.dev33.satoken.exception.BackResultException;import cn.dev33.satoken.exception.StopMatchException;import cn.dev33.satoken.fun.SaParamFunction;import cn.dev33.satoken.strategy.SaStrategy;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.lang.reflect.Method;/*** Sa-Token 综合拦截器,提供注解鉴权和路由拦截鉴权能力** @author click33* @since 1.31.0*/public class SaInterceptor implements HandlerInterceptor {/*** 是否打开注解鉴权,配置为 true 时注解鉴权才会生效,配置为 false 时,即使写了注解也不会进行鉴权*/public boolean isAnnotation = true;/*** 认证函数:每次请求执行*参数:路由处理函数指针
*/public SaParamFunction/*** 创建一个 Sa-Token 综合拦截器,默认带有注解鉴权能力*/public SaInterceptor() {}/*** 创建一个 Sa-Token 综合拦截器,默认带有注解鉴权能力* @param auth 认证函数,每次请求执行*/public SaInterceptor(SaParamFunctionthis.auth = auth;}/*** 设置是否打开注解鉴权:配置为 true 时注解鉴权才会生效,配置为 false 时,即使写了注解也不会进行鉴权* @param isAnnotation /* @return 对象自身*/public SaInterceptor isAnnotation(boolean isAnnotation) {this.isAnnotation = isAnnotation;return this;}/*** 写入 [ 认证函数 ]: 每次请求执行* @param auth /* @return 对象自身*/public SaInterceptor setAuth(SaParamFunctionthis.auth = auth;return this;}// ----------------- 验证方法 -----------------/*** 每次请求之前触发的方法*/@Override@SuppressWarnings("all")public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {try {// 这里必须确保 handler 是 HandlerMethod 类型时,才能进行注解鉴权if(isAnnotation && handler instanceof HandlerMethod) {// 获取此请求对应的 Method 处理函数Method method = ((HandlerMethod) handler).getMethod();// 如果此 Method 或其所属 Class 标注了 @SaIgnore,则忽略掉鉴权if(SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {// 注意这里直接就退出整个鉴权了,最底部的 auth.run() 路由拦截鉴权也被跳出了return true;}// 执行注解鉴权SaStrategy.instance.checkMethodAnnotation.accept(method);}// Auth 路由拦截鉴权校验auth.run(handler);} catch (StopMatchException e) {// StopMatchException 异常代表:停止匹配,进入Controller} catch (BackResultException e) {// BackResultException 异常代表:停止匹配,向前端输出结果// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 back 前自行设置 Content-Type 为 application/json// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");if(response.getContentType() == null) {response.setContentType("text/plain; charset=utf-8");}response.getWriter().print(e.getMessage());return false;}// 通过验证return true;}}
2.2.3SaCheckAspect
该类是在sa-token-spring-aop包中,此包需要单独引入
/** Copyright 2020-2099 sa-token.cc** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/package cn.dev33.satoken.aop;import java.lang.reflect.Method;import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.aspectj.lang.annotation.Pointcut;import org.aspectj.lang.reflect.MethodSignature;import org.springframework.core.annotation.Order;import org.springframework.stereotype.Component;import cn.dev33.satoken.annotation.SaIgnore;import cn.dev33.satoken.strategy.SaStrategy;import cn.dev33.satoken.util.SaTokenConsts;/*** Sa-Token 基于 Spring Aop 的注解鉴权*** 注意:在打开 注解鉴权 时,AOP 模式与拦截器模式不可同时使用,否则会出现在 Controller 层重复鉴权两次的问题*** @author click33* @since 1.19.0*/public class SaCheckAspect {/*** 构建*/public SaCheckAspect() {}/*** 定义AOP签名 (切入所有使用 Sa-Token 鉴权注解的方法)*/public static final String POINTCUT_SIGN ="@within(cn.dev33.satoken.annotation.SaCheckLogin) || @annotation(cn.dev33.satoken.annotation.SaCheckLogin) || "+ "@within(cn.dev33.satoken.annotation.SaCheckRole) || @annotation(cn.dev33.satoken.annotation.SaCheckRole) || "+ "@within(cn.dev33.satoken.annotation.SaCheckPermission) || @annotation(cn.dev33.satoken.annotation.SaCheckPermission) || "+ "@within(cn.dev33.satoken.annotation.SaCheckSafe) || @annotation(cn.dev33.satoken.annotation.SaCheckSafe) || "+ "@within(cn.dev33.satoken.annotation.SaCheckDisable) || @annotation(cn.dev33.satoken.annotation.SaCheckDisable) || "+ "@within(cn.dev33.satoken.annotation.SaCheckBasic) || @annotation(cn.dev33.satoken.annotation.SaCheckBasic)";/*** 声明AOP签名*/public void pointcut() {}/*** 环绕切入** @param joinPoint 切面对象* @return 底层方法执行后的返回值* @throws Throwable 底层方法抛出的异常*/public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 获取对应的 Method 处理函数MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();// 如果此 Method 或其所属 Class 标注了 @SaIgnore,则忽略掉鉴权if(SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {// ...} else {// 注解鉴权SaStrategy.instance.checkMethodAnnotation.accept(method);}// 执行原有逻辑return joinPoint.proceed();}}
2.2.4 官方文档地址
https://sa-token.cc/v/v1.36.0/doc.html#/plugin/aop-athttps://sa-token.cc/v/v1.36.0/doc.html#/up/global-filterhttps://sa-token.cc/v/v1.36.0/doc.html#/use/route-check
2.2.5小结
由此可见,Filter是责任链,Interceptor是java中的jdk接口动态代理,而sa-token中的@SaIgnore注解使用的是AspectJ的切面动态代理,执行顺序是Filter-->Interceptor(controller前、中、后)-->AspectJ切面代理-->controller-->Interceptor-->Filter。
3.正确姿势
正确姿势中是所有请求都要校验是否登录,可以在SaServletFilter或SaInterceptor排除登录校验,或者使用sa-token的@SaIgnore注解.
正确姿势有三种:
3.1.只使用SaServletFilter需要额外写代码实现
3.2使用SaServletFilter和SaInterceptor
3.3使用sa-token的注解
package xxxx.xxxx.xxxx.config;import cn.dev33.satoken.SaManager;import cn.dev33.satoken.annotation.SaIgnore;import cn.dev33.satoken.context.SaHolder;import cn.dev33.satoken.context.model.SaRequest;import cn.dev33.satoken.exception.SaTokenException;import cn.dev33.satoken.filter.SaServletFilter;import cn.dev33.satoken.interceptor.SaInterceptor;import cn.dev33.satoken.jwt.StpLogicJwtForSimple;import cn.dev33.satoken.router.SaHttpMethod;import cn.dev33.satoken.router.SaRouter;import cn.dev33.satoken.router.SaRouterStaff;import cn.dev33.satoken.stp.StpLogic;import cn.dev33.satoken.stp.StpUtil;import com.alibaba.fastjson.JSON;import xxxx.xxx.xxx.dto.CustomError;import xxx.xxxx.xxx.i.response.RestResponse;import lombok.extern.slf4j.Slf4j;import org.apache.commons.collections4.CollectionUtils;import org.apache.commons.lang3.StringUtils;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.web.method.HandlerMethod;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import org.springframework.web.servlet.mvc.method.RequestMappingInfo;import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;import javax.servlet.http.HttpServletResponse;import java.util.HashSet;import java.util.List;import java.util.Map;import java.util.Objects;import java.util.Set;import java.util.stream.Collectors;public class SaTokenConfigure implements WebMvcConfigurer, ApplicationContextAware {private ApplicationContext applicationContext;private static SetsaIgnoreUrls = new HashSet<>(); // Sa-Token 整合 jwt (Simple 简单模式)@Beanpublic StpLogic getStpLogicJwt() {return new StpLogicJwtForSimple();}// 注册拦截器@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 注册 Sa-Token 拦截器,校验规则为 StpUtil.checkLogin() 登录校验。//这里有登录校验,在controller上的登录方法就不要使用@SaCheckLogin注解,否则会有两次权限校验的//这个官方文档也有说明的:https://sa-token.cc/v/v1.36.0/doc.html#/plugin/aop-at/*** 注意点:* 使用拦截器模式,只能把注解写在Controller层,使用AOP模式,可以将注解写在任意层级* 拦截器模式和AOP模式不可同时集成,否则会在Controller层发生一个注解校验两次的bug*///方式二:在方式一里面排除的路径需要在这里在写一遍,或者删除方式一的排除路径,直接写在方式二这里registry.addInterceptor(new SaInterceptor(handle -> StpUtil.checkLogin()) //方式三需要注释这里,然后controller的方法上使用sa-token的注解).addPathPatterns("/**")//排除路径.excludePathPatterns("/admin/**").excludePathPatterns("/payRefund/**");}/*** 注册 [Sa-Token 全局过滤器]*/@Beanpublic SaServletFilter getSaServletFilter() {//方式一(方式二这里需要注释) //方式三需要注释这里,然后controller的方法上使用sa-token的注解this.saIgnoreUrls();return new SaServletFilter()// 指定 [拦截路由] 与 [放行路由].addInclude("/**")// 登录认证 -- 拦截所有路由,并排除/user/login 用于开放登录.addExclude("/admin/**").addExclude("/actuator/**").addExclude("/favicon.ico").addExclude("*.js").addExclude("*.css")// 认证函数: 每次请求执行.setAuth(obj -> {log.info("getSaServletFilter.setAuth.saIgnoreUrls:{}", JSON.toJSONString(saIgnoreUrls));SaRouterStaff saRouterStaff = SaRouter.match("/**"); // 拦截的 path 列表,可以写多个 *///方式一(方式二这里需要注释) //方式三需要注释这里,然后controller的方法上使用sa-token的注解if (CollectionUtils.isNotEmpty(saIgnoreUrls)) {saRouterStaff.notMatch(saIgnoreUrls.stream().collect(Collectors.toList()));}SaManager.getLog().debug("----- 请求path={} 提交token={}", SaHolder.getRequest().getRequestPath(), StpUtil.getTokenValue());// ...// 排除掉的 path 列表,可以写多个//方式一(方式二这里需要注释) //方式三需要注释这里,然后controller的方法上使用sa-token的注解saRouterStaff.check(r -> StpUtil.checkLogin());// 要执行的校验动作,可以写完整的 lambda 表达式// 根据路由划分模块,不同模块不同鉴权// 更多拦截处理方式,请参考“路由拦截式鉴权”章节 */})// 异常处理函数:每次认证函数发生异常时执行此函数.setError(e1 -> {log.error("sa-token异常:{}", e1.getMessage());// 设置响应头SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");/*** sa-token登录相关异常处理* https://sa-token.cc/v/v1.36.0/doc.html#/fun/exception-code*/if (e1 instanceof SaTokenException) {SaTokenException e = (SaTokenException) e1;// 根据不同异常细分状态码返回不同的提示if (e.getCode() == 11001 || e.getCode() == 11011) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "未能读取到有效Token");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 11002) {return JSON.toJSONString(RestResponse.fail("登录时的账号为空"));}if (e.getCode() == 11012) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "Token无效");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 11013) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "Token已过期");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 11014) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "Token已被顶下线");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 11015) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "Token已被踢下线");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 11016) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "Token已被冻结");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 11017) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "未按照指定前缀提交token");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 11041) {return JSON.toJSONString(RestResponse.fail("缺少指定的角色"));}if (e.getCode() == 11051) {return JSON.toJSONString(RestResponse.fail("缺少指定的权限"));}if (e.getCode() == 11061) {return JSON.toJSONString(RestResponse.fail("当前账号未通过服务封禁校验"));}if (e.getCode() == 11062) {return JSON.toJSONString(RestResponse.fail("提供要解禁的账号无效"));}if (e.getCode() == 12001) {return JSON.toJSONString(RestResponse.fail("请求中缺少指定的参数"));}if (e.getCode() == 12111) {return JSON.toJSONString(RestResponse.fail("密码md5加密异常"));}if (e.getCode() == 30201) {return JSON.toJSONString(RestResponse.fail("对jwt字符串解析失败"));}if (e.getCode() == 30202) {return JSON.toJSONString(RestResponse.fail("此jwt的签名无效"));}if (e.getCode() == 30203) {return JSON.toJSONString(RestResponse.fail("此jwt的loginType字段不符合预期"));}if (e.getCode() == 30204) {CustomError customError = new CustomError(String.valueOf(HttpServletResponse.SC_UNAUTHORIZED), "此jwt已超时");return JSON.toJSONString(RestResponse.fail(customError));}if (e.getCode() == 30205) {return JSON.toJSONString(RestResponse.fail("没有配置jwt秘钥"));}if (e.getCode() == 30206) {return JSON.toJSONString(RestResponse.fail("登录时提供的账号为空"));}// 更多 code 码判断 ...// 默认的提示return JSON.toJSONString(RestResponse.fail("sa-token异常,请联系管理员处理..."));}String message = e1.getMessage();if (StringUtils.isNotEmpty(message)) {return JSON.toJSONString(RestResponse.fail(message));}return JSON.toJSONString(RestResponse.fail("系统异常,请联系管理员处理..."));})// 前置函数:在每次认证函数之前执行.setBeforeAuth(obj -> {// 获得客户端domainSaRequest request = SaHolder.getRequest();String origin = request.getHeader("Origin");if (origin == null) {origin = request.getHeader("Referer");}// ---------- 设置一些安全响应头 ----------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")// ---------- 设置跨域响应头 ----------// 允许指定域访问跨域资源.setHeader("Access-Control-Allow-Origin", origin)// 允许所有请求方式.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, HEAD,PUT")// 允许的header参数.setHeader("Access-Control-Allow-Headers", "access-control-allow-origin, authority, content-type, version-info, X-Requested-With,satoken").setHeader("Access-Control-Allow-Credentials", "true")// 有效时间.setHeader("Access-Control-Max-Age", "3600");// 如果是预检请求,则立即返回到前端SaRouter.match(SaHttpMethod.OPTIONS).free(r -> {log.info("--------OPTIONS预检请求,不做处理,直接返回响应状态码为200");SaHolder.getResponse().setStatus(HttpServletResponse.SC_OK);}).back();});}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.applicationContext = applicationContext;}public void saIgnoreUrls() {RequestMappingHandlerMapping requestMappingHandlerMapping = applicationContext.getBean(RequestMappingHandlerMapping.class);MaphandlerMethods = requestMappingHandlerMapping.getHandlerMethods(); for (Map.Entryentry : handlerMethods.entrySet()) { HandlerMethod handlerMethod = entry.getValue();SaIgnore classSaIgnore = handlerMethod.getBeanType().getAnnotation(SaIgnore.class);SaIgnore methodSaIgnore = handlerMethod.getMethodAnnotation(SaIgnore.class);Setpatterns = entry.getKey().getPatternsCondition().getPatterns(); if (CollectionUtils.isNotEmpty(patterns)) {if (Objects.nonNull(classSaIgnore)) {ListclassMethodUrls = patterns.stream().collect(Collectors.toList()); log.info("saIgnoreUrls.classMethodUrls:{}", JSON.toJSONString(classMethodUrls));if (CollectionUtils.isNotEmpty(classMethodUrls)) {saIgnoreUrls.addAll(classMethodUrls);}} else if (Objects.nonNull(methodSaIgnore)) {String methodUrl = patterns.stream().findFirst().orElse(null);log.info("saIgnoreUrls.methodUrl:{}", methodUrl);if (StringUtils.isNotEmpty(methodUrl)) {saIgnoreUrls.add(methodUrl);}}}}}}
3.4说明
方式一:在controller上或者controller的方法上使用@SaIgnore或把controller类上的所有方法url解析排除或者标记的某些controller类的方法url解析排除
方式二:在没有关闭注解校验注解校验是开启的
此时在controller的方法上写了@SaIgnore注解,SaCheckAspect切面也不会进,原因是在SaInterceptor的preHandle方法这种已经让@SaIgnore注解标记调用的方法忽略了校验的:
/*** 每次请求之前触发的方法*/@Override@SuppressWarnings("all")public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)throws Exception {try {// 这里必须确保 handler 是 HandlerMethod 类型时,才能进行注解鉴权if(isAnnotation && handler instanceof HandlerMethod) {// 获取此请求对应的 Method 处理函数Method method = ((HandlerMethod) handler).getMethod();// 如果此 Method 或其所属 Class 标注了 @SaIgnore,则忽略掉鉴权if(SaStrategy.instance.isAnnotationPresent.apply(method, SaIgnore.class)) {// 注意这里直接就退出整个鉴权了,最底部的 auth.run() 路由拦截鉴权也被跳出了return true;}// 执行注解鉴权SaStrategy.instance.checkMethodAnnotation.accept(method);}// Auth 路由拦截鉴权校验auth.run(handler);} catch (StopMatchException e) {// StopMatchException 异常代表:停止匹配,进入Controller} catch (BackResultException e) {// BackResultException 异常代表:停止匹配,向前端输出结果// 请注意此处默认 Content-Type 为 text/plain,如果需要返回 JSON 信息,需要在 back 前自行设置 Content-Type 为 application/json// 例如:SaHolder.getResponse().setHeader("Content-Type", "application/json;charset=UTF-8");if(response.getContentType() == null) {response.setContentType("text/plain; charset=utf-8");}response.getWriter().print(e.getMessage());return false;}// 通过验证return true;}
方式三:
方式一不校验,可以在使用了方式二的校验在controller上加上
@SaIgnore@SaCheckLogin
这种或略逻辑虽然会走两次也不影响,优先走的是@SaIgnore的或略逻辑,其它权限逻辑校验是会走两次的,这种可能会有影响。
要想sa-token的注解权限生效必须关闭SaInterceptor的注解校验,SaCheckAspect切面才会进。
关闭SaInterceptor的注解校验:
public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new SaInterceptor(handle -> {SaRouter.match("/**").check(r -> StpUtil.checkLogin());}).isAnnotation(false) // 指定关闭掉注解鉴权能力,这样框架就只会做路由拦截校验了).addPathPatterns("/**");}
4.总结
由于我在项目中自定义注解使用aspectJ切面实现接口调用日志记录(这个实现思路后面会写一篇文章分享,好用到飞起,纵享丝滑)的时候发现切面居然没有生效。然后等我处理好了之后,想起了之前搞的sa-token的@SaIgnore注解失效,于是我就在次去搞了一下@SaIgnore的源码,解决之后写了这篇文章,我会把这篇文章投稿给sa-token官方,挂在sa-token的官网,避坑指南,希望我的分享对你有所启发和帮助,请一键三连,么么么哒!