Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to wrap an OAuth2 exception?

We have a rest API that uses Spring OAuth2. After the user is authenticated, all the JSON responses are in the following format:

{"code" : 12345, "data" : "..." }

But the JSON response for authentication failures is not inline with the above format, as that is handled by Spring.

For example in case of incorrect credentials, the clients get HTTP status code 400 with JSON response as follows:

{"error": "invalid_grant", "error_description": "Bad credentials" }

In case the user account is locked, the clients get HTTP status code 400 with JSON response as follows

{"error":"invalid_grant","error_description":"User account is locked"}

All of this is because Spring TokenEndpoint.handleException() is handling the exceptions associated with /oauth/token

I would like to change the JSON response for OAuth2 failures to follow the first format.

This is what I have tried so far with no success:

  1. Use ControllerAdvice with highest precendence order & use @ExceptionHandler as described here
  2. implementing OAuth2ExceptionRenderer as described here
  3. implement ExceptionMapper
  4. added a new ObjectMapper with extending StdSerializer. Although my objectmapper is initialized it is not being used for serializing the exceptions. Maybe because Spring is calling MappingJackson2HttpMessageConverter directly and there seems to be several instances of this class in my app.

Any help in any of the above approaches or a new one would be highly appreciated.

I haven't tried this approach as I cannot change the contextpath for the existing clients.

like image 895
alek Avatar asked Oct 30 '22 22:10

alek


1 Answers

If you want to handle the authentication process, you can setup your own custom authentication manager

<oauth:authorization-server
    client-details-service-ref="clientDetails" token-services-ref="tokenServices"
    user-approval-handler-ref="userApprovalHandler">
    <oauth:authorization-code />
    <oauth:implicit />
    <oauth:refresh-token />
    <oauth:client-credentials />
    <oauth:password authentication-manager-ref="customAuthenticationManager" />
</oauth:authorization-server>

<authentication-manager id="customAuthenticationManager"
    xmlns="http://www.springframework.org/schema/security">
    <authentication-provider ref="customAuthenticationProvider" />
</authentication-manager>

<bean id="customAuthenticationProvider"
    class="com.any.CustomAuthenticationProvider">
</bean>

create custom authentication provider that implements AuthenticationProvider

public class UserAuthenticationProvider implements AuthenticationProvider {

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {

        UsernamePasswordAuthenticationToken auth = (UsernamePasswordAuthenticationToken) authentication;
        String username = auth.getName();
        String password = token.getCredentials().toString();
        User user = userService.loadByUsername(username);
        if(user.isLocked){
            throw new UserLockedException("User is locked");
        }
        if(another.something.bad.happened){
            throw new AnotherSomethingBadHappenedException("Error");
        }

        // setup authorities
        //...

        return new UsernamePasswordAuthenticationToken(user, password, authorities);
    }


}

Now you have your own exception, and by using ExceptionMapper you can translate the exception thrown on authentication process into your custom response message.

Another customization you can create is on Authorization process by creating a custom class that extends ApprovalStoreUserApprovalHandler

public class CustomUserApprovalHandler extends ApprovalStoreUserApprovalHandler {

    // stripped

    @Override
    public AuthorizationRequest checkForPreApproval(AuthorizationRequest authorizationRequest,
            Authentication userAuthentication) {

        ClientDetails client = clientDetailsService
                            .loadClientByClientId(authorizationRequest.getClientId());
        // here, you have the client and the user
        // you can do any checking here and throw any exception
        authorizationRequest.setApproved(approved);
        return authorizationRequest;
    }
}

create bean definition for that class

<bean id="userApprovalHandler"
    class="com.any.CustomUserApprovalHandler">
        <property name="approvalStore" ref="approvalStore" />
        <property name="requestFactory" ref="oAuth2RequestFactory" />
        <property name="clientDetailsService" ref="clientDetails" />
        <property name="useApprovalStore" value="true" />
    </bean>
like image 126
KSTN Avatar answered Nov 04 '22 13:11

KSTN