I have implemented the AccessDeniedHandler interface and added the interface to the WebSecurityConfigurerAdapter, and I also have an ExceptionHandler of RuntimeException in my ControllerAdvice. When I receive accessDeniedException from MethodSecurity, ExceptionHandler calls RuntimeException before running AccessDeniedHandler.
How to call AccessDeniedHandler without deleting ExceptionHandler?
this is my WebSecurityConfig:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";
@Value("${payment.gateway.callback-path}")
private String paymentCallbackPath;
@Autowired
private ThingspodAccessDeniedHandler accessDeniedHandler;
@Autowired
private RestAuthenticationFailureHandler authenticationFailureHandler;
@Autowired
private JwtAuthenticationProvider jwtAuthenticationProvider;
protected JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
List<String> pathsToSkip = new ArrayList<>(Collections.singletonList(paymentCallbackPath));
SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
JwtAuthenticationFilter filter = new JwtAuthenticationFilter(matcher, JWT_TOKEN_HEADER_PARAM, authenticationFailureHandler);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) {
auth.authenticationProvider(jwtAuthenticationProvider);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.headers().cacheControl().and().frameOptions().disable()
.and()
.cors()
.and()
.csrf().disable()
.exceptionHandling()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers(paymentCallbackPath).permitAll()
.and()
.authorizeRequests()
.antMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated()
.and()
.authorizeRequests()
.and()
.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
}
and this is my AccessDeniedHander and ExceptionHandler in ControllerAdvice:
@Component
public class ThingspodAccessDeniedHandler implements AccessDeniedHandler {
private final ObjectMapper mapper;
public ThingspodAccessDeniedHandler(ObjectMapper mapper) {
this.mapper = mapper;
}
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
Object res = buildResponse(accessDeniedException, "You don't have permission to perform this operation!", HttpStatus.FORBIDDEN, ThingspodErrorCode.PERMISSION_DENIED, request);
response.setStatus(HttpStatus.FORBIDDEN.value());
mapper.writeValue(response.getWriter(), res);
}
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleAllUncaughtRuntimeException(
RuntimeException ex, WebRequest request){
return buildApiErrorResponse(ex, HttpStatus.INTERNAL_SERVER_ERROR, ThingspodErrorCode.GENERAL, request);
}
CodePudding user response:
When selecting a error handler, spring will look for the most specific one.
Since you have exception handler for the more general exception (RuntimeException
) and another one for AccessDeniedException
.
In that case first exception handler (for handling RuntimeException
) won't be called when AccessDeniedException
occurs.
So you could keep exception handler for handling RuntimeExceptions, but you will need to add exception handler for handling AccessDeniedException
and move logic from ThingspodAccessDeniedHandler
into that handler method, like this:
@ExceptionHandler(UnknownHostException.class)
public void handleUnknownAccessDeniedException(UnknownHostException e, WebRequest request) {
Object res = buildResponse(accessDeniedException, "You don't have permission to perform this operation!", HttpStatus.FORBIDDEN, ThingspodErrorCode.PERMISSION_DENIED, request);
response.setStatus(HttpStatus.FORBIDDEN.value());
mapper.writeValue(response.getWriter(), res);
}
@ExceptionHandler(RuntimeException.class)
public ResponseEntity<Object> handleAllUncaughtRuntimeException(
RuntimeException ex, WebRequest request){
return buildApiErrorResponse(ex, HttpStatus.INTERNAL_SERVER_ERROR, ThingspodErrorCode.GENERAL, request);
}