In real projects where springsecurity is used as a security framework, we will encounter business requirements that allow interfaces to be anonymously accessed. However, whenever the release is required, it needs to be modified in the security configuration class, which feels very inelegant. \
For example:
The picture
So you want to customize an annotation to allow anonymous access to the interface. Before implementing the requirements, let’s look at two ways of thinking about security.
The first is to configure the release in the configure(WebSecurity Web) method, as follows:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");
}
Copy the code
The second way is to configure in the configure(HttpSecurity HTTP) method:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception
{
httpSecurity
.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated()
}
Copy the code
The biggest difference between the two methods is that the first method does not go through the Spring Security filter chain, while the second method goes through the Spring Security filter chain, where the request is allowed. If you are learning the Spring Boot, then recommend a serial years continues to update free tutorial: blog.didispace.com/spring-boot…
When we used Spring Security, some resources could be released in the first way without validation, such as static resources on front-end pages.
If some resources are allowed, you must use the second method, for example, login interface. You know, the login interface also has to be exposed, you don’t need to be logged in to access it, but we can’t expose the login interface the first way, the login request has to go through the Spring Security filter chain, because there are other things to do in the process, Specific login process want to know you can baidu.
With security’s two release strategies in mind, we started implementing them
Start by creating a custom annotation
@target ({elementType.method}) // The Target position for annotation placement.METHOD is annotated in @Retention(retentionPolicy.runtime) Public @interface IgnoreAuth {}Copy the code
@target ({ElementType.METHOD}) is my implementation. Annotations can only be tagged on methods with @requestMapping annotations. Specific why the following implementation will be understood after reading.
Then create a security configuration class WebSecurityConfigurerAdapter SecurityConfig and inheritance
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping; /** * @ description: If an interface is released using this method, the user information cannot be obtained via SecurityContextHolder without going through the Spring Security filter chain. * because it hasn't been initially SecurityContextPersistenceFilter filter chain. * @ 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) - > {/ / take IgnoreAuth annotation method directly release the if (StringUtils. IsNotNull (method. GetMethodAnnotation (IgnoreAuth. Class))) {/ / Info.getmethodscondition ().getmethods ().foreach (requestMethod -> {switch (requestMethod) {case GET: // getPatternsCondition gets the request URL array, Through processing the info. GetPatternsCondition (.) getPatterns (). The forEach (pattern - > {/ / release 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; }}); }}); }}Copy the code
Using Spring here to provide us with the RequestMappingHandlerMapping class, we can through the RequestMappingHandlerMapping. GetHandlerMethods (); Get all RequestMappingInfo.
The following is the source part, can not see, see can deepen understanding
Here said the RequestMappingHandlerMapping simple workflow, easy to understand. We look through the source code
The picture
The inheritance relationship is shown in the figure above.
AbstractHandlerMethodMapping InitializingBean interface is realized
public interface InitializingBean {
void afterPropertiesSet() throws Exception;
}
Copy the code
AbstractHandlerMethodMapping class through the afterPropertiesSet method call initHandlerMethods is initialized
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()); }Copy the code
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);
}
}
Copy the code
Call the isHandler method to see if it is requestHandler. The source code is determined by RequestMapping, Controller annotations.
protected boolean isHandler(Class<? > beanType) { return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class); }Copy the code
When it passes, call the detectHandlerMethods method to register the handler in the HandlerMethod cache. If you are learning the Spring Boot, then recommend a serial years continues to update free tutorial: 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); }); }}Copy the code
Private final Map
mappingLookup = new LinkedHashMap(); In the map.
The requestMappingHandlerMapping. GetHandlerMethods () method is to get all the HandlerMapping.
public Map<T, HandlerMethod> getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
Map var1;
try {
var1 = Collections.unmodifiableMap(this.mappingRegistry.getMappings());
} finally {
this.mappingRegistry.releaseReadLock();
}
return var1;
}
Copy the code
Finally, the map is iterated to see if it has the ignoreauth.class annotation and is allowed for different requests.
handlerMethods.forEach((info, Method) - > {/ / take IgnoreAuth annotation method directly release the if (StringUtils. IsNotNull (method. GetMethodAnnotation (IgnoreAuth. Class))) {/ / Info.getmethodscondition ().getmethods ().foreach (requestMethod -> {switch (requestMethod) {case GET: // getPatternsCondition gets the request URL array, Through processing the info. GetPatternsCondition (.) getPatterns (). The forEach (pattern - > {/ / release 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; }}); }});Copy the code
See here to understand my initial emphasis on the need to tag methods with @requestMapping annotations. I use the configure(WebSecurity Web) mode. It does not follow the security filter chain and cannot obtain the login user information through the SecurityContextHolder. This issue needs to be noted.