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.
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.
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.