Customize auth error from Spring Security using OAuth2

pakkk picture pakkk · Aug 31, 2017 · Viewed 8.1k times · Source

I was wondering if I could customize the following authorization error:

{
  "error": "unauthorized",
  "error_description": "Full authentication is required to access this resource"
}

I get it when the user request does not have permissions. And I would like to customize it to be quite similar than Spring Boot error:

{
 "timestamp":1445441285803,
 "status":401,
 "error":"Unauthorized",
 "message":"Bad credentials",
 "path":"/oauth/token"
}

Could it be possible?

Many thanks.

Answer

Dherik picture Dherik · Apr 24, 2019

The accepted answer does not work for me using Oauth2. After some research, the exception translator solution works.

Basically, you need to create a WebResponseExceptionTranslator and register it as your exception translator.

First, create a WebResponseExceptionTranslator bean:

@Slf4j
@Configuration
public class Oauth2ExceptionTranslatorConfiguration {

    @Bean
    public WebResponseExceptionTranslator oauth2ResponseExceptionTranslator() {
        return new DefaultWebResponseExceptionTranslator() {

            @Override
            public ResponseEntity<OAuth2Exception> translate(Exception e) throws Exception {

                ResponseEntity<OAuth2Exception> responseEntity = super.translate(e);
                OAuth2Exception body = responseEntity.getBody();
                HttpStatus statusCode = responseEntity.getStatusCode();

                body.addAdditionalInformation("timestamp", dateTimeFormat.format(clock.instant()))
                body.addAdditionalInformation("status", body.getHttpErrorCode().toString())
                body.addAdditionalInformation("message", body.getMessage())
                body.addAdditionalInformation("code", body.getOAuth2ErrorCode().toUpperCase())

                HttpHeaders headers = new HttpHeaders();
                headers.setAll(responseEntity.getHeaders().toSingleValueMap());
                // do something with header or response
                return new ResponseEntity<>(body, headers, statusCode);
            }
        };
    }

}

Now you need to change your Oauth2 configuration to register the bean WebResponseExceptionTranslator:

@Slf4j
@Configuration
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private ClientDetailsServiceBuilder builder;

    @Autowired
    private WebResponseExceptionTranslator oauth2ResponseExceptionTranslator;

    @Autowired
    private UserDetailsService userDetailsService;


    @Override
    public void configure(ClientDetailsServiceConfigurer clients) {
        clients.setBuilder(builder);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();

        tokenEnhancerChain.setTokenEnhancers(
                Arrays.asList(tokenEnhancer(), accessTokenConverter()));

        endpoints.tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                .exceptionTranslator(oauth2ResponseExceptionTranslator);

    }

}

The final result will be:

{
    "error": "unauthorized",
    "error_description": "Full authentication is required to access this resource",
    "code": "UNAUTHORIZED",
    "message": "Full authentication is required to access this resource",
    "status": "401",
    "timestamp": "2018-06-28T23:55:28.86Z"
}

You can see that I not remove the error and error_description from the original body of OAuth2Exception. I recommend to maintain them because these two fields are following the OAuth2 specification. See the RFC and OAuth2 API definitions for more details.

You can also customize the result: override the error or error_description (just calling addAdditionalInformation), identify a specific exception with instance of to return a different json result, etc. But there are restriction too: if you want to define some field as integer, I don't think it's possible, because the addAdditionalInformation method only accepts String as type.