After struggling with this for a few days (searching SO for similar questions, doing trial & error), I am tempted to give up…
So the problem is I have a REST service based on Spring Boot using Spring Security and JWT for authentication. Now I want to secure some of the methods to be only called by authorized people using the @PreAuthorize
-annotation.
This seems to work partly because instead of calling the method Spring returns 404. I would have expected 403.
I have read this SO-question and tried the answers given there, but it did not help. I have moved the @EnableGlobalMethodSecurity(prePostEnabled = true)
-Annotation from my SecurityConfiguration to the Application class as suggested elsewhere, still it does not work.
My security configuration looks like this:
@Configuration @Profile("production") @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Value("${adDomain}") private String adDomain; @Value("${adUrl}") private String adUrl; @Value("${rootDn}") private String rootDn; @Value("${searchFilter}") private String searchFilter; private final AuthenticationManagerBuilder auth; private final SessionRepository sessionRepository; @Autowired public SecurityConfiguration(AuthenticationManagerBuilder auth, SessionRepository sessionRepository) { this.auth = auth; this.sessionRepository = sessionRepository; } @Override public void configure(WebSecurity webSecurity) throws Exception { webSecurity .ignoring() // All of Spring Security will ignore the requests .antMatchers("/static/**", "/api/web/logout") .antMatchers(HttpMethod.POST, "/api/web/login"); } @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // Using JWT there is no need for CSRF-protection! .authorizeRequests() .anyRequest().authenticated() .and() .addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository)); } @Bean(name = BeanIds.AUTHENTICATION_MANAGER) @Override public AuthenticationManager authenticationManagerBean() throws Exception { ActiveDirectoryLdapAuthenticationProvider adProvider = new ActiveDirectoryLdapAuthenticationProvider(adDomain, adUrl, rootDn); adProvider.setConvertSubErrorCodesToExceptions(true); adProvider.setUseAuthenticationRequestCredentials(true); adProvider.setSearchFilter(searchFilter); adProvider.setUserDetailsContextMapper(new InetOrgPersonContextMapper()); auth.authenticationProvider(adProvider); return super.authenticationManagerBean(); }
}
The controller method looks like this
@RequestMapping(path = "/licenses", method = RequestMethod.GET) @PreAuthorize("hasRole('ADMIN')") public ResponseEntity<?> getAllLicenses(@RequestParam("after") int pagenumber, @RequestParam("size") int pagesize , @RequestParam("searchText") String searchText) { List<LicenseDTO> result = ... return new ResponseEntity<Object>(result, HttpStatus.OK); }
I am quite sure I am missing something very simple, but I just cannot figure out what.
By the way: if the user requesting the licenses has the ADMIN role everything works as expected, so the problem is not a real 404.
Advertisement
Answer
You need to define the exceptionHandling at security configuration as follows,
@Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() // Using JWT there is no need for CSRF-protection! .authorizeRequests() .anyRequest().authenticated() .and() .exceptionHandling().accessDeniedHandler(new AccessDeniedExceptionHandler()) .and() .addFilter(new JwtAuthorizationFilter(authenticationManagerBean(), sessionRepository)); }
You can define AccessDeniedExceptionHandler class as follows,
public class AccessDeniedExceptionHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException ex) throws IOException, ServletException { response.setStatus(HttpStatus.FORBIDDEN); } }