Simplify JWT Retrieval in Spring Boot Controllers with a Custom Argument Resolver
This article demonstrates how to create a custom HandlerMethodArgumentResolver in Spring Boot that automatically extracts JWT token data and injects the corresponding user object—or selected fields—directly into controller method parameters, reducing boilerplate and improving code readability.
1. Introduction
In a front‑back separated application, JWT tokens are often parsed in the backend to obtain user information. Using filters or ThreadLocal works, but you may want to inject the user directly as a controller method parameter to reduce boilerplate.
2. Practical Example
2.1 Prepare Environment
public class Users {
private String id;
/**用户名*/
private String username;
/**密码*/
private String password;
/**身份证*/
private String idNo;
// getters, setters
}Define a simple service that provides login and user lookup.
@Service
public class UsersService {
// in‑memory users
private static final List<Users> USERS = List.of(
new Users("1", "admin", "123123", "111111"),
new Users("2", "guest", "123456", "222222")
);
private static final String SECRET = "aaaabbbbccccdddd";
public String login(String username, String password) {
Optional<Users> optionalUser = USERS.stream()
.filter(u -> u.getUsername().equals(username) && u.getPassword().equals(password))
.findFirst();
if (optionalUser.isPresent()) {
Users user = optionalUser.get();
Map<String, Object> claims = new HashMap<>();
claims.put("id", user.getId());
return Jwts.builder()
.setClaims(claims)
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
return null;
}
public Users getUser() {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String token = request.getHeader(HttpHeaders.AUTHORIZATION);
token = token.replace("Bearer ", "");
Claims body = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody();
String id = (String) body.get("id");
return USERS.stream().filter(u -> id.equals(u.getId())).findFirst().orElse(null);
}
}2.2 Define Controller
@RestController
@RequestMapping("/users")
public class UsersController {
private final UsersService usersService;
public UsersController(UsersService usersService) {
this.usersService = usersService;
}
@GetMapping("/login")
public String login(String username, String password) {
return usersService.login(username, password);
}
}The controller currently only has a login endpoint that returns a token.
2.3 Custom Annotation
@Target({ ElementType.PARAMETER, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TokenPrincipal {
String expression() default "";
}2.4 Custom Argument Resolver
public class TokenArgumentResolver implements HandlerMethodArgumentResolver {
private ExpressionParser parser = new SpelExpressionParser();
private final UsersService usersService;
public TokenArgumentResolver(UsersService usersService) {
this.usersService = usersService;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return findMethodAnnotation(TokenPrincipal.class, parameter) != null;
}
@Override
public Object resolveArgument(MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) throws Exception {
Object principal = usersService.getUser();
if (principal == null) {
return null;
}
TokenPrincipal annotation = findMethodAnnotation(TokenPrincipal.class, parameter);
String expr = annotation.expression();
if (StringUtils.hasLength(expr)) {
StandardEvaluationContext context = new StandardEvaluationContext();
context.setRootObject(principal);
Expression expression = parser.parseExpression(expr);
principal = expression.getValue(context);
}
if (principal != null && !ClassUtils.isAssignable(parameter.getParameterType(), principal.getClass())) {
return null;
}
return principal;
}
private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
// implementation omitted for brevity
return null;
}
}2.5 Register Resolver
@Component
public class TokenWebMvcConfig implements WebMvcConfigurer {
private final UsersService usersService;
public TokenWebMvcConfig(UsersService usersService) {
this.usersService = usersService;
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new TokenArgumentResolver(this.usersService));
}
}2.6 Testing
After registration, the following endpoints work: /users/get – returns the full Users object when annotated with @TokenPrincipal. /users/username – returns only the username using @TokenPrincipal(expression = "username").
Conclusion
Using a custom HandlerMethodArgumentResolver greatly simplifies obtaining the current user inside controller methods. For scenarios requiring the user across multiple components, combining this approach with ThreadLocal remains an effective strategy.
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.
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.
