从 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应用程序必须位于同一台服务器上。
我已经 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 时
{
"status": 403,
"message": "Could not verify the provided CSRF token because your session was not found.",
}
我需要做什么才能让 HttpClient
使用 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应用程序必须位于同一台服务器上。