How to Use a Custom Annotation for Anonymous Access in Spring Security
This article explains two Spring Security configuration approaches, introduces a custom @IgnoreAuth annotation, and shows how to automatically whitelist annotated endpoints by scanning RequestMappingHandlerMapping, complete with code examples and workflow details.
In real projects using Spring Security, you often need to allow certain interfaces to be accessed anonymously, but modifying the security configuration each time is cumbersome.
Two ways to configure Spring Security
The first method configures configure(WebSecurity web) to ignore static resources, bypassing the Spring Security filter chain:
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/js/**", "/index.html", "/img/**", "/fonts/**", "/favicon.ico", "/verifyCode");
}The second method configures configure(HttpSecurity http) to permit specific URLs while still passing through the filter chain:
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/hello").permitAll()
.anyRequest().authenticated();
}The main difference is that the first approach does not go through the filter chain, so SecurityContextHolder cannot retrieve the logged‑in user, while the second does.
Creating a custom annotation
@Target({ElementType.METHOD}) // target methods
@Retention(RetentionPolicy.RUNTIME) // runtime retention
@Documented // include in Javadoc
public @interface IgnoreAuth {
}This annotation should be placed on methods that also have @RequestMapping.
Security configuration using the custom annotation
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private RequestMappingHandlerMapping requestMappingHandlerMapping;
@Override
public void configure(WebSecurity web) throws Exception {
WebSecurity and = web.ignoring().and();
Map<RequestMappingInfo, HandlerMethod> handlerMethods = requestMappingHandlerMapping.getHandlerMethods();
handlerMethods.forEach((info, method) -> {
// Methods annotated with @IgnoreAuth are allowed directly
if (StringUtils.isNotNull(method.getMethodAnnotation(IgnoreAuth.class))) {
// Handle different HTTP methods
info.getMethodsCondition().getMethods().forEach(requestMethod -> {
switch (requestMethod) {
case GET:
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;
}
});
}
});
}
}The above code uses RequestMappingHandlerMapping to retrieve all handler methods, checks for the @IgnoreAuth annotation, and registers the corresponding URL patterns to be ignored for each HTTP method.
RequestMappingHandlerMapping workflow
public void afterPropertiesSet() {
this.initHandlerMethods();
}
protected void initHandlerMethods() {
String[] beanNames = this.getCandidateBeanNames();
for (String beanName : beanNames) {
if (!beanName.startsWith("scopedTarget.")) {
this.processCandidateBean(beanName);
}
}
this.handlerMethodsInitialized(this.getHandlerMethods());
} protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = this.obtainApplicationContext().getType(beanName);
} catch (Throwable ex) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && this.isHandler(beanType)) {
this.detectHandlerMethods(beanName);
}
} protected boolean isHandler(Class<?> beanType) {
return AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class);
} 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 ex) {
throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex);
}
});
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);
});
}
} private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap(); public Map<T, HandlerMethod> getHandlerMethods() {
this.mappingRegistry.acquireReadLock();
try {
return Collections.unmodifiableMap(this.mappingRegistry.getMappings());
} finally {
this.mappingRegistry.releaseReadLock();
}
}Finally, the configuration iterates over the map of handler methods, checks for the @IgnoreAuth annotation, and adds the appropriate ignoring().antMatchers calls for each HTTP method.
The above diagram shows the inheritance hierarchy of RequestMappingHandlerMapping and related classes.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
