在实践项目中运用到了springsecurity作为安全结构,咱们会遇到需求放行一些接口,使其能匿名访问的业务需求。可是每当需求当需求放行时,都需求在security的装备类中进行修改,感觉十分的不高雅。\

例如这样:

图片

所以想经过自定义一个注解,来进行接口匿名访问。在完成需求前,咱们先了解一下security的两种方行思路。

第一种便是在configure(WebSecurity web)办法中装备放行,像下面这样:

@Override
publicvoidconfigure(WebSecurityweb)throwsException{
web.ignoring().antMatchers("/css/**","/js/**","/index.html","/img/**","/fonts/**","/favicon.ico","/verifyCode");
}

第二种办法是在configure(HttpSecurity http)办法中进行装备:

@Override
protectedvoidconfigure(HttpSecurityhttpSecurity)throwsException
{
httpSecurity
.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
}

两种办法最大的区别在于,第一种办法是不走 Spring Security 过滤器链,而第二种办法走 Spring Security 过滤器链,在过滤器链中,给恳求放行。如果您正在学习Spring Boot,那么推荐一个连载多年还在持续更新的免费教程:blog.didispace.com/spring-boot…

在咱们运用 Spring Security 的时分,有的资源能够运用第一种办法额外放行,不需求验证,例如前端页面的静态资源,就能够依照第一种办法装备放行。

有的资源放行,则必须运用第二种办法,例如登录接口。大家知道,登录接口也是必须要暴露出来的,不需求登录就能访问到的,可是咱们却不能将登录接口用第一种办法暴露出来,登录恳求必须要走 Spring Security 过滤器链,由于在这个过程中,还有其他工作要做,具体的登录流程想了解的能够自行百度。

了解完了security的两种放行策略后,咱们开始完成

首要创立一个自定义注解

@Target({ElementType.METHOD})//注解放置的方针位置,METHOD是可注解在办法级别上
@Retention(RetentionPolicy.RUNTIME)//注解在哪个阶段履行
@Documented//生成文档
public@interfaceIgnoreAuth{
}

这儿阐明一下,@Target({ElementType.METHOD})我的完成办法,注解只能标记在带有@RequestMapping注解的办法上。具体为什么下面的完成办法看完就懂了。

接下来创立一个security的装备类SecurityConfig并承继WebSecurityConfigurerAdapter

@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true)
publicclassSecurityConfigextendsWebSecurityConfigurerAdapter
{
@Autowired
privateRequestMappingHandlerMappingrequestMappingHandlerMapping;
/**
*@description:运用这种办法放行的接口,不走SpringSecurity过滤器链,
*无法经过SecurityContextHolder获取到登录用户信息的,
*由于它一开始没经过 SecurityContextPersistenceFilter 过滤器链。
*@dateTime:2021/7/1910:22
*/
@Override
publicvoidconfigure(WebSecurityweb)throwsException{
WebSecurityand=web.ignoring().and();
Map<RequestMappingInfo,HandlerMethod>handlerMethods=requestMappingHandlerMapping.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的工作流程,便于了解。咱们经过翻看源码

利用自定义注解放行 Spring Security 项目的接口

图片

承继关系如上图所示。

AbstractHandlerMethodMapping完成了InitializingBean接口

publicinterfaceInitializingBean{
voidafterPropertiesSet()throwsException;
}

AbstractHandlerMethodMapping类中经过afterPropertiesSet办法调用initHandlerMethods进行初始化

publicvoidafterPropertiesSet(){
this.initHandlerMethods();
}
protectedvoidinitHandlerMethods(){
String[]var1=this.getCandidateBeanNames();
intvar2=var1.length;
for(intvar3=0;var3<var2;++var3){
StringbeanName=var1[var3];
if(!beanName.startsWith("scopedTarget.")){
this.processCandidateBean(beanName);
}
}
this.handlerMethodsInitialized(this.getHandlerMethods());
}

再调用processCandidateBean办法:

protectedvoidprocessCandidateBean(StringbeanName){
ClassbeanType=null;
try{
beanType=this.obtainApplicationContext().getType(beanName);
}catch(Throwablevar4){
if(this.logger.isTraceEnabled()){
this.logger.trace("Couldnotresolvetypeforbean'"+beanName+"'",var4);
}
}
if(beanType!=null&&this.isHandler(beanType)){
this.detectHandlerMethods(beanName);
}
}

经过调用办法中的isHandler办法是不是requestHandler办法,能够看到源码是经过RequestMapping,Controller 注解进行判别的。

protectedbooleanisHandler(Class<?>beanType){
returnAnnotatedElementUtils.hasAnnotation(beanType,Controller.class)||AnnotatedElementUtils.hasAnnotation(beanType,RequestMapping.class);
}

判别经过后,调用detectHandlerMethods办法将handler注册到HandlerMethod的缓存中。如果您正在学习Spring Boot,那么推荐一个连载多年还在持续更新的免费教程:blog.didispace.com/spring-boot…

protectedvoiddetectHandlerMethods(Objecthandler){
Class<?>handlerType=handlerinstanceofString?this.obtainApplicationContext().getType((String)handler):handler.getClass();
if(handlerType!=null){
Class<?>userType=ClassUtils.getUserClass(handlerType);
Map<Method,T>methods=MethodIntrospector.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)->{
MethodinvocableMethod=AopUtils.selectInvocableMethod(method,userType);
this.registerHandlerMethod(handler,invocableMethod,mapping);
});
}
}

经过registerHandlerMethod办法将handler放到private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap();map中。

requestMappingHandlerMapping.getHandlerMethods()办法便是获取一切的HandlerMapping。

publicMap<T,HandlerMethod>getHandlerMethods(){
this.mappingRegistry.acquireReadLock();
Mapvar1;
try{
var1=Collections.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(WebSecurity web)的放行办法。它是不走security的过滤链,是无法经过SecurityContextHolder获取到登录用户信息的,这点问题是需求注意的。