从 Angular 2 向 Spring 支持的 Java 应用程序发送 POST 请求时出现 CSRF 问题

CSRF issue while sending POST request from Angular 2 to Spring-backed Java app

我已经 UI 使用 Angular 2 和基于 Java 的后端编写,在 [=33= 之上使用 OpenID Connect 身份验证]Spring安全.

身份验证工作正常,但仅适用于 GET 请求。每次对资源执行 POST、PUT 或 DELETE 方法时,我都会收到 HTTP 403:

{
  "status": 403,
  "message": "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.",
}

我是这样使用 HttpClient 的:

http.post(
    '/api/my-resource',
    JSON.stringify(myObject),
    new RequestOptions({
        headers: new Headers({'Content-Type': 'application/json'})
    })
)

当我按照建议将 withCredentials: true 添加到 RequestOptions 时 我仍然收到 HTTP 403,但消息不同:

{
  "status": 403,
  "message": "Could not verify the provided CSRF token because your session was not found.",
}

我需要做什么才能让 HttpClient 使用 CSRF?

注意:我也看过类似的问题,特别是,但是那里提出的解决方案并没有解决我的问题。 同时我不想禁用CSRF。

更新 Angular >= 6

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
    HttpClientXsrfModule.withOptions({cookieName: 'XSRF-TOKEN'})
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

原回答

添加 CSRF cookie 配置如下:

@NgModule({
    declarations: [declarations],
    imports: [imports],
    providers:
    [
        {provide: XSRFStrategy, useFactory: xsrfFactory}
    ],
    bootstrap: [AppComponent]
})
export class AppModule {
}

export function xsrfFactory() {
    return new CookieXSRFStrategy('XSRF-TOKEN', 'XSRF-TOKEN');
}

并使用正确的 header 名称配置 Spring 安全性。

    private static final String CSRF_HEADER_NAME = "XSRF-TOKEN";

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http
            ...
            .and()
                .csrf()
                    .ignoringAntMatchers(CSRF_IGNORE)
                    .csrfTokenRepository(csrfTokenRepository())
            .and()
               .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class);
    }

    private CsrfTokenRepository csrfTokenRepository() {
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName(CSRF_HEADER_NAME);
        return repository;
    }

其中 CsrfHeaderFilter :

public class CsrfHeaderFilter extends OncePerRequestFilter {

    private static final String CSRF_COOKIE_NAME = "XSRF-TOKEN";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());

        if (csrf != null) {

            Cookie cookie = WebUtils.getCookie(request, CSRF_COOKIE_NAME);
            String token = csrf.getToken();

            if (cookie == null || token != null && !token.equals(cookie.getValue())) {
                cookie = new Cookie(CSRF_COOKIE_NAME, token);
                cookie.setPath("/");
                response.addCookie(cookie);
            }
        }

        filterChain.doFilter(request, response);
    }
}

使用 spring-boot 2.2.2(spring-security 5.2.1)和 Angular 8.2.14,此配置适用于我:

  @NgModule({
  declarations: [],
  imports: [
    BrowserModule,
    HttpClientModule,
    HttpClientXsrfModule.withOptions({cookieName: 'XSRF-TOKEN'}),

与 Radouane 的更新响应相同的配置。

但是对于springboot部分:

@Override
protected void configure(HttpSecurity http) throws Exception {

    http.csrf()
         .csrfTokenRepository(cookieCsrfTokenRepository())
         .and()
         .authorizeRequests()
         .antMatchers("/static/**").permitAll()
         ...
         .anyRequest().authenticated();
}

private CookieCsrfTokenRepository cookieCsrfTokenRepository() {

    final CookieCsrfTokenRepository cookieCsrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
    cookieCsrfTokenRepository.setCookiePath("/");
    return cookieCsrfTokenRepository;
}

据此answer,

Angular 将添加 X-XSRF-TOKEN header 仅当 XSRF-TOKEN cookie 生成时 server-side 具有以下选项:

Path = /
httpOnly = false

此外,Angular应用程序和被调用的URL应用程序必须位于同一台服务器上。