wimdeblauwe / error-handling-spring-boot-starter

Spring Boot starter for configurable REST API error handling
https://wimdeblauwe.github.io/error-handling-spring-boot-starter/
Apache License 2.0
430 stars 52 forks source link

Feature request: add AccessDeniedHandler implementation similar to UnauthorizedEntryPoint #88

Closed chpasha closed 6 months ago

chpasha commented 7 months ago

Exceptions, that are thrown if the user is authenticated, but lacks some required role, are not caught now. To fix that, we have to also set the accessDeniedHandler in security config exceptionHandling. Its implementation could be actually 99.9% the same as UnauthorizedEntryPoint except for returned http response status (which should be forbidden) and the type of exception

wimdeblauwe commented 6 months ago

Not sure I understand the request. The library does handle AccessDeniedException. I wrote a test just now for it, see https://github.com/wimdeblauwe/error-handling-spring-boot-starter/commit/6f8cdaba58a4d9101befd227c427952473d290f5

Let me know if I am missing something in your request.

chpasha commented 6 months ago

I think, you current test implementation doesn't completely resemble how the Spring security works in this case. Change you test security chain and specify that rest method needs some role, for instance

 http.authorizeHttpRequests()
                .requestMatchers("/test/spring-security/access-denied")
                .hasAuthority("AASSSS")

your test will fail, but 403 will be returned

MockHttpServletResponse:
           Status = 403
    Error message = Forbidden
          Headers = [X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = null
             Body = 
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

java.lang.AssertionError: No value at JSON path "code"

now modify exception handling adding accessDeniedHandler

http.exceptionHandling()
                .authenticationEntryPoint(unauthorizedEntryPoint)
                .accessDeniedHandler(((request, response, ex)
                        -> {
                    unauthorizedEntryPoint.commence(request, response, new InsufficientAuthenticationException(ex.getMessage(), ex));
                    response.setStatus(HttpStatus.FORBIDDEN.value());
                }));

I abuse here your unauthorizedEntryPoint - the test will still fail, but now the framework does return JSON, just the wrong error code, but the rest is fine (response status and message)

MockHttpServletResponse:
           Status = 403
    Error message = null
          Headers = [Content-Type:"application/json;charset=UTF-8", X-Content-Type-Options:"nosniff", X-XSS-Protection:"0", Cache-Control:"no-cache, no-store, max-age=0, must-revalidate", Pragma:"no-cache", Expires:"0", X-Frame-Options:"DENY"]
     Content type = application/json;charset=UTF-8
             Body = {"code":"UNAUTHORIZED","message":"Access Denied"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

java.lang.AssertionError: JSON path "code" expected:<ACCESS_DENIED> but was:<UNAUTHORIZED>
Expected :ACCESS_DENIED
Actual   :UNAUTHORIZED

My assumption is, AccessDeniedException is not thrown directly, but rather default accessDeniedHandler is used (probably AccessDeniedHandlerImpl), which will (again probably) try to redirect/forward somewhere but will not throw exception, so it won't be caught. Perhaps there is some possibility to make spring security throw accessdeniedexception, but not by default.

wimdeblauwe commented 6 months ago

Thanks for that info. I added a second test now, and an implementation of AccessDeniedHandler that can be used now.

chpasha commented 6 months ago

Great, thank you :-)

wimdeblauwe commented 6 months ago

4.3.0 is released: https://github.com/wimdeblauwe/error-handling-spring-boot-starter/releases/tag/4.3.0