在实际项目中使用到了springsecurity作为安全框架,我们会遇到需要放行一些接口,使其能匿名访问的业务需求。但是每当需要当需要放行时,都需要在security的配置类中进行修改,感觉非常的不优雅。
例如这样:
所以想通过自定义一个注解,来进行接口匿名访问。在实现需求前,我们先了解一下security的两种方行思路。
第一种就是在configure(WebSecurity web)
方法中配置放行,像下面这样:
@Overridepublicvoidconfigure(WebSecurityweb)throwsException{
web.ignoring().antMatchers("/css/**","/js/**","/index.html","/img/**","/fonts/**","/favicon.ico","/verifyCode");
}
第二种方式是在configure(HttpSecurity http)
方法中进行配置:
@Overrideprotectedvoidconfigure(HttpSecurityhttpSecurity)throwsException
{
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@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();
MaphandlerMethods=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
的工作流程,便于理解。我们通过翻看源码
继承关系如上图所示。
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) {
String beanName = 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的缓存中。
protectedvoiddetectHandlerMethods(Objecthandler){
Class>handlerType=handlerinstanceofString?this.obtainApplicationContext().getType((String)handler):handler.getClass();
if(handlerType!=null){
Class>userType=ClassUtils.getUserClass(handlerType);
Mapmethods=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
map中。
而requestMappingHandlerMapping.getHandlerMethods()
方法就是获取所有的HandlerMapping。
publicMapgetHandlerMethods() {
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
获取到登录用户信息的,这点问题是需要注意的。
审核编辑:郭婷
-
接口
+关注
关注
33文章
8516浏览量
150856
原文标题:如何利用自定义注解放行 Spring Security 项目的接口
文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论