Secure Spring Boot APIs with AOP and Spring Security: A Step‑by‑Step Guide
This article explains how to combine Spring AOP and Spring Security in a Spring Boot 2.7.12 application to implement permission verification, covering concepts, required dependencies, custom filters, annotations, aspect logic, global exception handling, test endpoints, JWT token handling, and extensions for SpEL‑based role checks.
Environment: SpringBoot 2.7.12
1. Introduction
In modern web applications, permission verification is essential for security, ensuring that only users with appropriate rights can access protected resources. As applications grow, implementing this becomes more complex. Using Spring AOP together with Spring Security provides an effective solution.
2. Spring AOP Overview
Spring AOP is a module that supports aspect‑oriented programming, allowing developers to define cross‑cutting concerns such as logging, transaction management, and permission checks in separate aspects, improving code reusability and maintainability.
3. Spring Security Overview
Spring Security is a powerful framework for protecting web applications, offering authentication, authorization, and access‑control features. It simplifies user identity verification, role‑based access, and URL‑level protection.
4. Combining Spring AOP and Spring Security
The combination enables permission verification through the following steps:
Define an Aspect that intercepts requests to protected resources and validates permissions.
Create a filter that parses a token, stores permission information in the SecurityContext, and adds the filter to the Spring Security filter chain.
Within the Aspect, use Spring Security APIs to obtain the current user's identity and roles, then decide whether access is allowed.
If the user lacks sufficient permission, throw an exception to block the request.
If the user has the required permission, allow the request to proceed.
5. Required Dependencies
<code><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency></code>6. Authentication Filter
<code>public class PackAuthenticationFilter extends OncePerRequestFilter {
public static final String TOKEN_NAME = "x-api-token";
@SuppressWarnings("unused")
private ApplicationContext context;
public PackAuthenticationFilter(ApplicationContext context) {
this.context = context;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader(TOKEN_NAME);
if (!StringUtils.hasLength(token)) {
response.setContentType("text/html;charset=UTF-8");
response.getWriter().println("没有权限访问");
return;
}
// Parse token
List<? extends GrantedAuthority> authorities = JwtUtils.parseAuthority(token);
Authentication authentication = new UsernamePasswordAuthenticationToken("", "", authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
}
}</code>7. Security Configuration
<code>@Configuration
public class SecurityConfig {
@Autowired
private ApplicationContext context;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable();
// Permit all resources, restrict only controller endpoints
http.authorizeRequests().anyRequest().permitAll();
// Add custom filter
http.addFilterBefore(new PackAuthenticationFilter(this.context), UsernamePasswordAuthenticationFilter.class);
http.formLogin().disable();
return http.build();
}
}</code>8. Custom Annotation
<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface PreAuthority {
String value() default "";
}</code>9. Permission‑Checking Aspect
<code>@Component
@Aspect
public class AuthenticationAspect {
private AuthorityVerify authorityVerify;
public AuthenticationAspect(AuthorityVerify authorityVerify) {
this.authorityVerify = authorityVerify;
}
@Pointcut("@annotation(auth)")
private void authority(PreAuthority auth) {}
@Around("authority(auth)")
public Object test(ProceedingJoinPoint pjp, PreAuthority auth) throws Throwable {
String authority = auth.value();
boolean permit = this.authorityVerify.hasAuthority(authority);
if (!permit) {
throw new RuntimeException("权限不足");
}
Object ret = pjp.proceed();
return ret;
}
}</code>10. Authority Verification Utility
<code>@Component
public class AuthorityVerify {
public boolean hasAuthority(String authority) {
Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities();
return authorities.contains(new SimpleGrantedAuthority(authority));
}
}</code>11. Global Exception Handler
<code>@RestControllerAdvice
public class GlobalExceptionAdvice {
@ExceptionHandler({Exception.class})
public Object exceptionProcess(Exception e) {
return e.getMessage();
}
}</code>12. Test Controller
<code>@RestController
@RequestMapping("/api")
public class ApiController {
@GetMapping("/save")
@PreAuthority("api:save")
public Object save(HttpServletResponse response) throws Exception {
return "save method invoke...";
}
@GetMapping("/{id}")
@PreAuthority("api:query")
public Object query(@PathVariable("id") Integer id) {
return "query method invoke...";
}
}</code>13. Test User and JWT Generation
<code>Map<String, Object> map = new HashMap<>();
map.put("userId", "888888");
map.put("authorities", List.of("api:create", "api:query", "api:update", "api:delete"));
String token = createToken(map);
System.out.println(token);
System.out.println(parseToken(token));
System.out.println(parseAuthority(token));</code>Generated JWT (example):
<code>eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiI4ODg4ODgiLCJhdXRob3JpdGllcyI6WyJhcGk6Y3JlYXRlIiwiYXBpOnF1ZXJ5IiwiYXBpOnVwZGF0ZSIsImFwaTpkZWxldGUiXSwiZXhwIjoxNjk5NjE3NTM3fQ.GGLYIP2g5RZZkBoLnyQ_NWOQq_NUQylr5iZH9ouDiCM</code>14. Test Results
The /api/save endpoint is configured with permission api:save . The simulated user lacks this permission, so the aspect throws an exception, which is shown in the image above.
The query endpoint works normally.
15. Extending to SpEL‑Based Role Checks
To mimic Spring Security's @PreAuthorize("hasRole('xxx')") , the aspect can be modified to evaluate SpEL expressions:
<code>@PostConstruct
public void init() {
SpelParserConfiguration config = new SpelParserConfiguration(true, true);
parser = new SpelExpressionParser(config);
context = new StandardEvaluationContext();
context.setRootObject(this.authorityVerify);
}</code> <code>@Around("authority(auth)")
public Object test(ProceedingJoinPoint pjp, PreAuthority auth) throws Throwable {
String authority = auth.value();
boolean permit = this.parser.parseExpression(authority).getValue(this.context, Boolean.class);
if (!permit) {
throw new RuntimeException("不具备对应角色");
}
return pjp.proceed();
}</code>Usage example:
<code>@GetMapping("/save")
@PreAuthority("hasRole({'ADMIN', 'MGR'})")
public Object save(HttpServletResponse response) throws Exception {
return "save method invoke...";
}</code>Now any user with ADMIN or MGR role can access the endpoint.
16. Conclusion
By integrating Spring AOP with Spring Security, developers can implement flexible permission verification, improve application security, reduce code coupling, and enhance reusability and maintainability. The article also demonstrates how to extend the approach with SpEL for richer role‑based access control.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.