ng2 从 cookie post 获取 csrf 令牌作为 header

ng2 get csrf token from cookie post it as header

在花了整整 2 天的时间在网上搜索和阅读文档以及面临相同问题的人们提出的大量未解决问题之后,我仍然不明白 Angular 2 如何处理 (x-origin) cookie 以及如何访问它们。

问题: Back-end 发送 2 个包含 x-csrf-token 和 JSESSIONID 的 cookie。我的工作是将 csrf 令牌保存在内存 (ng2) 中,并将其作为 header(不是 cookie)(不是 cookie)与每个 post 发送回 back-end.

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

我的部分解决方案: 我创建了一个扩展 BaseRequestOptions 的自定义 RequesstOptions class。添加了一些额外的 headers,并将 'withCredentials' 设置为 true.

export class MyRequestOptions extends BaseRequestOptions {

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

  withCredentials = true;
}

在我的 HttpService 中,我执行 post 并像这样:

@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 发送带有 jsessionid 和 x-csrf-token 的每个请求(GET 和 POST 无差别)的 cookie,如下所示:

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

我的十亿美元问题:

Before sending an HTTP request, the CookieXSRFStrategy looks for a cookie called XSRF-TOKEN and sets a header named X-XSRF-TOKEN with the value of that cookie.

请不要像"document.cookie"那样一字一句地回答。没有元数据的数据是无用的

更新 angular 5.0+

Http 服务已弃用,取而代之的是 HttpClientCookieXSRFStrategy class 也已弃用,现在此任务委托给 HttpClientXsrfModule class。如果你想自定义 header 和 cookie 名称,你只需要像这样导入这个模块:

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

对于未来的读者:

Ajax 响应 cookie

您无法从任何 Ajax 响应访问任何 cookie,如果您检查 the XHR spec,您会注意到对 header 匹配“Set-Cookie”的任何访问都是禁止:

A forbidden response-header name is a header name that is a byte-case-insensitive match for one of:

  • Set-Cookie
  • Set-Cookie2

但我的 cookie 不是 httpOnly

对你来说很好,但是 httpOnly 只说明你的 cookie 不能通过 document.cookie 访问(见下文)。

document.cookieAPI

您可以使用 javascript 访问的唯一 cookie 是 document.cookiedocument.cookie 指的是 随文档一起发送的 cookie(您的脚本所在的页面 运行)并且 不会在任何时候被修改 。 MDN明确指出它指的是当前文档:

Document.cookie

Get and set the cookies associated with the current document. For a general library see this simple cookie framework.

来源:MDN

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

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

cookie to header token protection 是必经之路。请注意,您将发送的令牌在整个 session 期间是相同的,并且不应根据发送 cookie.[=] 的疯狂 Ajax 请求而改变。 62=]

Web applications that use JavaScript for the majority of their operations may use an anti-CSRF technique that relies on same-origin policy:

  • 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

Security of this technique is based on the assumption that only JavaScript running within the same origin will be able to read the cookie's value. JavaScript running from a rogue file or email will not be able to read it and copy into the custom header. Even though the csrf-token cookie will be automatically sent with the rogue request, the server will be still expecting a valid X-Csrf-Token header.

来源:Wikipedia : CSRF

有了 Angular 2+ 这个任务由 CookieXSRFStrategy class 完成。

原回答

How and where do i access the x-csrf-token, and how do i add it to my requests?

使用 CookieXSRFStrategy 似乎是将其添加到您的请求中的方法。对于“如何”,不幸的是,答案可能是“你不能”(进一步查看)。

What does CookieXSRFStrategy('x-csrf-token', 'x-csrf-token'); exactly do. I don't like the blackbox feeling / understand the way the docs explained it.

CookieXSRFStrategy

/**
 * `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);
    }
  }
}

Source

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

Right now i'm sending the cookie to the backend with the sessionid and the csrf token but what is sending it? The CookieXSRFStrategy or 'withCredentials' flag.

那是 withCredentials 标志,这个标志表示 XHR 应该发送所有已经发送的 cookie(即使是那些先前由 Ajax 响应设置的,但是 作为 cookie,不是 headers,并且没有办法改变这种行为)

It doesn't set the header in my case ... but why ?

您所说的 cookie 不是随文档 (index.html) 一起发送的,而是来自另一个 ajax 请求。事实上,您无法访问由 ajax 响应 (see this answer) 设置的 cookie,因为那将是一个安全问题:一个简单的 ajax 从随机网页访问 www.whosebug.com将获得堆栈溢出 cookie,攻击者可以轻松窃取它(如果 Access-Control-Allow-Origin: * header 出现在 Whosebug 响应中)。

另一方面,document.cookie API 只能访问加载文档时设置的 cookie,不能访问任何其他 cookie。

因此您应该重新考虑 client/server 在 server-side 上的通信流程,因为您能够复制到 header 的唯一 cookie 是已随文档一起发送,您的脚本 运行 on (index.html).

it isn't a httpOnly cookie so it should be accessible with js even if it is X origin

httpOnly 使 cookie 对 document.cookie API、 但是 正如我告诉你的那样,document.cookie 指的是与文档一起发送的 cookie,而不是通过 Ajax 响应 发送的 cookie。您可以在响应中使用 Set-Cookie Header 进行数千次 ajax 调用,document.cookie 仍然是相同的字符串,无需任何修改。

十亿美元的解决方案

服务器应该只发送一个 x-csrf-token 包含文档令牌的 cookie,并且对于使用 [=16= 的每个请求,您应该使用对整个 session 有效的令牌].为什么 ?因为 that is how it works.

Angular 已内置对 XSRF 的支持,请参见此处: https://angular.io/guide/http#security-xsrf-protection

"When performing HTTP requests, an interceptor reads a token from a cookie, by default XSRF-TOKEN, and sets it as an HTTP header, X-XSRF-TOKEN"

因此,如果您的服务器设置了一个名为 XSRF-TOKEN 的 cookie,那么 它将自动工作!客户端无需执行任何操作。 如果你想给你的 cookie/header 命名,你也可以这样做:

imports: [
  HttpClientModule,
  HttpClientXsrfModule.withConfig({
    cookieName: 'My-Xsrf-Cookie',
    headerName: 'My-Xsrf-Header',
  }),
]

并且如果您正在使用 spring 安全性,它支持 angular 命名约定,因此您可以配置此服务器端:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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