Spring Security 未在 REST 应用程序中发送 CSRF 令牌

rbo*_*sov 4 spring csrf spring-security angular

我是 Spring Security 的新手,我正在尝试了解 CSRF 机制。我有一个基于 Spring 的 Angular 应用程序。据我所知,Spring 将在收到的第一个 GET 请求(称为 XSRF-TOKEN)上的 cookie 中发送 CSRF 令牌,然后在每个后续请求中,它将在另一个名为(X-XSRF-TOKEN)的 cookie 中查找该令牌)。我的问题是它没有生成令牌,我不明白为什么。我正在使用最新的 Spring 和 Angular 版本。

链接到 github 存储库

我创建了一个虚拟应用程序,其中有一个 /home 端点,它接受 GET 请求,然后返回一个虚拟字符串。我已经在 Angular 端设置了 withCredentials: true 并配置了 CookieCsrfTokenRepository。我没有看到包含令牌的 Set-cookie 标头。我应该做什么以及我做错了什么?

Spring安全配置

@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .cors()
                .and()
                .csrf()
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeHttpRequests()
                .requestMatchers(PathRequest.toStaticResources().atCommonLocations())
                .permitAll()
                .requestMatchers("/home")
                .permitAll()
                .anyRequest()
                .authenticated();

        return http.build();
    }

    @Bean
    public CorsFilter corsFilter() {
        UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setAllowedOrigins(Collections.singletonList("http://localhost:4200"));
        corsConfiguration.setAllowedHeaders(Arrays.asList("Origin", "Access-Control-Allow-Origin", "Content-Type",
                "Accept", "Authorization", "Origin, Accept", "X-Request-With", "Access-Control-Request-Method",
                "Access-Control-Request-Headers", "XSRF-TOKEN", "X-XSRF-TOKEN"));
        corsConfiguration.setExposedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization",
                "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials", "XSRF-TOKEN", "X-XSRF-TOKEN"));
        corsConfiguration.setAllowedMethods(Arrays.asList(
                HttpMethod.GET.name(), HttpMethod.POST.name(),
                HttpMethod.PUT.name(), HttpMethod.PATCH.name(),
                HttpMethod.DELETE.name(), HttpMethod.OPTIONS.name()
        ));
        urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
        return new org.springframework.web.filter.CorsFilter(urlBasedCorsConfigurationSource);
    }
}
Run Code Online (Sandbox Code Playgroud)

家庭控制器

@RestController
public class HomeResource {

    @GetMapping("/home")
    public String home() {
        return "Home is working";
    }
}
Run Code Online (Sandbox Code Playgroud)

主页.service.ts

@Injectable({
  providedIn: 'root'
})
export class HomeService {

  apiHost: string = 'http://www.localhost:8000';

  constructor(private http: HttpClient) {

  }

  public home(): Observable<string> {
    return this.http.get(`${this.apiHost}/home`, {withCredentials: true, responseType: 'text'});
  }
}
Run Code Online (Sandbox Code Playgroud)

home.component.html

<p>{{home$ | async}}</p>

Run Code Online (Sandbox Code Playgroud)

home.component.ts

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {

  home$!: Observable<string>;

  constructor(private homeService: HomeService) {
  }

  ngOnInit(): void {
    this.home$ = this.homeService.home();
  }
}
Run Code Online (Sandbox Code Playgroud)

Mar*_*gio 8

请参阅此迁移文档,并完整阅读。

总之,Spring Security 6.0 推迟了 的加载CsrfToken,这意味着它不会在响应中自动包含令牌。

您有几种方法可以解决这个问题:

  1. 恢复旧行为(如文档中所述):
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
    CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
    // set the name of the attribute the CsrfToken will be populated on
    requestHandler.setCsrfRequestAttributeName(null);
    http
        // ...
        .csrf((csrf) -> csrf
            .csrfTokenRequestHandler(requestHandler)
        );
    return http.build();
}
Run Code Online (Sandbox Code Playgroud)
  1. CsrfToken创建一个从请求中检索的控制器端点:
@RestController
@RequestMapping("/csrf")
public class CsrfController {

    @GetMapping
    public void getCsrfToken(HttpServletRequest request) {
        // https://github.com/spring-projects/spring-security/issues/12094#issuecomment-1294150717
        CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        csrfToken.getToken();
    }

}
Run Code Online (Sandbox Code Playgroud)

你可以考虑一下http.requestMatchers("/csrf").permitAll()

在所有情况下,您只需从请求属性访问 CsrfToken 即可。

并且,在 Angular 方面,您可以创建一个APP_INITIALIZER提供程序,该提供程序将在应用程序初始化时获取 CSRF 令牌:

function getCsrfToken(httpClient: HttpClient): () => Observable<any> {
  return () => httpClient.get('/csrf').pipe(catchError((err) => of(null)));
}

@NgModule({
  ...
  providers: [
    {
      provide: APP_INITIALIZER,
      useFactory: getCsrfToken,
      deps: [HttpClient],
      multi: true,
    },
  ],
  ...
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)