我基本上需要在我的 Web API Controller 中防止跨站点请求伪造,它是 MVC 应用程序的一部分。我愿意接受任何想法。此时,我有一个使用 ArcGIS for JavaScript API 显示 Esri map 的 MVC View 。用户在 map 上创建一条路线,路线和它所经过的各种特征可以通过 AJAX POST 保存。 View 没有表单。这是因为我发布到服务器的所有数据都在内存中,并且在屏幕上(或隐藏字段中)不可见。
所以,我的 MVC View 中有以下内容来获取防伪 token :
@functions{
public string GetTokenHeaderValue()
{
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
}
}
我在 View 中有这个隐藏的输入,但意识到这很糟糕,因为它同时具有与 AntiForgery.Validation 一起使用的“表单 token ”和 cookie token :
<input type="hidden" id="antiforgeryToken" value="@GetTokenHeaderValue()" />
然后,在一个单独的 JavaScript 文件中(不在 View 中的脚本标记中),我正在对我的 Web API Controller 执行一个 Http POST。这是我将 token 添加到请求 header 的地方:
var headers = {};
headers['RequestVerificationToken'] = $("#antiforgeryToken").val();
// Ajax POST to Web API Controller
$.ajax({
async: true,
url: '/api/RouteData',
type: 'POST',
headers: headers,
data: requestData,
dataType: 'json',
error: function (xhr, statusText, errorThrown) {
console.log('Error saving route data! ' + errorThrown);
},
success: function (result) {
}
});
注意:正文中发布的数据都在自定义 Dojo 小部件的内存中(因为该页面使用 ArcGIS for JavaScript 显示 Esri map )。页面上没有表单,因为用户没有输入数据。)
在 Web API Controller 的服务器端将它们捆绑在一起:
[System.Web.Http.HttpPost]
[ResponseType(typeof(RouteData))]
public async Task<IHttpActionResult> PostRouteData(RouteDataViewModel routeDataVM)
{
try
{
HttpRequestMessage httpRequestMessage = HttpContext.Current.Items["MS_HttpRequestMessage"] as HttpRequestMessage;
ValidateRequestHeader(httpRequestMessage);
}
catch (Exception ex)
{
_logger.Log(LogLevel.Error, ex, ex.Message);
throw;
}
// Now that we know user is who they say they are, perform update
}
void ValidateRequestHeader(HttpRequestMessage request)
{
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (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);
}
AntiForgery.Validate 用于验证 token 。
我看过这个SO post ,这给了我一些想法,但并没有完全解决我的问题。很多功劳归功于此 post on ASP.net
这对我来说(我认为)的不同之处在于我的 JavaScript 位于一个单独的文件中,无法调用服务器端 Razor 函数来获取防伪 token ,对吧?关于如何在没有表单的情况下防止 CSRF 的任何想法?
最佳答案
CSRF 保护正是您应该制定的,以防止跨站点请求伪造。
Cross-Site Request Forgery (CSRF) is an attack that forces an end user to execute unwanted actions on a web application in which they're currently authenticated. CSRF attacks specifically target state-changing requests, not theft of data, since the attacker has no way to see the response to the forged request. With a little help of social engineering (such as sending a link via email or chat), an attacker may trick the users of a web application into executing actions of the attacker's choosing.
如果您的 Web API 不 protected ,以下是针对您的 Web API 的 CSRF 攻击示例:
<form action="http://yourapi.com/api/DeleteAccount">
<input type="text" name="id" />
<input type="submit" />
</form>
通过从受感染的网站发布此表单,如果您使用 cookie 身份验证(例如)作为管理帐户登录到您的 Web API,它很可能能够从您的系统中删除帐户。
发生这种情况是因为当您被重定向到您的 API 时,您的浏览器正在传递针对 yourapi.com
存储的 cookie。域,从而验证用户并执行所需的操作。这种攻击向量不同于使用不记名 token 等方式对您的 API 进行身份验证,因为第三方不知道此 token 。
正如您正确陈述和实现的那样,使用防伪 token 可以防止这种情况发生,因为生成的 token 会在网络响应中向下发送到客户端,然后由客户端发送回来并进行验证以确保这是我们期望的某个地方允许的请求。与不记名 token 一样,第三方可能不知道您在服务器请求中发送的 token 。在这方面,您所实现的是绝对正确的,将 token 传递给客户端正是我们所期望的工作方式。
在 ASP.NET MVC(没有 API)中,这是按如下方式实现的:
<form .. />
@Html.AntiForgeryToken()
</form>
.. 并验证服务器端:
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Action(MyModel model)
{
// ..
}
当您调用 @Html.AntiForgeryToken()
时,该方法既设置了一个 cookie(包含 cookie token )并将其发送到客户端,还生成了一个 <input type="hidden" ..>
。标记发送给客户端。然后将它们发送回服务器端并验证服务器端。
有了这些知识,您就会发现您对同时发送“cookie token ”和“表单 token ”的担忧是没有根据的,因为无论如何都会发生这种情况。
我确实看到您担心的原因,但您试图缓解的似乎是中间人 (MitM) 攻击,而不是 CSRF。要解决大部分 MitM 攻击,您应该确保您的站点/API 都在 HTTPS 上运行。如果您的客户端仍然容易受到 MitM 攻击,您的 API 可能不会受到攻击者的关注。
关于javascript - AJAX POST 到 Web API Controller 和 CSRF,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41905773/