我最终没有发送客户端在每个请求中创建的随机令牌。我无法让它发挥作用。
这就是我解决问题的方法(某种程度上):
(1) 对于每个请求(包括第一个请求),我会从 API 的响应标头中发回一个名为“XSRF-TOKEN”的 cookie 以及与其关联的随机值。这是 AngularJS 在使用 CSRF 保护时默认寻找的名称。
(2) 收到该令牌后,请求中会发生什么情况,AngularJS 使用该令牌的值在名为“XSRF-TOKEN”的请求标头中发送 cookie,并在名为“X-XSRF-TOKEN”的标头中发送该令牌的值以及。
因此,我的 API 正在处理 XSRF 令牌的随机化,并且我的应用程序仍然是无状态的。我正在使用 Web API,并使用全局过滤器来处理此 XSRF 令牌创建。下面是我用于执行此操作的代码(C# 语言)。我不再有任何代码在用户界面中处理这个问题(因为它似乎不需要):
public class ValidateAntiForgeryToken : ActionFilterAttribute
{
private const string XsrfCookieName = "XSRF-TOKEN";
private const string XsrfHeaderName = "X-XSRF-TOKEN";
private const string CsrfTokenSalt = "RANDOM SALT";
public override void OnActionExecuting(HttpActionContext filterContext)
{
string requestMethod = filterContext.Request.Method.Method;
Boolean isValid = true;
if (requestMethod != "GET")
{
var headerToken = filterContext.Request.Headers.Where(x => x.Key.Equals(XsrfHeaderName, StringComparison.OrdinalIgnoreCase))
.Select(x => x.Value).SelectMany(x => x).FirstOrDefault();
var cookieToken = filterContext.Request.Headers.GetCookies().Select(x => x[XsrfCookieName]).FirstOrDefault();
// check for missing cookie or header
if (cookieToken == null || headerToken == null)
{
isValid = false;
}
// ensure that the cookie matches the header
if (isValid && !String.Equals(headerToken, cookieToken.Value, StringComparison.OrdinalIgnoreCase))
{
isValid = false;
}
if (!isValid)
{
filterContext.Response = filterContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
filterContext.Response.ReasonPhrase = "Unauthorized to make that request.";
return;
}
}
base.OnActionExecuting(filterContext);
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
string textToHash = RandomStringGeneration();
string cookieText = HashService.HashText(textToHash, CsrfTokenSalt);
var cookie = new CookieHeaderValue(XsrfCookieName, HttpUtility.UrlEncode(cookieText));
/* don't use this flag if you're not using HTTPS */
cookie.Secure = true;
cookie.HttpOnly = false; // javascript needs to be able to get this in order to pass it back in the headers in the next request
/* if you have different environments on the same domain (which I did in one application using this code) make sure you set the path to be ApplicationPath of the request. Case sensitivity does matter in Chrome and IE, so be wary of that. */
cookie.Path = "/";
actionExecutedContext.Response.Headers.AddCookies(new[] { cookie });
base.OnActionExecuted(actionExecutedContext);
}
}
这是我的 HashService.HashText() 代码:
public class HashService
{
public static string HashText(string text, string salt)
{
SHA512Managed hashString = new SHA512Managed();
byte[] textWithSaltBytes = Encoding.UTF8.GetBytes(string.Concat(text, salt));
byte[] hashedBytes = hashString.ComputeHash(textWithSaltBytes);
hashString.Clear();
return Convert.ToBase64String(hashedBytes);
}
}
希望这对将来使用无状态应用程序的人有所帮助。不幸的是,发回的令牌仅在 cookie 值和标头值中与其自身进行比较。这是我现在能够验证它的唯一方法(我觉得这是相当安全的)。我可能会创建一个全新的 XSRF 保护表,并使用它来验证令牌确实是用户应该使用的令牌。这是我能够保持 API 无状态的唯一方法。
在阅读 AngularJS 的 $http 文档后,我偶然发现了这个解决方案,其中规定:
跨站请求伪造 (XSRF) 保护:XSRF 是一种技术
未经授权的网站可以获取您用户的私人数据。角
提供了一种对抗 XSRF 的机制。当执行 XHR 请求时,
$http 服务从 cookie 读取令牌(默认情况下为 XSRF-TOKEN)
并将其设置为 HTTP 标头 (X-XSRF-TOKEN)。由于只有 JavaScript
在您的域上运行的程序可以读取 cookie,您的服务器可以是
确保 XHR 来自在您的域上运行的 JavaScript。这
跨域请求不会设置 header。
为了利用这一点,您的服务器需要在
第一个 HTTP 上名为 XSRF-TOKEN 的 JavaScript 可读会话 cookie
获取请求。在后续的 XHR 请求中,服务器可以验证
cookie 与 X-XSRF-TOKEN HTTP 标头匹配,因此请确保
只有在您的域上运行的 JavaScript 才能发送请求。
每个用户的令牌必须是唯一的,并且必须可由
服务器(以防止 JavaScript 组成自己的令牌)。我们
建议该令牌是您网站身份验证的摘要
饼干加盐以增加安全性。
标头的名称可以使用 xsrfHeaderName 和
$httpProvider.defaults 处的 xsrfCookieName 属性
配置时、运行时的 $http.defaults 或每个请求的配置
目的。