30

I'm using Spring Oauth2 and Spring Pre-post Annotations With Spring-boot

I Have a service class MyService. one of MyService methods is:

@PreAuthorize("#id.equals(authentication.principal.id)")
public SomeResponse getExampleResponse(String id){...}

can i control in some manner the json that is returned by the caller Controller?

the json that is returned by default is:

{error : "access_denied" , error_message: ".."}

I Want to be able to control the error_message param. I'm looking for something similar to:

@PreAuthorize(value ="#id.equals(authentication.principal.id)", onError ="throw new SomeException("bad params")")
public SomeResponse getExampleResponse(String id){...}

One way i thought of doing it is by Using ExceptionHandler

@ExceptionHandler(AccessDeniedException.class)
public Response handleAccessDeniedException(Exception ex, HttpServletRequest request){
    ...
}

but i can't control the message of the exception. and also i can't be sure that this Exception will be thrown in future releases

royB
  • 12,779
  • 15
  • 58
  • 80

3 Answers3

10

Spring Boot docs on error handling: http://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#boot-features-error-handling. One way you can control the JSON is by adding a @Bean of type ErrorAttributes.

@Bean
ErrorAttributes errorAttributes() {
    return new MyErrorAttributes();
}
Dave Syer
  • 56,583
  • 10
  • 155
  • 143
  • 1
    thanks for your answer. The documentation part is not really clear and it will be great to have an `How To` on this subject. `DefaultErrorAttributes` method: `addErrorDetail` how can i control the `Throwable error` message that is passed to `ErrorAttributes`? – royB Feb 09 '15 at 15:36
  • 1
    If the exception comes from `@PreAuthorize` you can't control it much (or at least I can't see why you'd want to). What you can do, I suppose, is add a custom `AccessDecisionManager`, but that's really not very mainstream. – Dave Syer Feb 09 '15 at 16:51
  • 3
    Hi @DaveSyer, I have the situation `@PreAuthorize("isAuthenticated() and principal.user.isEnabled() == true and principal.user.isConfirmed() == true")` and i want to differ which of constrains throws a AccessDeniedException. Then handle it and throw my own exception depending on unsatisfied condition. Or only what i can to do its to write my own aspect and throw exceptions by my own? – InsFi May 19 '15 at 14:13
  • @royB Have you found an answer to your original question? I have exactly the same problem as you i.e. I want to throw different exceptions according to different conditions... – balteo Mar 24 '16 at 15:29
  • @DaveSyer. Does the only way of throwing different exceptions according to different conditions mean implementing an `AccessDecisionManager`? Say I want to throw a custom `PaymentRequiredException` instead of a plain `AccessDeniedException` if the condition in the preauthorize is not matched. Is there a way of doing this with the current implementation of Spring boot and spring security? – balteo Mar 24 '16 at 15:34
  • I have opened a bounty as I think this question is very interesting and I have upvoted the question as well. – balteo Mar 24 '16 at 15:35
  • For what it's worth, I think the best solution so far is to use the ControllerAdvice support, add an @ExceptionHandler for the IllegalArgumentException, or whatever PreAuthorize is throwing, and then inspect the message body itself to see if it's a spel related error and customize the errors returned to the client- http://www.baeldung.com/2013/01/31/exception-handling-for-rest-with-spring-3-2/ – chrismarx Aug 31 '16 at 15:39
8

Implement AccessDeniedHandler

@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
        AccessDeniedException accessDeniedException) throws IOException, ServletException {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    try {
        ObjectMapper mapper = new ObjectMapper();
        SomeJsonModel jsonResponse =new SomeJsonModel();
        mapper.writeValue(response.getOutputStream(), jsonResponse);
    } catch (Exception e) {
        throw new ServletException();
    }
}

SomeJsonModel will be your own POJO/model class which you can control And add that access denied handler in Resource Server Configuration

@Override
public void configure(HttpSecurity http) throws Exception {
        http.requestMatchers()
                .antMatchers(SECURED_PATTERN).and().authorizeRequests()
                .antMatchers(HttpMethod.POST,SECURED_PATTERN).access(SECURED_WRITE_SCOPE)
                .anyRequest().access(SECURED_READ_SCOPE).and()
              .exceptionHandling().authenticationEntryPoint(newAuthExceptionEntryPoint())
                .accessDeniedHandler(new MyAccessDeniedHandler());
}
António Ribeiro
  • 4,129
  • 5
  • 32
  • 49
Abdul Rab Memon
  • 396
  • 5
  • 19
3

It was not working for me when I implemented AccessDeniedHandler. So I created a ExceptionHandler function inside AuthenticationEntryPoint and marked the class as @ControllerAdvice.

Please find the code below

@ControllerAdvice
@Component  
public class EmrExceptionHandler implements AuthenticationEntryPoint {


    private static final Logger logger = LoggerFactory.getLogger(EmrExceptionHandler.class);

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                         AuthenticationException authException) throws IOException, ServletException {
        logger.error("Unauthorized error: {}", authException.getMessage());
        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
        httpServletResponse.getWriter().write(convertObjectToJson(new ErrorResponse(ResponseMessages.NOT_AUTHORIZED)));
    }

    @ExceptionHandler(value = {AccessDeniedException.class})
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse,
                         AccessDeniedException accessDeniedException) throws IOException {
        logger.error("AccessDenied error: {}", accessDeniedException.getMessage());
        httpServletResponse.setStatus(HttpStatus.FORBIDDEN.value());
        httpServletResponse.getWriter().write(convertObjectToJson(new ErrorResponse(ResponseMessages.NOT_PERMITTED)));
    }


    public String convertObjectToJson(Object object) throws JsonProcessingException {
        if (object == null) {
            return null;
        }
        ObjectMapper mapper = new ObjectMapper();
        return mapper.writeValueAsString(object);
    }
}
António Ribeiro
  • 4,129
  • 5
  • 32
  • 49
emilpmp
  • 1,716
  • 17
  • 32