Spring Security-身份验证后响应中的CSRF令牌错误

时间:2019-12-07 16:55:49

标签: spring-boot spring-security csrf-token

我对Spring Security的理解是,在进行身份验证时,将在执行身份验证之前生成一个csrf令牌,在成功完成身份验证之后生成另一个csrf令牌。

我注意到身份验证后,响应头中返回了错误的csrf令牌,当尝试访问POST端点时,它会导致403响应(无效的CSRF令牌)。该项目可以在github上找到; here。这是一些特定于安全性的代码:

Spring安全配置:

@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  public void configure(HttpSecurity httpSecurity) throws Exception {
    CsrfTokenRepository csrfTokenRepository = new HttpSessionCsrfTokenRepository();

    httpSecurity
        .authorizeRequests().anyRequest().authenticated().and()
        //.antMatchers("/status").permitAll().and()
        .cors().and().csrf().csrfTokenRepository(csrfTokenRepository).and()
        .authenticationProvider(new BasicAuthenticationProvider())
        .addFilterAfter(new CsrfTokenResponseHeaderBindingFilter(csrfTokenRepository), CsrfFilter.class)
        .addFilterBefore(new CustomBasicAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  }

  @Bean
  CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();

    configuration.setAllowedOrigins(new ArrayList<>());
    configuration.setAllowedMethods(ImmutableList.of("GET", "POST"));
    configuration.setAllowedHeaders(ImmutableList.of("*"));
    configuration.setExposedHeaders(ImmutableList.of("X-Csrf-Token"));

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);

    return source;
  }
}

用于在响应头中添加CSRF令牌的过滤器:

public class CsrfTokenResponseHeaderBindingFilter extends OncePerRequestFilter {
  private static final String CSRF_TOKEN_NAME = "x-csrf-token";
  private CsrfTokenRepository csrfTokenRepository;

  public CsrfTokenResponseHeaderBindingFilter(CsrfTokenRepository csrfTokenRepository) {
    this.csrfTokenRepository = csrfTokenRepository;
  }

  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    response.addHeader(CSRF_TOKEN_NAME, csrfTokenRepository.loadToken(request).getToken());

    System.out.println("shoop: " + csrfTokenRepository.loadToken(request).getToken());
    filterChain.doFilter(request, response);
    System.out.println("shap: " + csrfTokenRepository.loadToken(request).getToken());
  }
}

用于基本身份验证的过滤器:

public class CustomBasicAuthenticationFilter extends OncePerRequestFilter {
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    BasicAuthenticationToken basicAuthenticationToken = generateBasicAuthenticationToken(request);

    if (basicAuthenticationToken != null) {
      SecurityContextHolder.getContext().setAuthentication(basicAuthenticationToken);
    }

    filterChain.doFilter(request, response);
  }

  private BasicAuthenticationToken generateBasicAuthenticationToken(HttpServletRequest request) {
    System.out.println("generateBasicAuthenticationToken");
    String authorizationHeaderValue = request.getHeader("Authorization");
    BasicAuthenticationToken basicAuthenticationToken = null;

    if (authorizationHeaderValue != null && authorizationHeaderValue.startsWith("Basic ")) {
      System.out.println("basic authentication header provided");
      final String base64EncodedCredentials = authorizationHeaderValue.replace("Basic ", "").trim();
      final byte[] decodedCredentials = Base64.getDecoder().decode(base64EncodedCredentials);
      final String credentials = new String(decodedCredentials);
      final String[] splitCredentials = credentials.split(":", 2);

      System.out.println("credentials: " + credentials);
      System.out.println("username: " + splitCredentials[0]);
      System.out.println("password: " + splitCredentials[1]);

      basicAuthenticationToken = new BasicAuthenticationToken (splitCredentials[0], splitCredentials[1]);
    }

    return basicAuthenticationToken;
  }
}

基本身份验证提供程序:

public class BasicAuthenticationProvider implements AuthenticationProvider {
  private final String VALID_USERNAME = "user";
  private final String VALID_PASSWORD = "pass";

  @Override
  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
    System.out.println("BasicAuthenticationProvider");
    String username = authentication.getName();
    String password = (String) authentication.getCredentials();

    if (!username.equals(VALID_USERNAME) || !password.equals(VALID_PASSWORD)) {
      throw new InvalidCredentialsException("Invalid credentials provided.");
    }

    return new UsernamePasswordAuthenticationToken(username, "", Collections.emptyList());
  }

  @Override
  public boolean supports(Class<?> aClass) {
    return BasicAuthenticationToken.class.equals(aClass);
  }
}

为确认问题与无效的csrf令牌有关,我打印了在身份验证前后由spring生成的csrf令牌,并将这些令牌与响应标头中的令牌进行了比较。另外,当我使用调试级别的日志记录运行该应用程序时,在尝试通过身份验证后尝试命中经过身份验证的POST端点时,会从spring中收到以下错误消息:Invalid CSRF token found for http://localhost:8080/auth/status

在执行此测试时,我使用了以下curl命令:

curl -XGET 'http://localhost:8080/login' -H 'Authorization: Basic dXNlcjpwYXNz' -v
curl -XPOST 'http://localhost:8080/auth/status' -H 'Cookie: <session id>' -H 'x-csrf-token: <csrf token>' -v

P.S .:对于在本地运行上面链接到我的项目的任何人,当您运行脚本/构建时,系统都会要求您在本地计算机上输入凭据。这是因为我在需要超级用户特权的Linux机器上使用docker。

0 个答案:

没有答案
相关问题