Customize Spring Security OAuth Token Responses with HandlerMethodReturnValueHandler & AOP
This article explains how to modify the default OAuth2 token response format in Spring Security by adding custom fields using a HandlerMethodReturnValueHandler implementation or an AOP advice, and discusses the implications of deviating from the OAuth2 specification.
Background
The default OAuth2 token response returned by Spring Security looks like this:
{
"access_token": "e6669cdf-b6cd-43fe-af5c-f91a65041382",
"token_type": "bearer",
"refresh_token": "da91294d-446c-4a89-bdcf-88aee15a75e8",
"expires_in": 43199,
"scope": "server"
}After extending the token with additional business fields, the JSON may become:
{
"access_token": "a6f3b6d6-93e6-4eb8-a97d-3ae72240a7b0",
"token_type": "bearer",
"refresh_token": "710ab162-a482-41cd-8bad-26456af38e4f",
"expires_in": 42396,
"scope": "server",
"tenant_id": 1,
"license": "made by pigx",
"dept_id": 1,
"user_id": 1,
"username": "admin"
}In some scenarios we need to wrap the response in a custom format, for example using an R object that contains a business code and message:
{
"code": 1,
"msg": "",
"data": {
"access_token": "e6669cdf-b6cd-43fe-af5c-f91a65041382",
"token_type": "bearer",
"refresh_token": "da91294d-446c-4a89-bdcf-88aee15a75e8",
"expires_in": 43199,
"scope": "server"
}
}Method 1: HandlerMethodReturnValueHandler
Spring MVC provides the HandlerMethodReturnValueHandler interface to intercept and modify the return value of controller methods.
public class FormatterToken implements HandlerMethodReturnValueHandler {
private static final String POST_ACCESS_TOKEN = "postAccessToken";
@Override
public boolean supportsReturnType(MethodParameter returnType) {
// Only handle the token endpoint method
return POST_ACCESS_TOKEN.equals(Objects.requireNonNull(returnType.getMethod()).getName());
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer container, NativeWebRequest request) throws Exception {
ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity) returnValue;
OAuth2AccessToken body = responseEntity.getBody();
HttpServletResponse response = request.getNativeResponse(HttpServletResponse.class);
assert response != null;
WebUtils.renderJson(response, R.ok(body));
}
}Register the handler early so it runs before Spring MVC's default handlers:
public class FormatterTokenAutoConfiguration implements ApplicationContextAware, InitializingBean {
private ApplicationContext applicationContext;
@Override
public void afterPropertiesSet() {
RequestMappingHandlerAdapter handlerAdapter = applicationContext.getBean(RequestMappingHandlerAdapter.class);
List<HandlerMethodReturnValueHandler> returnValueHandlers = handlerAdapter.getReturnValueHandlers();
List<HandlerMethodReturnValueHandler> newHandlers = new ArrayList<>();
newHandlers.add(new FormatterToken());
assert returnValueHandlers != null;
newHandlers.addAll(returnValueHandlers);
handlerAdapter.setReturnValueHandlers(newHandlers);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}Method 2: AOP Interception of /oauth/token
Alternatively, an AOP advice can wrap the response of the token endpoint:
@Around("execution(* org.springframework.security.oauth2.provider.endpoint.TokenEndpoint.postAccessToken(..))")
public Object handlePostAccessTokenMethod(ProceedingJoinPoint joinPoint) throws Throwable {
// Get the original response and wrap it
Object proceed = joinPoint.proceed();
ResponseEntity<OAuth2AccessToken> responseEntity = (ResponseEntity<OAuth2AccessToken>) proceed;
OAuth2AccessToken body = responseEntity.getBody();
return ResponseEntity.status(HttpStatus.OK).body(R.ok(body));
}Conclusion
Modifying the token endpoint response format is generally discouraged because it breaks compatibility with the OAuth2 specification, causing downstream components such as Swagger UI, gateway OAuth2 plugins, and Spring Security's built‑in SSO to fail.
Therefore, weigh the benefits against the drawbacks; in most cases the disadvantages outweigh the gains.
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.
Java Architecture Diary
Committed to sharing original, high‑quality technical articles; no fluff or promotional content.
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.
