ng2从cookie中获取CSRF令牌,并将其作为头部提交。

10

在花费了整整2天的时间在网上搜索、阅读文档和大量开放性问题后,我仍然不明白Angular 2如何处理(x-origin) cookie以及如何访问它们。

问题:后端将两个cookie发送到前端,其中包含x-csrf-token和JSESSIONID。我的工作是在内存中保留csrf令牌(ng2),并且将其作为标头 (而不是cookie) 与每个post请求一起发送回后端。

HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Access-Control-Allow-Origin: http://localhost:4200
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Access-Control-Allow-Origin,Access-Control-Allow-Credentials
Set-Cookie: x-csrf-token=8555257a-396f-43ac-8587-c6d489e76026; Path=/app
Set-Cookie: JSESSIONID=73E38392C60370E38FBAF80143ECE212; Path=/app/; HttpOnly
Expires: Thu, 12 Apr 2018 07:49:02 GMT
Cache-Control: max-age=31536000
Content-Type: application/json;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 12 Apr 2017 07:49:02 GMT

我的部分解决方案: 我创建了一个自定义的RequestOptions类,它继承了BaseRequestOptions。添加了一些额外的头部信息,并将'withCredentials'设置为true。

export class MyRequestOptions extends BaseRequestOptions {

  headers: Headers = new Headers({
    'Accept': 'application/json',
    'Content-Type': 'application/json',
  });

  withCredentials = true;
}

在我的 HttpService 中,我这样进行 post 和 get 请求:

@Injectable()
export class HttpService {

  constructor(
    protected _http: Http,
    protected requestOptions: RequestOptions
  ) {  }

  get(url): Observable<any> {
    return this._http.get(url, this.requestOptions).map( res => res.json() );
  }

  post(url: string, object: any): Observable<any> {
    return this._http.post(url, object, this.requestOptions).map( res => res.json() );
  }
}

我在我的app.module中这样做:

 providers: [
    { provide: RequestOptions, useClass: DocumentumDefaultRequestOptions },
    { provide: XSRFStrategy, useFactory: xsrfFactory }
  ],

我的xsrfFactory

export function xsrfFactory() {
  return new CookieXSRFStrategy('x-csrf-token', 'x-csrf-token');
}

我的部分结果: 此时Angular会在每个请求中发送一个Cookie(不区分GET和POST),其中包含jsessionid和x-csrf-token,形如:

POST /app/business-objects/business-objects-type HTTP/1.1
Host: localhost:8040
Connection: keep-alive
Content-Length: 26
Pragma: no-cache
Cache-Control: no-cache
Authorization: Basic ZG1hZG1pbjphZG1pbg==
Origin: http://localhost:4200
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36
Content-Type: application/json
Accept: application/json
Referer: http://localhost:4200/page
Cookie: JSESSIONID=B874C9A170EFC12BEB0EDD4266896F2A; x-csrf-token=0717876e-f402-4a1c-a31a-2d60e48509d3

我的十亿美元问题:

  • 我应该在哪里获取 x-csrf-token,并如何将其添加到我的请求中?
  • CookieXSRFStrategy('x-csrf-token', 'x-csrf-token') 到底是做什么的?我不喜欢文档中对它的解释,能否访问它来获取数据?

在发送 HTTP 请求之前,CookieXSRFStrategy 查找名为 XSRF-TOKEN 的 cookie,并使用该 cookie 的值设置名为 X-XSRF-TOKEN 的头部。

  • 在我的情况下它并没有设置这个头部,但为什么呢?

  • 现在我正在与 sessionid 和 csrf token 一起向后端发送 cookie,但是是 CookieXSRFStrategy 或 "withCredentials" 标志在发送它们呢?

请不要用像 "document.cookie" 这样的一行回答。没有元数据的数据是无用的。


x-csrf-token cookie是由后端与文档一起发送还是与ajax GET请求一起发送的? - n00dl3
document.cookie 返回什么? - n00dl3
Q1:后端会自动发送csrf令牌,无论是通过“rest调用”还是“浏览器导航”,每次都会发送(不考虑其来源)。如果后端在请求中看到有效的会话令牌作为cookie,则会在响应中省略该信息。Q2:请不要用“document.cookie”这样的一行代码回答。如果你想让我尝试某些东西,请说明在哪里以及如何实现它。 - VikingCode
1
  1. 这不是一个答案,这是一条评论,这样的答案会被删除。
  2. 你删除的评论“你读了问题吗?”没有建设性,也不鼓励人们帮助你。
  3. 没有人在这里是你的雇员,我不是为了帮助你而受到报酬的,其他人也不是。“我不明白我应该在哪里尝试这个,你能详细说明一下吗?”比“如果你想让我尝试什么,就说在哪里和如何实现”要温和得多。友善一点。
- n00dl3
2个回答

21

更新 Angular 5.0+

Http 服务已被弃用,建议使用 HttpClient。同时,CookieXSRFStrategy 类也被弃用,现在该任务由 HttpClientXsrfModule 类来完成。如果您想自定义标头和 cookie 名称,只需像这样导入此模块:

@NgModule({
  imports: [
    HttpClientModule,
    HttpClientXsrfModule.withOptions({
      cookieName: 'My-Xsrf-Cookie',
      headerName: 'My-Xsrf-Header',
    }),
  ]
})
export class MyModule{}

对于未来的读者:

Ajax响应cookie

如果您查看XHR规范,您会注意到任何访问与“Set-Cookie”匹配的标头都是被禁止的:

被禁止的响应标头名称是一个字节大小写不敏感匹配的标头名称之一:

  • Set-Cookie
  • Set-Cookie2

但我的cookie没有httpOnly

那很好,但httpOnly只表示您的cookie无法通过document.cookie访问(请参见下文)。

document.cookie API

您将能够使用JavaScript访问的唯一cookie是document.cookie,但是document.cookie指的是已发送到文档的cookie(运行脚本的页面),并且不会在任何时候修改。 MDN清楚地说明它是指当前文档:

Document.cookie

获取和设置与当前文档关联的cookie。有关通用库,请参见此简单的cookie框架。

来源: MDN

由Ajax响应设置的任何cookie均不属于当前文档。

那么如何实现我的csrf保护呢?

cookie to header token protection是正确的方法。请注意,您发送的令牌在整个会话期间都是相同的,并且不应根据发送cookie的任意Ajax请求而更改

使用JavaScript执行大部分操作的Web应用程序可能会使用依赖于同源策略的反CSRF技术:

  • On login, the web application sets a cookie containing a random token that remains the same for the whole user session

      Set-Cookie: Csrf-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
    
  • JavaScript operating on the client side reads its value and copies it into a custom HTTP header sent with each transactional request

      X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
    
  • The server validates presence and integrity of the token

这种技术的安全性基于一个假设,即只有在同一来源内运行的 JavaScript 才能读取 cookie 的值。来自恶意文件或电子邮件的 JavaScript 将无法读取它并复制到自定义标头中。即使 csrf-token cookie 将随着恶意请求自动发送,服务器仍将期望有效的 X-Csrf-Token 标头。

来源:Wikipedia : CSRF

使用 Angular 2+ 中的 CookieXSRFStrategy 类来实现此任务。

原始回答

如何在我的请求中访问 x-csrf-token,并将其添加到请求中?

使用 CookieXSRFStrategy 看起来是添加到您的请求中的方法。对于“如何”,不幸的是,答案可能是“您无法”(请参见下文)。

我很乐意帮助您翻译。这段内容是关于CookieXSRFStrategy的介绍,它与编程有关。它使用两个参数 'x-csrf-token' 和 'x-csrf-token',但作者不确定它具体的作用。作者不喜欢黑匣子式的感觉,也无法理解文档中的说明方式。
/**
 * `XSRFConfiguration` sets up Cross Site Request Forgery (XSRF) protection for the application
 * using a cookie. See https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)
 * for more information on XSRF.
 *
 * Applications can configure custom cookie and header names by binding an instance of this class
 * with different `cookieName` and `headerName` values. See the main HTTP documentation for more
 * details.
 *
 * @experimental
 */
export class CookieXSRFStrategy implements XSRFStrategy {
  constructor(
      private _cookieName: string = 'XSRF-TOKEN', private _headerName: string = 'X-XSRF-TOKEN') {}

  configureRequest(req: Request): void {
    const xsrfToken = getDOM().getCookie(this._cookieName);
    if (xsrfToken) {
      req.headers.set(this._headerName, xsrfToken);
    }
  }
}

来源

基本上,它从document.cookie读取cookie并相应地修改Request头。

现在我正在使用sessionid和csrf token将cookie发送到后端,但是谁在发送它?CookieXSRFStrategy还是'withCredentials'标志。

那就是withCredentials标志,该标志表示XHR应该发送已发送的所有cookie(即使是以前由Ajax响应设置的cookie,但是作为cookie而不是头部,没有办法更改此行为)。

在我的情况下它没有设置头部...但是为什么?

你所说的cookie并不是通过文档(index.html)发送的,而是通过另一个ajax请求发送的。事实上,你无法访问由ajax响应设置的cookie (见这个答案),因为这将是一个安全问题:从随机网页对 www.stackoverflow.com 进行简单的ajax获取将获得stack overflow cookie,攻击者可以轻松地窃取它(如果stackoverflow响应中存在 Access-Control-Allow-Origin: * 头)。
另一方面,document.cookie API只能访问在文档加载时设置的cookie,而不能访问其他cookie。
因此,你应该重新考虑客户端/服务器通信的流程,因为你能够复制到头文件中的唯一cookie是与脚本正在运行的文档(index.html)一起发送的那个cookie。

如果不是HttpOnly cookie,即使跨域也应该可以使用js访问。

httpOnly 使得cookie在 document.cookie API 中不可用,但是正如我告诉你的那样,document.cookie 指的是与文档一起发送的cookie,而不是通过Ajax响应发送的cookie。您可以使用响应中的 Set-Cookie 头进行成千上万次的ajax调用,document.cookie 仍将是相同的字符串,没有任何修改。

十亿美元的解决方案

服务器应该只发送一个包含令牌的 x-csrf-token cookie,并且您应该在每个请求中使用该令牌,该令牌将在整个会话中有效,使用 CookieXSRFStrategy。为什么?因为这就是它的工作方式


感谢您抽出时间。但是,我尝试使用的流程有什么问题吗?我有一个REST后端和Angular 2作为前端应用程序。 REST后端期望在每个带有令牌的POST请求的标头中使用csrf令牌。该令牌位于从同一后端获取的cookie中。它不是httpOnly cookie,因此即使它是X origin,也应该可以使用js访问。 - VikingCode
1
访问 set-Cookie 头在 XHR 规范 中是被禁止的。您可以通过使用 withCredentials 标志,在不修改 cookie 的情况下将其发送回服务器,但由于 JavaScript 无法读取该 cookie,因此您无法将其放在头中。httpOnly 让 cookie 不会出现在 document.cookie 中,但 document.cookie 只涉及那些已经随文档(index.html)一起发送的 cookie,而不是 Ajax 响应设置的 cookie。 - n00dl3
@n00dl3,所以你应该在文档(index.html)上设置cookie吗?每次加载index.html时,客户端会获得一个新的cookie吗? - Anthony
2
是的,现在是HttpClientXsrfModule.withOptions({ cookieName : "foo", headerName : "bar"})。我明天会编辑。 - n00dl3
1
我认为答案需要进行编辑,将“withConfig”更改为“withOptions”。 - Chris
显示剩余3条评论

1
Angular内置了XSRF支持,详情请参见:https://angular.io/guide/http#security-xsrf-protection。在执行HTTP请求时,拦截器会从cookie中读取一个令牌,默认为XSRF-TOKEN,并将其设置为HTTP头X-XSRF-TOKEN。因此,如果您的服务器设置了名为XSRF-TOKEN的Cookie,则它将自动工作!客户端无需进行任何操作。如果您想要将cookie/header命名为其他内容,则也可以这样做:
imports: [
  HttpClientModule,
  HttpClientXsrfModule.withConfig({
    cookieName: 'My-Xsrf-Cookie',
    headerName: 'My-Xsrf-Header',
  }),
]

如果您正在使用Spring Security,它支持Angular命名约定,因此您可以在服务器端进行配置:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
            .and()
            .authorizeRequests()
            ....    

我正在使用.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())。但问题是,对我来说,这只适用于GET请求... 当我尝试进行PUT、DELETE或POST时,我会收到403错误。 - Andre

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接