如何利用自定义注解放行SpringSecurity项目的接口
来源:blog.csdn.net/weixin_45089791/article/details/118890274
在实际项目中使用到了springsecurity作为安全框架,我们会遇到需要放行一些接口,使其能匿名访问的业务需求。但是每当需要当需要放行时,都需要在security的配置类中进行修改,感觉非常的不优雅。
例如这样:
所以想通过自定义一个注解,来进行接口匿名访问。在实现需求前,我们先了解一下security的两种方行思路。
第一种就是在 configure(WebSecurity web) 方法中配置放行,像下面这样:@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode"); }
第二种方式是在 configure(HttpSecurity http) 方法中进行配置:@Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .authorizeRequests() .antMatchers("/hello").permitAll() .anyRequest().authenticated() }
两种方式最大的区别在于,第一种方式是不走 Spring Security 过滤器链,而第二种方式是走 Spring Security 过滤器链,在过滤器链中,被请求放行。
在我们使用 Spring Security 的时候,有的资源可以使用第一种方式额外放行,不需要验证,例如前端页面的静态资源,就可以按照第一种方式配置放行。
有的资源放行,则必须使用第二种方式,例如登录接口。大家知道,登录接口也是必须要暴露出来的,不需要登录就能访问到的,但是我们却不能将登录接口用第一种方式暴露出来,登录请求必须要走 Spring Security 过滤器链,因为在这个过程中,还有其他事情要做,具体的登录流程想了解的可以自行百度。 了解完了security的两种放行策略后,我们开始实现
首先创建一个自定义注解 @Target({ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上 @Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行 @Documented //生成文档 public @interface IgnoreAuth { }
这里说明一下, @Target({ElementType.METHOD}) 我的实现方式,注解只能标记在带有@RequestMapping 注解的方法上。具体为什么下面的实现方式看完就懂了。
接下来创建一个security的配置类SecurityConfig并继承 WebSecurityConfigurerAdapter @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping; /** * @ description: 使用这种方式放行的接口,不走 Spring Security 过滤器链, * 无法通过 SecurityContextHolder 获取到登录用户信息的, * 因为它一开始没经过 SecurityContextPersistenceFilter 过滤器链。 * @ dateTime: 2021/7/19 10:22 */ @Override public void configure(WebSecurity web) throws Exception { WebSecurity and = web.ignoring().and(); Map handlerMethods = requestMappingHandlerMapping.getHandlerMethods(); handlerMethods.forEach((info, method) -> { // 带IgnoreAuth注解的方法直接放行 if (StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))) { // 根据请求类型做不同的处理 info.getMethodsCondition().getMethods().forEach(requestMethod -> { switch (requestMethod) { case GET: // getPatternsCondition得到请求url数组,遍历处理 info.getPatternsCondition().getPatterns().forEach(pattern -> { // 放行 and.ignoring().antMatchers(HttpMethod.GET, pattern); }); break; case POST: info.getPatternsCondition().getPatterns().forEach(pattern -> { and.ignoring().antMatchers(HttpMethod.POST, pattern); }); break; case DELETE: info.getPatternsCondition().getPatterns().forEach(pattern -> { and.ignoring().antMatchers(HttpMethod.DELETE, pattern); }); break; case PUT: info.getPatternsCondition().getPatterns().forEach(pattern -> { and.ignoring().antMatchers(HttpMethod.PUT, pattern); }); break; default: break; } }); } }); } }
在这里使用Spring为我们提供的 RequestMappingHandlerMapping 类,我们可以通过requestMappingHandlerMapping.getHandlerMethods(); 获取到所有的RequestMappingInfo 信息。
以下是源码部分,可不看,看了可以加深理解
这里简单说一下 RequestMappingHandlerMapping 的工作流程,便于理解。我们通过翻看源码
继承关系如上图所示。
AbstractHandlerMethodMapping 实现了InitializingBean 接口public interface InitializingBean { void afterPropertiesSet() throws Exception; }
AbstractHandlerMethodMapping 类中通过afterPropertiesSet 方法调用initHandlerMethods 进行初始化 public void afterPropertiesSet() { this.initHandlerMethods(); } protected void initHandlerMethods() { String[] var1 = this.getCandidateBeanNames(); int var2 = var1.length; for(int var3 = 0; var3 < var2; ++var3) { String beanName = var1[var3]; if (!beanName.startsWith("scopedTarget.")) { this.processCandidateBean(beanName); } } this.handlerMethodsInitialized(this.getHandlerMethods()); }
再调用 processCandidateBean 方法: protected void processCandidateBean(String beanName) { Class beanType = null; try { beanType = this.obtainApplicationContext().getType(beanName); } catch (Throwable var4) { if (this.logger.isTraceEnabled()) { this.logger.trace("Could not resolve type for bean "" + beanName + """, var4); } } if (beanType != null && this.isHandler(beanType)) { this.detectHandlerMethods(beanName); } }
通过调用方法中的isHandler方法是不是 requestHandler 方法,可以看到源码是通过RequestMapping ,Controller 注解进行判断的。protected boolean isHandler(Class<?> beanType) { return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class); }
判断通过后,调用 detectHandlerMethods 方法将handler注册到HandlerMethod的缓存中。protected void detectHandlerMethods(Object handler) { Class<?> handlerType = handler instanceof String ? this.obtainApplicationContext().getType((String)handler) : handler.getClass(); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); Map methods = MethodIntrospector.selectMethods(userType, (method) -> { try { return this.getMappingForMethod(method, userType); } catch (Throwable var4) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, var4); } }); if (this.logger.isTraceEnabled()) { this.logger.trace(this.formatMappings(userType, methods)); } methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); this.registerHandlerMethod(handler, invocableMethod, mapping); }); } }
通过 registerHandlerMethod 方法将handler放到private final Map mappingLookup = new LinkedHashMap(); map中。
而 requestMappingHandlerMapping.getHandlerMethods() 方法就是获取所有的HandlerMapping。public Map getHandlerMethods() { this.mappingRegistry.acquireReadLock(); Map var1; try { var1 = Collections.unmodifiableMap(this.mappingRegistry.getMappings()); } finally { this.mappingRegistry.releaseReadLock(); } return var1; }
最后就是对map进行遍历,判断是否带有 IgnoreAuth.class 注解,然后针对不同的请求方式进行放行。handlerMethods.forEach((info, method) -> { // 带IgnoreAuth注解的方法直接放行 if (StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))) { // 根据请求类型做不同的处理 info.getMethodsCondition().getMethods().forEach(requestMethod -> { switch (requestMethod) { case GET: // getPatternsCondition得到请求url数组,遍历处理 info.getPatternsCondition().getPatterns().forEach(pattern -> { // 放行 and.ignoring().antMatchers(HttpMethod.GET, pattern); }); break; case POST: info.getPatternsCondition().getPatterns().forEach(pattern -> { and.ignoring().antMatchers(HttpMethod.POST, pattern); }); break; case DELETE: info.getPatternsCondition().getPatterns().forEach(pattern -> { and.ignoring().antMatchers(HttpMethod.DELETE, pattern); }); break; case PUT: info.getPatternsCondition().getPatterns().forEach(pattern -> { and.ignoring().antMatchers(HttpMethod.PUT, pattern); }); break; default: break; } }); } });
看到这里就能理解我最开始的强调的需标记在带有 @RequestMapping 注解的方法上。我这里使用到的是configure(WebSecurity web) 的放行方式。它是不走security的过滤链,是无法通过 SecurityContextHolder 获取到登录用户信息的,这点问题是需要注意的。
全球最奢侈的二十大名表排名(下)手表,现在做为计时工具的作用已不复存在了。但手表已经成为了装饰和身份的象征(尤其是男人),尤其是机械手表,在现在已成为了手腕上的艺术品。而世界名表的价格惊人,其制作精良,造型极具艺
均衡器(EQ)对耳机耳塞听感的改善的实践基础篇一提到均衡器EQ,发烧友多数人的反应就是增加失真的玩意!在很多烧友的眼里,只要是hifi,只要是高保真,就是不能EQ的。原因很简单,器材已经高保真的,你的EQ只能失真而不可能让原来
全球最奢侈的二十大名表排名手表,现在做为计时工具的作用已不复存在了。但手表已经成为了装饰和身份的象征(尤其是男人),尤其是机械手表,在现在已成为了手腕上的艺术品。而世界名表的价格惊人,其制作精良,造型极具艺
阿丽塔,罗德里格斯的酷和卡梅隆的特效阿丽塔上影后,大家注意力都放到了卡梅隆的身上。确实,有了卡神的加入(那怕只是监制),影片的质量都有了很大的保证。电影中的特效让人眼前一亮,片中阿丽塔栩栩如生的股肤,细致到每一个毛孔
为什么当前区块链技术难以运用到银行系统随着近年来区块链技术及DLT(分布式账本)的发展与应用,银行和其他金融机构正在努力的将这些技术带入到业务模式种。实际上许多这些机构已经通过概念验证(PoC)把这些商业模式运用到市场
从此Facebook不存在,新品牌Meta诞生10月29日凌晨,Facebook更名为Meta的消息刷爆全网,成为人们争相讨论的话题。从此Facebook不存在,新品牌Meta诞生早在10月20日,就有知情人士透露Facebo
为港珠澳大桥谱写的交响曲梦桥及唱片介绍目前世界上没有任何一个国家可以做到这件事情55公里跨海大桥7公里海底隧道,面对海面异常复杂的汹涌波涛,历时13年建造,桥面铺装相当于98个足球场!作曲家方岽清骄傲地说。这座完全由中
灰度向美国SEC提出比特币现货ETF申请灰度公司创始人昨日发推特,称灰度已通过NYSEArca向美国SEC提交了19b4文件,申请将GBTC转换为比特币现货ETF。灰度向美国SEC提出比特币现货ETF申请!根据官方推特数
SHIB领头Meme币价格齐飞CloudRushSHIB起飞了!这是近两天圈子里讨论最高的话题。10月27日,比特币迎来登高之后的首次下跌,到达58100美元附近,接近近半月以来的最低水平。自10月20日短暂触及3。7万美元的历
美参议员为何反对Facebook推出Novi试点?社交媒体巨头Facebook近期推出了一个试验性的加密货币钱包服务Novi,目前已开始在美国和危地马拉推出其Novi数字钱包的小型试点,并选择Coinbase作为其试点的托管合作伙
加密货币总市值突破2。7万亿美元据最新数据显示,目前加密货币总市值已突破2。7万亿美元,24小时涨幅4。4。加密货币总市值突破2。7万亿美元10月16日,加密货币总市值达到2。6万亿美元,仅过去不到一周时间,总市