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.

Java Architecture Diary
Java Architecture Diary
Java Architecture Diary
Customize Spring Security OAuth Token Responses with HandlerMethodReturnValueHandler & AOP

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.

Illustration
Illustration
Illustration
Illustration
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

aopcustom token
Java Architecture Diary
Written by

Java Architecture Diary

Committed to sharing original, high‑quality technical articles; no fluff or promotional content.

0 followers
Reader feedback

How this landed with the community

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.