我想通过 AntiforgeryToken 属性保护我们的登录操作 - 我知道为什么会出现该主题的异常,但我似乎无法找到任何好的解决方案。
假设我们有以下情况:
上午8点,应用程序用户开始工作,他们坐下来开始登录过程 - 现在非常可能某些用户将获得相同的 ValidationToken 。在第一个登录后 - 所有其他人在尝试登录时都会看到上述异常(或其他一些自定义异常屏幕)。
有些用户已登录,然后意外按下" 返回"按钮并尝试再次登录 - 虽然这种情况不太可能发生,但可能会发生,并且我不希望用户看到例外情况。
所以问题很简单 - 如何防止上述情况,或如何处理它们以便用户不会注意到任何事情。我尝试过以下方法:
现在我正在考虑手动验证动作正文中的令牌,捕获错误,并检查匿名用户是否尝试:
public ActionResult SomeAction()
{
try
{
AntiForgery.Validate();
}
catch(HttpAntiForgeryException ex)
{
if(String.IsNullOrEmpty(HttpContext.User.Identity.Name))
{
throw;
}
}
//Rest of action body here
//..
//..
}
以上似乎可以防止错误 - 但是安全吗? 有哪些替代方案?
提前致谢。
最好的问候。
编辑:
最终解决方案"是在登录表单上禁用令牌验证 - 可能有更好的方法来处理它,但似乎我发现的所有解决方案都是类似于我上面提到的丑陋的解决方法。
因为没有办法知道"安全"那些替代方案(如果它们完全是安全的),我们决定在登录时禁用令牌验证。
答案 0 :(得分:16)
尝试设置(在global.cs中):
AntiForgeryConfig.UniqueClaimTypeIdentifier = ClaimTypes.NameIdentifier;
这会将名称标识符添加到您的令牌
对于双重登录问题,请尝试使用脚本记录原始提交的日期和时间,以便使用相同的令牌停止第二次提交。
// jQuery plugin to prevent double submission of forms
jQuery.fn.preventDoubleSubmission = function() {
$(this).on('submit',function(e){
var $form = $(this);
if ($form.data('submitted') === true) {
// Previously submitted - don't submit again
e.preventDefault();
} else {
// Mark it so that the next submit can be ignored
$form.data('submitted', true);
}
});
// Keep chainability
return this;
};
所以我们知道一件事;用户喜欢后退按钮并且有双击的习惯,这是AntiforgeryToken的一个大问题。
但是,根据您的应用程序的作用,有限制其强制的方法。最简单的方法是尽力让访问者不要觉得他们需要“倒回”他们改变它的请求。
确保表单错误消息传递清晰简洁以确保 用户知道什么是错的。上下文错误会给予奖励积分。
始终在表单提交之间维护表单状态。除了 密码或信用卡号码,没有理由感谢MVC 帮助者。
@Html.LabelFor(x => x.FirstName)
如果表单分布在制表符或隐藏的div中,例如在中使用的那些 像Angular或者ember.js这样的SPA框架,很聪明,并且显示出来 控制器布局或错误实际源自的形式 显示错误时的表单提交。不要只是指导他们 到家庭控制器或第一个标签。
“发生了什么事?” - 让用户了解情况
当AntiForgeryToken未验证您的网站时,将抛出类型为System.Web.Mvc.HttpAntiForgeryException的异常。
如果您设置正确,则会启用友好错误,这意味着您的错误页面不会显示异常并显示一个很好的错误页面,告诉他们发生了什么。
通过捕获HttpAntiForgeryException,至少为用户提供针对这些异常的信息更丰富的页面,可以使这更容易。
private void Application_Error(object sender, EventArgs e)
{
Exception ex = Server.GetLastError();
if (ex is HttpAntiForgeryException)
{
Response.Clear();
Server.ClearError(); //make sure you log the exception first
Response.Redirect("/error/antiforgery", true);
}
}
并且您的/error/antiforgery
视图可以告诉他们抱歉您尝试过两次提交相同的信息
另一个想法是记录错误并将用户返回登录屏幕:
创建一个覆盖OnException方法的HandleAntiforgeryTokenErrorAttribute
类。
HandleAntiforgeryTokenErrorAttribute.cs:
public class HandleAntiforgeryTokenErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
filterContext.ExceptionHandled = true;
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { action = "Login", controller = "Account" }));
}
}
全局过滤器:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new HandleAntiforgeryTokenErrorAttribute()
{ ExceptionType = typeof(HttpAntiForgeryException) }
);
}
}
我还会使用一些工具来记录您的所有信息,因为登录是您应用程序的关键部分
NLog ,用于关键应用程序例外(包括网络例外)的常规日志记录和电子邮件。
Elmah ,用于过滤和发送网络例外电子邮件。
修改强> 您还可以查看一个名为SafeForm的jQuery插件。 Link
修改强>
我已经看到有关此问题的争论,每个人对这个问题的观点都有正确的观点,我怎么看(取自owasp.org)
跨站点请求伪造(CSRF)是一种迫使最终用户的攻击 在他们所在的Web应用程序上执行不需要的操作 当前已通过身份验证,CSRF攻击专门针对状态更改请求,而不是数据被盗。防伪令牌是 特定于'谁登录'。所以一旦你登录,然后回去, 旧令牌不再有效
现在,如果用户IP地址发生变化,我还会使用授权的IP地址登录我的应用程序,并使用2因素授权,因此如果跨站点请求伪造正在进行中,则用户将无法匹配IP地址,要求2因素授权。几乎像安全路由器的工作方式。但是如果你想把它保存在你的登录页面上我没有看到问题,只要你设置了友好的错误页面,人们就不会感到不安,因为他们会看到他们做错了。
答案 1 :(得分:0)
我只是想添加到 pool pro's answer - 关于过滤器部分 - 必须小心,因为这将覆盖所有关于 AntiforgeryToken 的例外,如果您(像我一样)在应用程序的其他部分进行了此令牌验证,您可能会考虑向过滤器添加一些验证:
public class FilterConfig
{
public static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new HandleErrorAttribute());
filters.Add(new HandleAntiforgeryTokenErrorAttribute() { ExceptionType = typeof(HttpAntiForgeryException) });
}
}
public class HandleAntiforgeryTokenErrorAttribute : HandleErrorAttribute
{
public override void OnException(ExceptionContext filterContext)
{
string actionName = filterContext.Controller.ControllerContext.RouteData.Values["action"].ToString();
string controllerName = filterContext.Controller.ControllerContext.RouteData.Values["controller"].ToString();
if (actionName.ToLower() == "login" && controllerName.ToLower() == "account")
{
//Handle Error
//In here you handle the error, either by logging, adding notifications, etc...
//Handle Exception
filterContext.ExceptionHandled = true;
filterContext.Result = new RedirectToRouteResult(
new RouteValueDictionary(new { action = "Login", controller = "Account" }));
}
else
{
base.OnException(filterContext);
}
}
}
请注意,我将在 Account/Login
方法上抛出此异常的情况与可能使用 [ValidateAntiForgeryToken]
的所有其他方法分开。