来源:blog。csdn。netweixin45089791articledetails118890274 在实际项目中使用到了springsecurity作为安全框架,我们会遇到需要放行一些接口,使其能匿名访问的业务需求。但是每当需要当需要放行时,都需要在security的配置类中进行修改,感觉非常的不优雅。 例如这样: 所以想通过自定义一个注解,来进行接口匿名访问。在实现需求前,我们先了解一下security的两种方行思路。 第一种就是在configure(WebSecurityweb)方法中配置放行,像下面这样:Overridepublicvoidconfigure(WebSecurityweb)throwsException{web。ignoring()。antMatchers(css,js,index。html,img,fonts,favicon。ico,verifyCode);} 第二种方式是在configure(HttpSecurityhttp)方法中进行配置:Overrideprotectedvoidconfigure(HttpSecurityhttpSecurity)throwsException{httpSecurity。authorizeRequests()。antMatchers(hello)。permitAll()。anyRequest()。authenticated()} 两种方式最大的区别在于,第一种方式是不走SpringSecurity过滤器链,而第二种方式是走SpringSecurity过滤器链,在过滤器链中,被请求放行。 在我们使用SpringSecurity的时候,有的资源可以使用第一种方式额外放行,不需要验证,例如前端页面的静态资源,就可以按照第一种方式配置放行。 有的资源放行,则必须使用第二种方式,例如登录接口。大家知道,登录接口也是必须要暴露出来的,不需要登录就能访问到的,但是我们却不能将登录接口用第一种方式暴露出来,登录请求必须要走SpringSecurity过滤器链,因为在这个过程中,还有其他事情要做,具体的登录流程想了解的可以自行百度。了解完了security的两种放行策略后,我们开始实现 首先创建一个自定义注解Target({ElementType。METHOD})注解放置的目标位置,METHOD是可注解在方法级别上Retention(RetentionPolicy。RUNTIME)注解在哪个阶段执行Documented生成文档publicinterfaceIgnoreAuth{} 这里说明一下,Target({ElementType。METHOD})我的实现方式,注解只能标记在带有RequestMapping注解的方法上。具体为什么下面的实现方式看完就懂了。 接下来创建一个security的配置类SecurityConfig并继承WebSecurityConfigurerAdapterEnableGlobalMethodSecurity(prePostEnabledtrue,securedEnabledtrue)publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{AutowiredprivateRequestMappingHandlerMappingrequestMappingHandlerMapping;description:使用这种方式放行的接口,不走SpringSecurity过滤器链,无法通过SecurityContextHolder获取到登录用户信息的,因为它一开始没经过SecurityContextPersistenceFilter过滤器链。dateTime:202171910:22Overridepublicvoidconfigure(WebSecurityweb)throwsException{WebSecurityandweb。ignoring()。and();MapRequestMappingInfo,HandlerMethodhandlerMethodsrequestMappingHandlerMapping。getHandlerMethods();handlerMethods。forEach((info,method){带IgnoreAuth注解的方法直接放行if(StringUtils。isNotNull(method。getMethodAnnotation(IgnoreAuth。class))){根据请求类型做不同的处理info。getMethodsCondition()。getMethods()。forEach(requestMethod{switch(requestMethod){caseGET:getPatternsCondition得到请求url数组,遍历处理info。getPatternsCondition()。getPatterns()。forEach(pattern{放行and。ignoring()。antMatchers(HttpMethod。GET,pattern);});break;casePOST:info。getPatternsCondition()。getPatterns()。forEach(pattern{and。ignoring()。antMatchers(HttpMethod。POST,pattern);});break;caseDELETE:info。getPatternsCondition()。getPatterns()。forEach(pattern{and。ignoring()。antMatchers(HttpMethod。DELETE,pattern);});break;casePUT:info。getPatternsCondition()。getPatterns()。forEach(pattern{and。ignoring()。antMatchers(HttpMethod。PUT,pattern);});break;default:break;}});}});}} 在这里使用Spring为我们提供的RequestMappingHandlerMapping类,我们可以通过requestMappingHandlerMapping。getHandlerMethods();获取到所有的RequestMappingInfo信息。 以下是源码部分,可不看,看了可以加深理解 这里简单说一下RequestMappingHandlerMapping的工作流程,便于理解。我们通过翻看源码 继承关系如上图所示。 AbstractHandlerMethodMapping实现了InitializingBean接口publicinterfaceInitializingBean{voidafterPropertiesSet()throwsException;} AbstractHandlerMethodMapping类中通过afterPropertiesSet方法调用initHandlerMethods进行初始化publicvoidafterPropertiesSet(){this。initHandlerMethods();}protectedvoidinitHandlerMethods(){String〔〕var1this。getCandidateBeanNames();intvar2var1。length;for(intvar30;var3var2;var3){StringbeanNamevar1〔var3〕;if(!beanName。startsWith(scopedTarget。)){this。processCandidateBean(beanName);}}this。handlerMethodsInitialized(this。getHandlerMethods());} 再调用processCandidateBean方法:protectedvoidprocessCandidateBean(StringbeanName){ClassbeanTypenull;try{beanTypethis。obtainApplicationContext()。getType(beanName);}catch(Throwablevar4){if(this。logger。isTraceEnabled()){this。logger。trace(CouldnotresolvetypeforbeanbeanName,var4);}}if(beanType!nullthis。isHandler(beanType)){this。detectHandlerMethods(beanName);}} 通过调用方法中的isHandler方法是不是requestHandler方法,可以看到源码是通过RequestMapping,Controller注解进行判断的。protectedbooleanisHandler(Classlt;?beanType){returnAnnotatedElementUtils。hasAnnotation(beanType,Controller。class)AnnotatedElementUtils。hasAnnotation(beanType,RequestMapping。class);} 判断通过后,调用detectHandlerMethods方法将handler注册到HandlerMethod的缓存中。protectedvoiddetectHandlerMethods(Objecthandler){Classlt;?handlerTypehandlerinstanceofString?this。obtainApplicationContext()。getType((String)handler):handler。getClass();if(handlerType!null){Classlt;?userTypeClassUtils。getUserClass(handlerType);MapMethod,TmethodsMethodIntrospector。selectMethods(userType,(method){try{returnthis。getMappingForMethod(method,userType);}catch(Throwablevar4){thrownewIllegalStateException(Invalidmappingonhandlerclass〔userType。getName()〕:method,var4);}});if(this。logger。isTraceEnabled()){this。logger。trace(this。formatMappings(userType,methods));}methods。forEach((method,mapping){MethodinvocableMethodAopUtils。selectInvocableMethod(method,userType);this。registerHandlerMethod(handler,invocableMethod,mapping);});}} 通过registerHandlerMethod方法将handler放到privatefinalMapT,HandlerMethodmappingLookupnewLinkedHashMap();map中。 而requestMappingHandlerMapping。getHandlerMethods()方法就是获取所有的HandlerMapping。publicMapT,HandlerMethodgetHandlerMethods(){this。mappingRegistry。acquireReadLock();Mapvar1;try{var1Collections。unmodifiableMap(this。mappingRegistry。getMappings());}finally{this。mappingRegistry。releaseReadLock();}returnvar1;} 最后就是对map进行遍历,判断是否带有IgnoreAuth。class注解,然后针对不同的请求方式进行放行。handlerMethods。forEach((info,method){带IgnoreAuth注解的方法直接放行if(StringUtils。isNotNull(method。getMethodAnnotation(IgnoreAuth。class))){根据请求类型做不同的处理info。getMethodsCondition()。getMethods()。forEach(requestMethod{switch(requestMethod){caseGET:getPatternsCondition得到请求url数组,遍历处理info。getPatternsCondition()。getPatterns()。forEach(pattern{放行and。ignoring()。antMatchers(HttpMethod。GET,pattern);});break;casePOST:info。getPatternsCondition()。getPatterns()。forEach(pattern{and。ignoring()。antMatchers(HttpMethod。POST,pattern);});break;caseDELETE:info。getPatternsCondition()。getPatterns()。forEach(pattern{and。ignoring()。antMatchers(HttpMethod。DELETE,pattern);});break;casePUT:info。getPatternsCondition()。getPatterns()。forEach(pattern{and。ignoring()。antMatchers(HttpMethod。PUT,pattern);});break;default:break;}});}}); 看到这里就能理解我最开始的强调的需标记在带有RequestMapping注解的方法上。我这里使用到的是configure(WebSecurityweb)的放行方式。它是不走security的过滤链,是无法通过SecurityContextHolder获取到登录用户信息的,这点问题是需要注意的。