Spring Security anonymous 401 instead of 403

22,256

Solution 1

I've got solution here:

http
   .authenticationEntryPoint(authenticationEntryPoint)

AuthenticationEntryPoint source code:

@Component
public class Http401UnauthorizedEntryPoint implements AuthenticationEntryPoint {

    private final Logger log = LoggerFactory.getLogger(Http401UnauthorizedEntryPoint.class);

    /**
     * Always returns a 401 error code to the client.
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException arg2) throws IOException,
            ServletException {

        log.debug("Pre-authenticated entry point called. Rejecting access");
        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Access Denied");
    }
}

Solution 2

As of Spring Boot 2 class Http401AuthenticationEntryPoint has been removed (see Spring Boot Issue 10725).

Instead of Http401AuthenticationEntryPoint use HttpStatusEntryPoint with HttpStatus.UNAUTHORIZED:

http.exceptionHandling()
    .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));

Solution 3

With spring security 4.x there is already a class for that

org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint 

Spring boot also includes one

org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint

and both benefits that they require the developer to use spec compliant as 401 responses requires that header WWW-Authenticate must be set, example 401 response could be:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer realm="example",
                   error="invalid_token",
                   error_description="The access token expired"

So in your security configuration you define and autowire a bean of class

So for instance with spring boot app:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter{

    @Bean
    public Http401AuthenticationEntryPoint securityException401EntryPoint(){

        return new Http401AuthenticationEntryPoint("Bearer realm=\"webrealm\"");
    }

...
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .authorizeRequests()
                .antMatchers("/login").anonymous()
                .antMatchers("/").anonymous()
                .antMatchers("/api/**").authenticated()
            .and()
            .csrf()
                .disable()
                .headers()
                .frameOptions().disable()
            .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .logout()
                .permitAll()
         .exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());
}

the relevant line is:

 .exceptionHandling().authenticationEntryPoint(securityException401EntryPoint());

Solution 4

You need to extend AuthenticationEntryPoint to do customization based upon the exceptions.

@ControllerAdvice
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
  @Override
  public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException)
      throws IOException, ServletException {
    // 401
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authentication Failed");
  }

  @ExceptionHandler (value = {AccessDeniedException.class})
  public void commence(HttpServletRequest request, HttpServletResponse response,
      AccessDeniedException accessDeniedException) throws IOException {
    // 401
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Authorization Failed : " + accessDeniedException.getMessage());
  }
}

Specify the above custom AuthenticationEntryPoint in your SecurityConfig like below:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity (prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.exceptionHandling()
        .authenticationEntryPoint(new MyAuthenticationEntryPoint());
  }
}

Solution 5

A simple approach in Spring Boot 2 using lambda expressions:

@Override
public void configure(HttpSecurity http) throws Exception {
    http.
        ...
        .exceptionHandling()
            .authenticationEntryPoint((request, response, e) -> {
                response.setStatus(HttpStatus.UNAUTHORIZED.value());
                response.setContentType("application/json");
                response.getWriter().write("{ \"error\": \"You are not authenticated.\" }");
            })
        ...
}
Share:
22,256
Mati
Author by

Mati

Updated on August 10, 2021

Comments

  • Mati
    Mati over 2 years

    I have a problem with default behaviour in spring security with authorize requests provided with Java Config.

    http
           ....
           .authorizeRequests()
              .antMatchers("/api/test/secured/*").authenticated()
    

    When I do a call to for example /api/test/secured/user without login (with anonymous user), it returns 403 Forbidden. Is there an easy way to change status to 401 Unauthorized when anonymous user wants to get secured by authenticated() or @PreAuthorize resource?