Web API 和 ValidateAntiForgeryToken

我们有一些现有的 MVC Web 服务,称为 AJAX 风格的网页。这些服务使用 ValidateAntiForgeryToken 属性来帮助防止请求伪造。

我们正在寻求将这些服务迁移到 Web API,但似乎没有相应的防伪功能。

我是否遗漏了什么? 是否存在使用 Web API 处理请求伪造的不同方法?

68041 次浏览

您可以实现这样的授权属性:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
try
{
AntiForgery.Validate();
}
catch
{
actionContext.Response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
};
return FromResult(actionContext.Response);
}
return continuation();
}


private Task<HttpResponseMessage> FromResult(HttpResponseMessage result)
{
var source = new TaskCompletionSource<HttpResponseMessage>();
source.SetResult(result);
return source.Task;
}
}

然后用它来装饰你的 API 操作:

[ValidateAntiForgeryToken]
public HttpResponseMessage Post()
{
// some work
return Request.CreateResponse(HttpStatusCode.Accepted);
}

这个 链接帮助,你可以从剃须刀视图中检索反伪造令牌,并将令牌作为标题传递:

var csrfToken = $("input[name='__RequestVerificationToken']").val();
$.ajax({
headers: { __RequestVerificationToken: csrfToken },
type: "POST",
dataType: "json",
contentType: 'application/json; charset=utf-8',
url: "/api/products",
data: JSON.stringify({ name: "Milk", price: 2.33 }),
statusCode: {
200: function () {
alert("Success!");
}
}
});

补充以上代码 FilterAttribute 过滤器属性

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
public sealed class ValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
{
try
{
string cookieToken = "";
string formToken = "";


IEnumerable<string> tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
{
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
{
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
}
}
AntiForgery.Validate(cookieToken, formToken);
}
catch (System.Web.Mvc.HttpAntiForgeryException e)
{
actionContext.Response = new HttpResponseMessage
{
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
};
return FromResult(actionContext.Response);
}
return continuation();
}


private Task<HttpResponseMessage> FromResult(HttpResponseMessage result)
{
var source = new TaskCompletionSource<HttpResponseMessage>();
source.SetResult(result);
return source.Task;
}

使用 Razor 的 Html 函数

@functions{
public string TokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}

使用角度

return $http({
method: 'POST',
url: '@Url.Content("~/api/invite/")',
data: {},
headers: {
'RequestVerificationToken': '@TokenHeaderValue()'
}
});

Oswaldo 的答案,但实现为 AuthorizeAttribute

  [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ApiValidateAntiForgeryToken : AuthorizeAttribute
{
public static string GenerateAntiForgeryTokenForHeader() {
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}




protected override bool IsAuthorized(HttpActionContext actionContext) {
var headers = actionContext.Request.Headers;


// we pass both the cookie and the form token into a single header field
string headerToken = headers.Contains("__RequestVerificationToken") ? headers.GetValues("__RequestVerificationToken").FirstOrDefault() : null;


if (headerToken == null) {
return false;
}


string[] tokens = headerToken.Split(':');
if (tokens.Length != 2) {
return false;
}


string cookieToken = tokens[0].Trim();
string formToken = tokens[1].Trim();


try {
AntiForgery.Validate(cookieToken, formToken);
}
catch {
return false;
}


return base.IsAuthorized(actionContext);
}
}

您可以使用[ ApiValidateAntiForgeryToken ]装饰您的控制器或方法,然后传递 RequestVerificationToken: “@ApiValidateAntiForgeryToken。GenerateAntiForgeryTokenForHeader ()”作为剃须刀 javascript 代码中方法的标题。

在进一步考虑之后,将 cookie 和表单令牌混合使用是一个糟糕的主意,因为它破坏了防伪令牌的整个目的。最好将 cookie 部分保留为 cookie,同时将表单部分移动到 auth Header,因此这个新的答案(同样作为 AuthorizeAttribute)。

using System;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Helpers;
using System.Web.Http;
using System.Web.Http.Controllers;


[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ApiValidateAntiForgeryToken : AuthorizeAttribute {
public const string HeaderName = "X-RequestVerificationToken";


private static string CookieName => AntiForgeryConfig.CookieName;


public static string GenerateAntiForgeryTokenForHeader(HttpContext httpContext) {
if (httpContext == null) {
throw new ArgumentNullException(nameof(httpContext));
}


// check that if the cookie is set to require ssl then we must be using it
if (AntiForgeryConfig.RequireSsl && !httpContext.Request.IsSecureConnection) {
throw new InvalidOperationException("Cannot generate an Anti Forgery Token for a non secure context");
}


// try to find the old cookie token
string oldCookieToken = null;
try {
var token = httpContext.Request.Cookies[CookieName];
if (!string.IsNullOrEmpty(token?.Value)) {
oldCookieToken = token.Value;
}
}
catch {
// do nothing
}


string cookieToken, formToken;
AntiForgery.GetTokens(oldCookieToken, out cookieToken, out formToken);


// set the cookie on the response if we got a new one
if (cookieToken != null) {
var cookie = new HttpCookie(CookieName, cookieToken) {
HttpOnly = true,
};
// note: don't set it directly since the default value is automatically populated from the <httpCookies> config element
if (AntiForgeryConfig.RequireSsl) {
cookie.Secure = AntiForgeryConfig.RequireSsl;
}
httpContext.Response.Cookies.Set(cookie);
}


return formToken;
}




protected override bool IsAuthorized(HttpActionContext actionContext) {
if (HttpContext.Current == null) {
// we need a context to be able to use AntiForgery
return false;
}


var headers = actionContext.Request.Headers;
var cookies = headers.GetCookies();


// check that if the cookie is set to require ssl then we must honor it
if (AntiForgeryConfig.RequireSsl && !HttpContext.Current.Request.IsSecureConnection) {
return false;
}


try {
string cookieToken = cookies.Select(c => c[CookieName]).FirstOrDefault()?.Value?.Trim(); // this throws if the cookie does not exist
string formToken = headers.GetValues(HeaderName).FirstOrDefault()?.Trim();


if (string.IsNullOrEmpty(cookieToken) || string.IsNullOrEmpty(formToken)) {
return false;
}


AntiForgery.Validate(cookieToken, formToken);
return base.IsAuthorized(actionContext);
}
catch {
return false;
}
}
}

然后用[ ApiValidateAntiForgeryToken ]装饰您的控制器或方法

并添加到剃须刀文件,以生成您的 javascript 令牌:

<script>
var antiForgeryToken = '@ApiValidateAntiForgeryToken.GenerateAntiForgeryTokenForHeader(HttpContext.Current)';
// your code here that uses such token, basically setting it as a 'X-RequestVerificationToken' header for any AJAX calls
</script>

您如何处理非 UI 客户端的防伪令牌实现? (bot UI 和非 UI 客户端调用?) 为什么非 UI 客户端需要通过防伪令牌? (对于非 UI 客户端没有防伪问题... ...)