问题javascript
经过 CSRF(Cross-Site Request Forgery)防御,保护从 MVC 页面提交到ASP.NET Web API 的数据。html
解决方案java
ASP.NET 已经加入了 CSRF 防御功能,只要经过 System.web.Helpers.AntiForgery 类(System.Web.WebPages 的一部分)就能够。web
他会生成两个 Token:ajax
Cookie Tokenjson
基于字符串的 Tokenc#
基于字符串的 Token 是能够嵌入到表单或者请求头(使用 Ajax 的状况下)。为了防止 CSRF 攻击,表单提交和Ajax 请求到 API 的数据必须包含这些Token,服务器将会验证这两个 Token。api
在 ASP.NET Web API,anti-CSRF Token 验证是一个典型的实现了横切关系的 MessageHandler。服务器
工做原理cookie
为了能在 MVC 应用程序的上下文中生成 Token,咱们必须在表单中调用一个叫作 AntiForgeryToken 的HtmlHelper 的扩展方法。
1
2
3
4
|
<
form
id
=
"myForm"
>
@Html.AntiForgeryToken()
@* 其余标签 *@
</
form
>
|
这个帮助方法在AntiForgery 类中。他会写一个 Token 到响应的 Cookie 中,同时生成一个名字叫作_RequestVerificationToken 的字段,也会随着表单数据同时被提交。
为能在服务器端验证 Token,咱们能够经过调用AntiForgery 类的静态方法 Validate 来验证。若是调用的时候没有传递参数的话,就会从 HttpContext.Current 中试着获取相关的 Cookie 和请求体中提取 Token,在这里,咱们假设确实有一个 Body 而且 Body 中也有一个 _RequestVerificationToken。
因为这个方法是 void (无返回值)的,因此,请求验证成功后,方法什么反馈也没有,若是失败,就会抛HttpAntiForgeryException 的异常。咱们能够捕获这个异常,而后返回给客户端相应的响应(例如,一个 HTTP 403 的状态码)。
有一个可替代的方式就是调用 Validate 方法,咱们本身来传这两个 Token。这时候,就要从 Request 中获取这两个值。例如,多是在 Header 中。这种方式也能够摆脱对 HttpContext 的依赖。
对于 Web API,咱们能够自定义消息处理器,在每一个请求进入 Web API 的时候来负责 CSRF Token 的验证,执行必要的验证,而后继续管道执行,或者,在请求无效的状况下,直接短路错误响应(也就是说,当即返回错误码)。
代码演示
咱们来演示 MessageHandler 执行 CSRF 验证的例子如清单 1-23 所示。
两种方式:
用 Ajax 请求。
用其余的请求。
咱们都简单假设他们都是表单提交的。若是是一个 Ajax 请求,咱们能够尝试着从请求 Header 中获取Token,同时,能够从与 Request 一同提交的 Cookie 集合中获取Cookie Token,而后,使用无参的 Validate方法验证,这样,就须要咱们本身来提取 Token。
若是验证失败,客户端会获得一个 403 的错误响应。
清单 1-23. Anti_CSRF 消息处理器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
|
public
class
AntiForgeryHandler : DelegatingHandler
{
protected
override
async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
string
cookieToken =
null
;
string
formToken =
null
;
if
(request.IsAjaxRequest())
{
IEnumerable<
string
> tokenHeaders;
if
(request.Headers.TryGetValues(
"__RequestVerificationToken"
,
out
tokenHeaders))
{
var
cookie = request.Headers.GetCookies(AntiForgeryConfig.CookieName).
FirstOrDefault();
if
(cookie !=
null
)
{
cookieToken = cookie[AntiForgeryConfig.CookieName].Value;
}
formToken = tokenHeaders.FirstOrDefault();
}
}
try
{
if
(cookieToken !=
null
&& formToken !=
null
)
{
AntiForgery.Validate(cookieToken, formToken);
}
else
{
AntiForgery.Validate();
}
}
catch
(HttpAntiForgeryException)
{
return
request.CreateResponse(HttpStatusCode.Forbidden);
}
return
await
base
.SendAsync(request, cancellationToken);
}
}
|
咱们还须要在 API 的HttpConfiguration 中注册,这样才会在全局起做用。
1
|
config.MessageHandlers.Add(
new
AntiForgeryHandler());
|
构筑一个 anti-CSRF 护盾做为消息处理器并非惟一方式。咱们也能够在过滤器内部使用一样的代码,而后将过滤器应用到相应的 Action 上(相似的,怎么用过滤器验证,咱们将在 5-4 详细讨论)。若是消息处理器不是全局使用,也能够附加到指定路由上。咱们将在 3-9 详细讨论这一起。
HttpRequestMessage有一个内建的方式来检查是否为 Ajax 请求,就是用一个简单的扩展方法来实现,他依赖于 Header 的 X-Requested-With,大多数的 JavaScript 框架都会自动发送这个在 Header 中。这个方法如清单1-24 所示。
清单 1-24. 检查 HttpRequestMessage 是否为一个 Ajax 请求的扩展方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public
static
class
HttpRequestMessageExtensions
{
public
static
bool
IsAjaxRequest(
this
HttpRequestMessage request)
{
IEnumerable<
string
> headers;
if
(request.Headers.TryGetValues(
"X-Requested-With"
,
out
headers))
{
var
header = headers.FirstOrDefault();
if
(!
string
.IsNullOrEmpty(header))
{
return
header.ToLowerInvariant() ==
"xmlhttprequest"
;
}
}
return
false
;
}
}
|
清单 1-25 展现了,传统表单提交和 Ajax 请求都利用 anti-CSRF Token 的例子。在传统表单提交的状况下,HTML helper 会生成一个隐藏域,同时,anti-forgery token 会随着表单一起被自动提交。在 Ajax 请求的状况下,咱们显示的从隐藏域中读取 Token,而后,将其附加到请求头中。
清单 1-25. 传统表单和 Ajax 请求方式下,提交数据到 ASP.NET WEB API 使用 Anti-CSRF 防御
//HTML表单
1
2
3
4
5
6
7
8
9
10
11
12
|
<form id=
"form1"
method=
"post"
action=
"/api/form"
enctype=
"application/x-www-form-urlencoded"
>
@Html.AntiForgeryToken()
<div>
<label
for
=
"name"
>Name</label>
</div>
<div>
<input type=
"text"
name=
"name"
value=
"Some Name"
/>
</div>
<div>
<button id=
"postData"
name=
"postData"
>Post form</button>
</div>
</form>
|
// Ajax 表单
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
@Html.AntiForgeryToken()
<
input
id
=
"itemJS"
type
=
"text"
disabled
=
"disabled"
name
=
"text"
value
=
"some text"
/>
<
div
>
<
button
id
=
"postJS"
name
=
"postJS"
>Post JS</
button
>
</
div
>
<
script
type
=
"text/javascript"
>
$(function () {
$("#postJS").on("click", function () {
$.ajax({
dataType: "json",
data: JSON.stringify({ name: $("#itemJS").val() }),
type: "POST",
headers: {
"__RequestVerificationToken": $("#jsData input[name='__
RequestVerificationToken']").val()
},
contentType: "application/json; charset=utf-8",
url: "/api/items"
}).done(function (res) {
alert(res.Name);
});
});
});
</
script
>
|