In the actual project using springsecurity as a security framework, we will encounter business requirements that need to release some interfaces so that they can be accessed anonymously. But whenever you need to release, you need to modify it in the security configuration class, which feels very inelegant. \
For example this:
picture
So I want to make anonymous access to the interface by customizing an annotation. Before implementing the requirements, let's first understand the two ways of thinking about security.
The first is configure(WebSecurity web)
to configure the release in the method, like this:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");
}
复制代码
The second way is configure(HttpSecurity http)
to configure it in the method:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity
.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
}
复制代码
The biggest difference between the two methods is that the first method does not follow the Spring Security filter chain, while the second method follows the Spring Security filter chain, and in the filter chain, the request is released. If you are learning Spring Boot, I recommend a free tutorial that has been serialized for many years and continues to be updated: blog.didispace.com/spring-boot…
When we use Spring Security, some resources can be released additionally using the first method without verification. For example, the static resources of the front-end page can be configured and released according to the first method.
For some resources to be released, the second method must be used, such as the login interface. As we all know, the login interface must also be exposed, and it can be accessed without login, but we cannot expose the login interface in the first way. The login request must go through the Spring Security filter chain, because in this In the process, there are other things to do. If you want to know the specific login process, you can Baidu by yourself.
After understanding the two release strategies of security, we start to implement
First create a custom annotation
@Target({ElementType.METHOD}) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface IgnoreAuth {
}
复制代码
Here is a description of @Target({ElementType.METHOD})
my implementation, annotations can only be marked on @RequestMapping
methods with annotations. Specifically why the following implementation will understand after reading it.
Next, create a security configuration class SecurityConfig and inheritWebSecurityConfigurerAdapter
@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<RequestMappingInfo, HandlerMethod> 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;
}
});
}
});
}
}
复制代码
Using the classes provided by Spring here RequestMappingHandlerMapping
, we can requestMappingHandlerMapping.getHandlerMethods();
get all the RequestMappingInfo
information through.
The following is the source code part, but don't read it, you can deepen your understanding after reading it
Here is a brief description RequestMappingHandlerMapping
of the workflow for easy understanding. By looking at the source code
picture
The inheritance relationship is shown in the figure above.
AbstractHandlerMethodMapping
implements the InitializingBean
interface
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
复制代码
AbstractHandlerMethodMapping
The class is initialized by a afterPropertiesSet
method callinitHandlerMethods
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());
}
复制代码
Call the processCandidateBean
method again:
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);
}
}
复制代码
By calling the isHandler method in the method is not a requestHandler
method, you can see that the source code is passed RequestMapping
, and the Controller annotation is used to judge.
protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
}
复制代码
After the judgment is passed, the calling detectHandlerMethods
method registers the handler in the cache of HandlerMethod. If you are learning Spring Boot, I recommend a free tutorial that has been serialized for many years and continues to be updated: blog.didispace.com/spring-boot…
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<Method, T> 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
The handler is put into the private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap();
map by the method.
The requestMappingHandlerMapping.getHandlerMethods()
method is to get all HandlerMappings.
public Map<T, HandlerMethod> getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
Map var1;
try {
var1 = Collections.unmodifiableMap(this.mappingRegistry.getMappings());
} finally {
this.mappingRegistry.releaseReadLock();
}
return var1;
}
复制代码
The last step is to traverse the map, determine whether it is IgnoreAuth.class
annotated or not, and then release it for different request methods.
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;
}
});
}
});
复制代码
Seeing this, you can understand my initial emphasis on marking @RequestMapping
methods with annotations. What I am using here is configure(WebSecurity web)
the release method. It does not follow the security filter chain, and cannot SecurityContextHolder
obtain the login user information. This problem needs to be paid attention to.