Mati
Mati

Reputation: 2576

Spring Security anonymous 401 instead of 403

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?

Upvotes: 50

Views: 37922

Answers (7)

Maksym Ivanov
Maksym Ivanov

Reputation: 35

Works for Spring 6

@Bean
 public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http.exceptionHandling(exp -> exp.authenticationEntryPoint(new 
    HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)));
}

Upvotes: 0

Anton
Anton

Reputation: 730

Who interested in mechanism of work.
If you don't set http.exceptionHandling().authenticationEntryPoint() spring will use defaultAuthenticationEntryPoint() and method ExceptionHandlingConfigurer.createDefaultEntryPoint() will return new Http403ForbiddenEntryPoint()
So, just create Http401UnauthorizedEntryPoint(). Above answers how to do it, didn't duplicate it.

P.S. It's actual for Spring Security 5.2.5.RELEASE

Upvotes: 1

le0diaz
le0diaz

Reputation: 2508

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());

Upvotes: 21

Hamid Mohayeji
Hamid Mohayeji

Reputation: 4295

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.\" }");
            })
        ...
}

Upvotes: 6

Tai Truong
Tai Truong

Reputation: 778

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));

Upvotes: 37

Sahil Chhabra
Sahil Chhabra

Reputation: 11716

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());
  }
}

Upvotes: 5

Vadim Fedorenko
Vadim Fedorenko

Reputation: 2571

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");
    }
}

Upvotes: 17

Related Questions