Spring Безопасность анонимного 401 вместо 403

У меня проблема с поведением по умолчанию в безопасности spring с авторизацией запросов, предоставляемых с помощью Java Config.

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

Когда я вызываю, например, /api/test/secured/user без входа (с анонимным пользователем), он возвращает 403 Forbidden. Есть ли простой способ изменить статус на 401 Несанкционированный, когда анонимный пользователь хочет получить защиту с помощью ресурсов authenticated() или @PreAuthorize?

Ответ 1

У меня есть решение здесь:

http
   .authenticationEntryPoint(authenticationEntryPoint)

Исходный код AuthenticationEntryPoint:

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

Ответ 2

С spring security 4.x уже существует класс для

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

Spring boot также включает в себя один

org.springframework.boot.autoconfigure.security.Http401AuthenticationEntryPoint

и оба преимущества, которые они требуют, чтобы разработчик использовал спецификацию в качестве 401 ответов требует, чтобы заголовок WWW-Authenticate должен быть установлен, например, 401 ответ может быть:

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

Итак, в вашей конфигурации безопасности вы определяете и autowire bean класса

Так, например, с spring загрузочным приложением:

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

    @Bean
    public Http401AuthenticationEntryPoint securityException401EntryPoint(){

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

    @Autowired
    private Http401AuthenticationEntryPoint authEntrypoint;
...
@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(authEntrypoint);
}

соответствующая строка:

 .exceptionHandling().authenticationEntryPoint(authEntrypoint);

Ответ 3

Начиная с Spring Boot 2 класс Http401AuthenticationEntryPoint был удален (см. Spring Boot Issue 10725).

Вместо Http401AuthenticationEntryPoint используйте HttpStatusEntryPoint с HttpStatus.UNAUTHORIZED:

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

Ответ 4

Вам нужно расширить AuthenticationEntryPoint, чтобы выполнить настройку на основе исключений.

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

Укажите указанный выше пользовательский AuthenticationEntryPoint в вашем SecurityConfig, как показано ниже:

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

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

Ответ 5

Простой подход в Spring Boot 2 с использованием лямбда-выражений:

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