How to wrap an OAuth2 exception?

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

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

But the JSON response for authentication failures is not a string with the above format, since this is handled by Spring.

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

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

If the user account is locked, clients receive an HTTP 400 status code with a JSON response as follows

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

This is because Spring TokenEndpoint.handleException () handles exceptions related to / oauth / token

I would like to modify the JSON response for OAuth2 crashes to follow the first format.

This is what I have tried so far without success:

  • Use the ControllerAdvice with the highest acknowledgment order and use @ExceptionHandler as described here
  • implementation of OAuth2ExceptionRenderer as described here
  • implement ExceptionMapper
  • Added a new ObjectMapper with the StdSerializer extension. Although my object matrix is ​​initialized, it is not used to serialize exceptions. Perhaps because Spring calls MappingJackson2HttpMessageConverter directly, and there seem to be several instances of this class in my application.

Any help in any of the above approaches or new would be greatly appreciated.

I have not tried using this , as I cannot change the context path for existing clients.

+5
source share
2 answers

In the end, I came across the same problem and training. I use the custom ExceptionHandlerExceptionResolver class as resolver, which overwrites the getExceptionHandler method as shown in the code below, and then uses @ControllerAdvice again with a higher confirmation order, finally it works.

 public class MyExceptionHandlerExceptionResolver extends ExceptionHandlerExceptionResolver { private Map<ControllerAdviceBean, ExceptionHandlerMethodResolver> exceptionHandlerAdviceCache = null; @Override protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) { Class<?> handlerType = (handlerMethod != null ? handlerMethod.getBeanType() : null); List<ControllerAdviceBean> adviceBeans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext()); if (exceptionHandlerAdviceCache==null){ exceptionHandlerAdviceCache = new LinkedHashMap<ControllerAdviceBean, ExceptionHandlerMethodResolver>(); for (ControllerAdviceBean adviceBean:adviceBeans){ ExceptionHandlerMethodResolver resolver = new ExceptionHandlerMethodResolver(adviceBean.getBeanType()); exceptionHandlerAdviceCache.put(adviceBean, resolver); } } for (Map.Entry<ControllerAdviceBean, ExceptionHandlerMethodResolver> entry : this.exceptionHandlerAdviceCache.entrySet()) { if (entry.getKey().isApplicableToBeanType(handlerType)) { ExceptionHandlerMethodResolver resolver = entry.getValue(); Method method = resolver.resolveMethod(exception); if (method != null) { return new ServletInvocableHandlerMethod(entry.getKey().resolveBean(), method); } } } return null; } } 

use class MyExceptionHandlerExceptionResolver in configuration

 @EnableWebMvc @Configuration public class WebMVCConfiguration extends WebMvcConfigurationSupport { @Bean public ExceptionHandlerExceptionResolver handlerExceptionResolver() { MyExceptionHandlerExceptionResolver exceptionResolver = new MyExceptionHandlerExceptionResolver(); exceptionResolver.setOrder(0); exceptionResolver.setMessageConverters(messageConverters()); return exceptionResolver; } private MappingJackson2HttpMessageConverter jsonHttpMessageConverter() { return new MappingJackson2HttpMessageConverter(); } private List<HttpMessageConverter<?>> messageConverters() { List<HttpMessageConverter<?>> messageConverters = new ArrayList<>(); messageConverters.add(jsonHttpMessageConverter()); return messageConverters; } } 
+1
source

If you want to handle the authentication process, you can set up your own 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 your own 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 with ExceptionMapper you can translate the exception that occurred during the authentication process into your custom response message.

Another setting you can create is the authorization process by creating your own 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 this 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> 
0
source

All Articles