-1

I'm new to building rest APi using spring boot.

Here is my controller snippet

@PreAuthorize("hasRole('ADMIN')")
    @PostMapping(value = "/api/post/posts", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<PostDto> createPost(@Valid @RequestBody PostDto postDto) {

        System.out.println("postDto : "  + postDto.getId());
        return new ResponseEntity<>(postService.createPost(postDto), HttpStatus.CREATED);

    }

this is my Security config

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)    //give method level security
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.csrf().disable().authorizeRequests().antMatchers(HttpMethod.GET, "/api/**").permitAll().anyRequest()
                .authenticated().and().httpBasic();
    }

    @Override
    @Bean
    protected UserDetailsService userDetailsService() {
        // In Memory Users
        UserDetails ashish = User.builder().username("oxana").password(getPasswordEncoder().encode("password")).roles("USER").build();
        UserDetails admin = User.builder().username("admin").password(getPasswordEncoder().encode("admin")).roles("ADMIN").build();

        return new InMemoryUserDetailsManager(ashish, admin);
    }
    
    @Bean
    PasswordEncoder getPasswordEncoder() {
        
        return new BCryptPasswordEncoder();
    }
}

I'm trying land above exception here

@ExceptionHandler(Exception.class)
    public ResponseEntity<Errors> handleGlobalException(Exception exception,
                                                               WebRequest webRequest){
        Error errorDetails = new Error();
        errorDetails.setErrorDesc(exception.getMessage());
        errorDetails.setErrorCode(Error.ErrorCodeEnum.BAD_REQUEST);
        Errors errors = new Errors();
        errors.addErrorsItem(errorDetails);
        
        return new ResponseEntity<>(errors, HttpStatus.INTERNAL_SERVER_ERROR);
    }

but its not coming and giving a big mess of error, like this

"timestamp": "2022-02-21T11:39:28.797+00:00",
    "status": 403,
    "error": "Forbidden",
    "trace": "org.springframework.security.access.AccessDeniedException: Access is denied\r\n\tat org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:73)\r\n\tat org.springframework.security.access.intercept.AbstractSecurityInterceptor.attemptAuthorization(AbstractSecurityInterceptor.java:238)\r\n\tat org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:208)\r\n\tat org.springframework.security.access.intercept.aopalliance.

Can anyone please suggest me, How can I handle or catch this exception to customize error, where user has no access to do something ?

Thanks

Update

Implemented AccessDeniedHandler in below way

@ResponseStatus(value = HttpStatus.FORBIDDEN, reason = "Dont have sufficient priviliges to perform this action")
public class AccessDeniedError implements AccessDeniedHandler {
    
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exec)
            throws IOException, ServletException {

        response.sendRedirect("Dont have sufficient priviliges to perform this action");

    }

}

And now able to get message like this

{
    "timestamp": "2022-02-21T13:29:08.377+00:00",
    "status": 404,
    "error": "Not Found",
    "message": "No message available",
    "path": "/api/post/Dont%20have%20sufficient%20priviliges%20to%20perform%20this%20action"
}

Its somewhat better, but how can I take control of these variables ("error", "message", "status") values from above response, so that I could add mine custom values in it ?

Oxana Grey
  • 327
  • 2
  • 12

1 Answers1

3

The AccessDeniedException is handled by the ExceptionTranslationFilter which then delegates to the AccessDeniedHandler to write to corresponding response to the client.

If you want to customize this behavior, then you can implement a AccessDeniedHandler and then set your implementation to the HttpSecurity object.

MyAccessDeniedHandler.java

public class MyAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        writeCustomResponse(response);
    }

    private void writeCustomResponse(HttpServletResponse response) {
        if (!response.isCommitted()) {
            try {
                response.setStatus(HttpStatus.FORBIDDEN.value());
                response.getWriter().write("{ \"error\": \"User is not authorized.\"}");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

EDIT

Since WebSecurityConfigurerAdapter was deprecated in Spring Security 5.7.0-M2, the following shows how to configure it by registering a SecurityFilterChain bean.

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler())

    return http.build();
}

OLD WAY

SecurityConfiguration.java

@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // set the customized AccessDeniedHandler to the HttpSecurity object
        http.exceptionHandling().accessDeniedHandler(new MyAccessDeniedHandler());
    }
}
Matheus
  • 3,058
  • 7
  • 16
  • 37
  • @OxanaGrey . . . the `sendRedirect` method sends a redirect response - [see](https://docs.oracle.com/javaee/6/api/javax/servlet/http/HttpServletResponse.html#sendRedirect(java.lang.String)). I've updated the MyAccessDeniedHandler.java example to show how you can write a custom json response. – Matheus Feb 21 '22 at 14:16
  • so there is no way of get control over these 3 attributes (error, status, message) ? – Oxana Grey Feb 21 '22 at 14:31
  • 1
    afaik, not exactly take control over the spring's response object, but nothing stops you from creating your own object and then serialize it as a json. My example already tells you how you can write your own response. Now, you can create a class with the fields you want (error, status, message), write a json by using a object mapper and then write to the response (as shown above). It seems like you're not familiarized with java ee filters, I suggest you to take a look at them. – Matheus Feb 21 '22 at 14:51