ng2 从 cookie 获取 csrf 令牌,将其作为标头发布



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

问题:后端发送2个cookie,里面有x-csrf-token和JSESSIONID。我的工作是将 csrf 令牌保存在内存 (ng2) 中,并将其(仅)作为标头(而不是 cookie)发送回后端。

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

我的部分解决方案:我创建了一个自定义的RequesstOptions类来扩展BaseRequestOptions。添加了一些额外的标头,并将"withCredentials"设置为 true。

export class MyRequestOptions extends BaseRequestOptions {
headers: Headers = new Headers({
'Accept': 'application/json',
'Content-Type': 'application/json',
});
withCredentials = true;
}

在我的 HttpService 中,我做了这篇文章,结果是这样的:

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

在我的应用程序模块中,我像这样施展魔法:

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

我的xsrf工厂

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

我的十亿美元问题:

  • 我如何以及在何处访问 x-csrf 令牌,以及如何将其添加到我的请求中?
  • CookieXSRFStrategy('x-csrf-token', 'x-csrf-token');到底是做什么的。我不喜欢黑匣子的感觉/理解文档解释它的方式。我可以访问它以获取数据吗?

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

  • 在我的情况下,它没有设置标题...但是为什么?

  • 现在我正在使用会话 ID 和 csrf 令牌将 cookie 发送到后端,但发送它是什么?CookieXSRFStrategy 或 'withCredentials' 标志。

请不要用像"document.cookie"这样的一行话来回答。没有元数据,数据就毫无用处

角度 5.0+ 的更新

Http服务被弃用以支持HttpClientCookieXSRFStrategy类也被弃用,现在此任务委托给HttpClientXsrfModule类。如果要自定义标头和cookie名称,则只需像这样导入此模块:

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

对于未来的读者:

Ajax 响应饼干

您不能从任何 Ajax 响应访问任何 cookie,如果您检查 XHR 规范,您会注意到禁止访问与"Set-Cookie"匹配的标头:

禁止的响应标头名称是以下之一的不区分字节大小写的标头名称:

  • Set-Cookie
  • Set-Cookie2

但我的饼干不是httpOnly

对您有好处,但httpOnly仅声明无法通过document.cookie访问您的cookie(另请参阅)。

document.cookieAPI

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

Document.cookie

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

来源 : MDN

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

那么,我该如何实施我的 csrf 保护?

cookie 到标头令牌保护是要走的路。请注意,您将发送的令牌在整个会话期间是相同的,并且不应根据发送 cookie 的野生 Ajax 请求而更改

在大多数操作中使用 JavaScript 的 Web 应用程序可能会使用依赖于同源策略的反 CSRF 技术:

  • 登录时,Web 应用程序会设置一个包含随机令牌的 cookie,该令牌在整个用户会话中保持不变

    Set-Cookie: Csrf-token=i8XNjC4b8KVok4uw5RftR38Wgp2BFwql; expires=Thu, 23-Jul-2015 10:25:33 GMT; Max-Age=31449600; Path=/
    
  • 在客户端运行的 JavaScript 读取其值并将其复制到随每个事务请求一起发送的自定义 HTTP 标头中

    X-Csrf-Token: i8XNjC4b8KVok4uw5RftR38Wgp2BFwql
    
  • 服务器验证令牌的存在和完整性

此技术的安全性基于以下假设:只有在同一源中运行的 JavaScript 才能读取 cookie 的值。从流氓文件或电子邮件运行的 JavaScript 将无法读取它并复制到自定义标头中。即使csrf令牌cookie将自动与流氓请求一起发送,服务器仍将期望有效的X-Csrf-Token标头。

来源:维基百科:CSRF

使用Angular 2+,该任务由CookieXSRFStrategy级完成。

原答案

我如何以及在何处访问 x-csrf 令牌,以及如何将其添加到我的请求中?

使用CookieXSRFStrategy似乎是将其添加到您的请求中的方法。不幸的是,对于"如何",答案可能是"你不能"(另见)。

CookieXSRFStrategy('x-csrf-token', 'x-csrf-token')到底做了什么。我不喜欢黑匣子的感觉/理解文档解释它的方式。

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

来源

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

现在我正在使用会话 ID 和 csrf 令牌将 cookie 发送到后端,但发送它是什么?CookieXSRFStrategy 或 'withCredentials' 标志。

这是withCredentials标志,此标志表示 XHR 应该发送所有已发送的 cookie(即使是之前由 Ajax 响应设置的 cookie,但作为 cookie,而不是标头,并且无法更改此行为)

在我的情况下,它没有设置标题...但是为什么?

您正在谈论的cookie不是与文档(index.html)一起发送的,而是来自另一个ajax请求。事实是,您无法访问由 ajax 响应设置的 cookie(请参阅此答案),因为这将是一个安全问题:来自随机网页的简单 ajax get onwww.stackoverflow.com将获得堆栈溢出 cookie,攻击者可以轻松窃取它(如果堆栈溢出响应中存在Access-Control-Allow-Origin: *标头)。

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

因此,您应该重新考虑服务器端的客户端/服务器通信流程,因为您唯一能够复制到标头的cookie是与运行脚本的文档一起发送的cookie(index.html)。

它不是一个httpOnly cookie,所以它应该可以用js访问,即使它是X来源

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

十亿美元的解决方案

服务器应仅发送一个包含文档令牌的x-csrf-tokencookie,并且您应该使用该令牌,该令牌对使用CookieXSRFStrategy的每个请求的整个会话都有效。为什么?因为这就是它的工作原理。

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 安全性,它支持角度命名约定,因此您可以配置此服务器端:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.authorizeRequests()
....    

最新更新