Я реализую веб-сайт в Angular.js, который попадает в бэкэнд ASP.NET WebAPI.
Angular.js имеет встроенные функции для защиты от анти-csrf. В каждом HTTP-запросе он будет искать cookie под названием "XSRF-TOKEN" и отправит его в виде заголовка "X-XSRF-TOKEN".
Это зависит от того, как веб-сервер может установить cookie XSRF-TOKEN после аутентификации пользователя, а затем проверить заголовок X-XSRF-TOKEN для входящих запросов.
В документации Angular указано:
Чтобы воспользоваться этим, вашему серверу необходимо установить токен в JavaScript-читаемом сеансовом cookie под названием XSRF-TOKEN при первом запросе HTTP GET. При последующих не-GET-запросах сервер может проверить, соответствует ли файл cookie HTTP-заголовку X-XSRF-TOKEN, и поэтому убедитесь, что только JavaScript, работающий в вашем домене, мог прочитать токен. Маркер должен быть уникальным для каждого пользователя и должен быть проверен сервером (чтобы JavaScript не создавал свои собственные токены). Мы рекомендуем, чтобы токен являлся дайджестом cookie проверки подлинности вашего сайта с солью для дополнительной безопасности.
Я не мог найти хороших примеров этого для ASP.NET WebAPI, поэтому я применил свой собственный с помощью различных источников. Мой вопрос: может ли кто-нибудь увидеть что-то не так с кодом?
Сначала я определил простой вспомогательный класс:
public class CsrfTokenHelper
{
const string ConstantSalt = "<ARandomString>";
public string GenerateCsrfTokenFromAuthToken(string authToken)
{
return GenerateCookieFriendlyHash(authToken);
}
public bool DoesCsrfTokenMatchAuthToken(string csrfToken, string authToken)
{
return csrfToken == GenerateCookieFriendlyHash(authToken);
}
private static string GenerateCookieFriendlyHash(string authToken)
{
using (var sha = SHA256.Create())
{
var computedHash = sha.ComputeHash(Encoding.Unicode.GetBytes(authToken + ConstantSalt));
var cookieFriendlyHash = HttpServerUtility.UrlTokenEncode(computedHash);
return cookieFriendlyHash;
}
}
}
Тогда у меня есть следующий метод в моем контроллере авторизации, и я вызываю его после вызова FormsAuthentication.SetAuthCookie():
// http://www.asp.net/web-api/overview/security/preventing-cross-site-request-forgery-(csrf)-attacks
// http://docs.angularjs.org/api/ng.$http
private void SetCsrfCookie()
{
var authCookie = HttpContext.Current.Response.Cookies.Get(".ASPXAUTH");
Debug.Assert(authCookie != null, "authCookie != null");
var csrfToken = new CsrfTokenHelper().GenerateCsrfTokenFromAuthToken(authCookie.Value);
var csrfCookie = new HttpCookie("XSRF-TOKEN", csrfToken) {HttpOnly = false};
HttpContext.Current.Response.Cookies.Add(csrfCookie);
}
Затем у меня есть пользовательский атрибут, который я могу добавить в контроллеры, чтобы заставить их проверить заголовок csrf:
public class CheckCsrfHeaderAttribute : AuthorizeAttribute
{
// http://stackoverflow.com/info/11725988/problems-implementing-validatingantiforgerytoken-attribute-for-web-api-with-mvc
protected override bool IsAuthorized(HttpActionContext context)
{
// get auth token from cookie
var authCookie = HttpContext.Current.Request.Cookies[".ASPXAUTH"];
if (authCookie == null) return false;
var authToken = authCookie.Value;
// get csrf token from header
var csrfToken = context.Request.Headers.GetValues("X-XSRF-TOKEN").FirstOrDefault();
if (String.IsNullOrEmpty(csrfToken)) return false;
// Verify that csrf token was generated from auth token
// Since the csrf token should have gone out as a cookie, only our site should have been able to get it (via javascript) and return it in a header.
// This proves that our site made the request.
return new CsrfTokenHelper().DoesCsrfTokenMatchAuthToken(csrfToken, authToken);
}
}
Наконец, я очищаю токен Csrf, когда пользователь выходит из системы:
HttpContext.Current.Response.Cookies.Remove("XSRF-TOKEN");
Можно ли выявить какие-либо очевидные (или не столь очевидные) проблемы с этим подходом?