Why AccessDeniedException and AuthenticationException be handled differently

1.7k views Asked by At

I create a demo app with Spring Boot 2 and Spring Security 5. The source locates at here.

It provides two kinds of endpoints -- HTML web pages and REST apis. Therefore, two subclasses of WebSecurityConfigurerAdapter are introduced as follows:

@Configuration
@Order(1)
public class ApiSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
        http.antMatcher("/api/**")
            .authorizeRequests()
            .antMatchers("/api/admin**").hasRole("ADMIN")
            .anyRequest().authenticated()
            .and().httpBasic();
    }
}

@Configuration
public class PageSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
            .authorizeRequests()
            .antMatchers("/login").permitAll()
            .anyRequest().authenticated()
            .and().formLogin().loginPage("/login").permitAll().defaultSuccessUrl("/index")
            .and().logout()
            .invalidateHttpSession(true).deleteCookies("JSESSIONID")
            .logoutSuccessUrl("/login").permitAll();
    }
}

There are three cases which will cause authentication and authorization failed for a REST api call:

  1. No credentials provided for a protected endpoint
  2. Wrong credentials provided
  3. Right credentials provided with wrong roles

The result shows org.springframework.security.access.AccessDeniedException risen in case 1 and 3, and org.springframework.security.authentication.BadCredentialsException risen in case 2, and the return values for the three cases are:

  1. a json error message with HTTP status 401
  2. HTTP status 302 redirected to /login endpoint returned
  3. a json error message with HTTP status 403

For case 2, the phenomenon has been explained in my another question. Generally, it is because the exception will be handled by default error endpoint /error, and in Spring Boot 2 the endpoint has been also protected and therefore requires login (for detail -- Spring security 5 "Bad credentials" exception not shown with errorDetails).

The further question is why case 1 and 3 are not handled by the same mechanism? Why does not the default error handler handle the AccessDeniedException with /error endpoint?

1

There are 1 answers

3
motzmann On

HTTP.401 or »Unauthrized« is because the security context didn't receive any credentials. And it also depends on the URL being invoked (static page, role and so on). Normally a browser would display a login dialog. Your illustration does not contain any configuration of this forwarding case to a login form.

HTTP.403 or »Forbidden« is already at the end of the chain: wrong role thus no access (no forward, no browser's dialog).

Does your *SecurityConfig reside in different components/ JARs? I assume not, because you use @Order-annotation. If your intention is to have both active at the same time and they're in the same JAR merge them to a single one. This makes it more obvious what exactly is meant without keeping the one or the other in mind.

I also can only guess how your roles are expressed. At least three different options exist for example @RolesAllowed, Spring EL or as you did. And mind the difference between Spring's Granted Authority and Role. The latter has to start with 'ROLE_' if not configured otherwise to be found with hasRole SpringEL. Everything else ends up as Granted Authority instead.

There are also additional endpoints maybe lacking protection or now being inaccessible, e.g. monitoring- and health-endpoints. Depending on their configuration it might have an effect on all endpoints (I simply assume they would be configured through their properties in a src/main/application.properties or .yaml and not using any @Configuration-class.)