简体   繁体   English

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

[英]Spring Security not sending CSRF token in REST Application

I'm new to Spring Security and I'm trying to understand the CSRF mechanism.我是 Spring Security 的新手,我正在尝试了解 CSRF 机制。 I have a Spring based application with Angular.我有一个基于 Spring 的 Angular 应用程序。 As far as I know, Spring will send a CSRF Token in a cookie on the first GET Request it recieves (called XSRF-TOKEN) and then on every subsequent request it will look for that token in another cookie called (X-XSRF-TOKEN).据我所知,Spring 会在它收到的第一个 GET 请求(称为 XSRF-TOKEN)上在 cookie 中发送一个 CSRF 令牌,然后在每个后续请求中它会在另一个名为(X-XSRF-TOKEN)的 cookie 中查找该令牌). My problem is that it doesn't generate a token and I can't figure out why.我的问题是它不生成令牌,我不知道为什么。 I'm using the latest Spring and Angular versions.我正在使用最新的 Spring 和 Angular 版本。

Link to the github repo链接到 github 仓库

I created a dummy application, in which I have a /home endpoint, which accepts GET request and then returns a dummy string.我创建了一个虚拟应用程序,其中有一个 /home 端点,它接受 GET 请求,然后返回一个虚拟字符串。 I've set the withCredentials: true on the Angular side and I've configured CookieCsrfTokenRepository.我在 Angular 端设置了 withCredentials: true 并且配置了 CookieCsrfTokenRepository。 I don't see the Set-cookie header containing the token.我没有看到包含令牌的 Set-cookie 标头。 What am I expected to do and what am I doing wrong?我应该做什么,我做错了什么?

Spring Security Configuration弹簧安全配置

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

Home Controller家庭控制器

@RestController
public class HomeResource {

    @GetMapping("/home")
    public String home() {
        return "Home is working";
    }
}

Angular有角的

home.service.ts家庭服务.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'});
  }
}

home.component.html主页.component.html

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

home.component.ts主页.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();
  }
}

Please refer to this Migration documentation , and read it completely.请参考此迁移文档,并完整阅读。

In summary, Spring Security 6.0 defer the loading of the CsrfToken , this means that it will not include the token automatically in the response.总之,Spring Security 6.0 推迟了CsrfToken的加载,这意味着它不会在响应中自动包含令牌。

You have a few ways to solve this:你有几种方法可以解决这个问题:

  1. Restore the old behavior (as described in the documentation):恢复旧行为(如文档中所述):
@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();
}
  1. Create a controller endpoint that retrieves the CsrfToken from the request:创建一个从请求中检索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();
    }

}

You might consider http.requestMatchers("/csrf").permitAll() .您可能会考虑http.requestMatchers("/csrf").permitAll()

In all cases, you have simply to access the CsrfToken from request attributes.在所有情况下,您只需从请求属性访问 CsrfToken。

And, on the Angular side, you can create a APP_INITIALIZER provider that will get the CSRF token when the application initializes:而且,在 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 {}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM