Backend Development 12 min read

Spring Boot 3 Security Tutorial: AOP, Custom Annotations & JWT

This article presents a continuously updated collection of over 100 Spring Boot 3 practical cases, demonstrating how to implement permission authentication using Spring AOP, custom annotations, JWT utilities, interceptors, a global exception handler, a security context, and a custom argument resolver, complete with test examples and screenshots.

Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Full-Stack Practical Cases
Spring Boot 3 Security Tutorial: AOP, Custom Annotations & JWT

Introduction

The "Spring Boot 3实战案例合集" now includes more than 100 carefully selected practical articles. Subscribers receive the full MD documentation and source code, with a promise of permanent updates to keep pace with the latest Spring technologies.

1. Overview

Spring AOP provides an efficient way to implement permission authentication in Java applications. By intercepting method execution before, after, or on exception, developers can add authentication logic without modifying business code.

2. Practical Cases

2.1 Custom Annotations

<code>@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface AuthUser {
  /** Retrieve user information from the current login context; supports SpEL expressions */
  String value() default "";
}</code>
<code>@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PreAuthorize {
  /** Permissions */
  String[] value() default {};
  /** Permission logic: AND or OR */
  Logical logic() default Logical.AND;
  enum Logical { AND, OR }
}</code>

2.2 Core Utility Classes

<code>@Component
public class JwtUtil {
  @Value("${jwt.secret}")
  private String secret;
  @Value("${jwt.expiration}")
  private Long expiration;
  private final ObjectMapper objectMapper;
  public JwtUtil(ObjectMapper objectMapper) {
    this.objectMapper = new ObjectMapper();
  }
  /** Generate JWT token */
  public String generateToken(User user) {
    String json = null;
    try {
      json = this.objectMapper.writeValueAsString(user);
    } catch (JsonProcessingException e) {
      throw new RuntimeException(e);
    }
    return createToken(json);
  }
  private String createToken(String payload) {
    return Jwts.builder()
        .claims().add("info", payload).subject("pack_xxxooo")
        .issuedAt(new Date(System.currentTimeMillis()))
        .expiration(new Date(System.currentTimeMillis() + expiration * 1000)).and()
        .signWith(Keys.hmacShaKeyFor(secret.getBytes()))
        .compact();
  }
  /** Extract user from token */
  public User getUser(String token) {
    Object ret = getClaimFromToken(token, claims -> claims.get("info"));
    User value = null;
    try {
      value = new ObjectMapper().readValue(ret.toString(), User.class);
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return value;
  }
  private <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
    final Claims claims = (Claims) Jwts.parser()
        .verifyWith(Keys.hmacShaKeyFor(secret.getBytes()))
        .build()
        .parse(token)
        .getPayload();
    return claimsResolver.apply(claims);
  }
}
</code>
<code>@RestControllerAdvice
public class GlobalControllerAdvice {
  @ExceptionHandler(AuthException.class)
  public ResponseEntity<Object> authException(AuthException e) {
    return ResponseEntity.ok(Map.of("code", -1, "message", e.getMessage()));
  }
}
</code>
<code>public class SecurityContext {
  private static final ThreadLocal<User> context = new ThreadLocal<>();
  public static void setUser(User user) { context.set(user); }
  public static User getUser() { return context.get(); }
  public static void clear() { context.remove(); }
}
</code>

2.3 Interceptor and Aspect

<code>@Component
public class AuthInterceptor implements HandlerInterceptor {
  private static final Pattern authorizationPattern = Pattern.compile(
      "^Bearer (?<token>[a-zA-Z0-9-._~+/]+=*)$", Pattern.CASE_INSENSITIVE);
  private final JwtUtil jwtUtil;
  private final ObjectMapper objectMapper;
  public AuthInterceptor(JwtUtil jwtUtil, ObjectMapper objectMapper) {
    this.jwtUtil = jwtUtil;
    this.objectMapper = objectMapper;
  }
  @Override
  public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    String authorization = request.getHeader(HttpHeaders.AUTHORIZATION);
    if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) { error(response, "缺失token"); return false; }
    Matcher matcher = authorizationPattern.matcher(authorization);
    if (!matcher.matches()) { error(response, "无效token"); return false; }
    String token = matcher.group("token");
    User user = parseToken(token);
    if (user == null) { error(response, "登录无效,重新登录"); return false; }
    SecurityContext.setUser(user);
    return true;
  }
  @Override
  public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    SecurityContext.clear();
  }
  private void error(HttpServletResponse response, String message) throws Exception {
    response.setContentType("application/json;charset=utf-8");
    response.getWriter().print(objectMapper.writeValueAsString(Map.of("code", -1, "message", message)));
  }
  private User parseToken(String token) { return jwtUtil.getUser(token); }
}
</code>
<code>@Aspect
@Component
public class PermissionAspect {
  @Around("@annotation(authority)")
  public Object checkPermission(ProceedingJoinPoint joinPoint, PreAuthorize authority) throws Throwable {
    User user = SecurityContext.getUser();
    if (user == null) { throw new AuthException("请登录"); }
    Set<String> requiredPerms = Set.of(authority.value());
    Set<String> userPerms = user.getPermissions();
    boolean hasPermission = checkLogic(requiredPerms, userPerms, authority.logic());
    if (!hasPermission) { throw new AuthException("没有权限"); }
    return joinPoint.proceed();
  }
  private boolean checkLogic(Set<String> required, Set<String> has, PreAuthorize.Logical logic) {
    if (PreAuthorize.Logical.AND == logic) {
      return has.containsAll(required);
    } else {
      return !Collections.disjoint(required, has);
    }
  }
}
</code>

2.4 Configuration

<code>@Configuration
public class WebConfig implements WebMvcConfigurer {
  private final AuthInterceptor authInterceptor;
  public WebConfig(AuthInterceptor authInterceptor) { this.authInterceptor = authInterceptor; }
  @Override
  public void addInterceptors(InterceptorRegistry registry) {
    registry.addInterceptor(this.authInterceptor).addPathPatterns("/users/**");
  }
  @Override
  public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    resolvers.add(new AuthUserArgumentResolver());
  }
}
</code>

2.5 Test Controllers

<code>@RestController
public class LoginController {
  private final JwtUtil jwtUtil;
  public LoginController(JwtUtil jwtUtil) { this.jwtUtil = jwtUtil; }
  @GetMapping("/login")
  public ResponseEntity<Object> login(String username) {
    User user = new User();
    user.setId(1L);
    user.setName("pack");
    user.setIdCard("100819883");
    user.setUsername(username);
    user.setPermissions(Set.of("C", "R", "U", "D"));
    return ResponseEntity.ok(Map.of("token", jwtUtil.generateToken(user)));
  }
}
</code>
<code>@RestController
@RequestMapping("/users")
public class UserController {
  @GetMapping("")
  public ResponseEntity<Object> query() { return ResponseEntity.ok("查询用户"); }
  @GetMapping("/create")
  @PreAuthorize(value = {"C", "X"}, logic = Logical.OR)
  public ResponseEntity<Object> create() { return ResponseEntity.ok("创建成功"); }
}
</code>
<code>@RestController
public class TestController {
  @GetMapping("/info")
  public ResponseEntity<Object> info(@AuthUser User user) { return ResponseEntity.ok(user); }
  @GetMapping("/name")
  public ResponseEntity<Object> name(@AuthUser("name") String name) { return ResponseEntity.ok(name); }
}
</code>

Testing Results

Screenshots (omitted for brevity) show successful token acquisition, request rejection when token is missing, and successful access after providing a valid token.

All code and explanations above constitute the complete article.

JavaAOPSpring BootsecurityJWT
Spring Full-Stack Practical Cases
Written by

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.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.