在我们开发的电子商务解决方案中,我们使用AspNet Identity 2.2.1,并且要求任何访客(匿名(用户无需事先注册网站即可完成结帐。为了满足这一要求,编写了一个名为UserMigrationAttribute的ActionFilter,它从cookie中获取SessionTrackId(字符串GUID(-如果未找到SessionTrackId以及请求cookie,我们将从HttpModule为每个请求设置该Cookie-并在数据库中使用类似于 SessionTrackId@mydomain.com 的用户名创建实际的IdentityUser。
我们用这个UserMigration属性装饰了我们的BaseController类,以便在整个站点中使用它的功能。
到目前为止,一切都按预期工作,但存在单个缺点问题,即当任何用户第一次加载页面时,如果我们尝试对具有[ValidateAntiForgeryToken]
属性的方法进行 Jquery Ajax 调用,调用也会失败并显示 'The provided anti-forgery token was meant for a different claims-based user than the current user.
' 错误,即使我们在每次 ajax 调用时都发送__RequestVerificationToken
参数。
但是,如果用户通过单击链接打开另一个页面和/或重新加载/刷新当前页面,则所有后续 ajax 调用都成功完成。
根据我们的理解,UserMigrationAttribute 在 OnActionExecute 方法上创建用户,但是在我们登录用户的过程中@Html.AntiForgeryToken(( 没有使用正确的值进行更新。
您可以在下面找到用户迁移属性代码;
[AttributeUsage(AttributeTargets.Class)]
public class UserMigrationAttribute : ActionFilterAttribute
{
public ApplicationSignInManager SignInManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().Get<ApplicationSignInManager>();
}
public UserManager UserManager(ActionExecutingContext filterContext)
{
return filterContext.HttpContext.GetOwinContext().GetUserManager<UserManager>();
}
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CreateMigrateCurrentUser(filterContext);
base.OnActionExecuting(filterContext);
}
private static readonly object LockThis = new object();
private void CreateMigrateCurrentUser(ActionExecutingContext filterContext)
{
lock (LockThis)
{
var signInManager = SignInManager(filterContext);
var userManager = UserManager(filterContext);
var sessionTrackId = GetSessionTrackId(filterContext);
if (!filterContext.HttpContext.Request.IsAuthenticated)
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}@mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user == null)
{
user = new User() {UserName = username, Email = username};
var result = userManager.Create(user);
userManager.AddToRole(user.Id, StringResources.AnonymousVisitorsGroup);
}
signInManager.SignIn(user, true, true);
}
}
else
{
if (!string.IsNullOrEmpty(sessionTrackId))
{
var username = string.Format("{0}@mydomain.com", sessionTrackId);
var user = userManager.FindByName(username);
if (user != null)
{
if (!HttpContext.Current.User.IsInRole(StringResources.AnonymousVisitorsGroup))
{
var targetUserId = HttpContext.Current.User.Identity.GetUserId<int>();
var service = new Service();
service.Users.MigrateUser(user.Id, targetUserId);
}
}
}
}
}
}
private string GetSessionTrackId(ActionExecutingContext filterContext)
{
var retVal = string.Empty;
if (filterContext.HttpContext.Request.Cookies["stid"] != null)
{
retVal = filterContext.HttpContext.Request.Cookies["stid"].Value;
}
return retVal;
}
}
任何帮助或建议都非常感谢。
谢谢
发生这种情况是因为防伪令牌是在 cookie 中设置的,直到下一个请求才会更新。如果您手动登录用户,您还应该发出重定向(即使他们已经转到同一页面(,以确保 cookie 数据正确无误。这通常是自然发生的,因为登录表单将在用户登录后重定向到需要授权的 URL,从而消除问题。由于您当前未重定向,因此数据不同步。
但是,我不得不说,对于这个特定用例来说,这似乎是一个非常糟糕的解决方案。创建某种临时类型的用户并登录该用户以处理访客结帐,充其量会在您的数据库中产生不必要的无用数据过剩,并导致错误和其他问题,例如您遇到的问题,最坏的情况是。
我还经营着一个电子商务网站,我们处理客人结账的方式非常简单。结帐数据仅存储在会话中(电子邮件,送货/帐单地址等(。我们构建了一个视图模型来处理实际结账,其中提交销售所需的数据来自用户对象(如果他们已登录(或这些会话变量(如果他们未登录(。如果用户既未登录,也没有设置必要的会话变量,则他们将被重定向到收集计费/运输等的载入表单。
对于其他方面,例如维护匿名购物车,我们使用带有购物车标识符的永久cookie。如果用户最终创建了一个帐户,我们将匿名购物车与其用户相关联,然后删除cookie。这可确保他们的购物车在会话超时和关闭浏览器等操作后幸存下来,即使它们是匿名的。
换句话说,在所有这些事情中,实际上不需要用户对象。如果它在那里(用户已登录(,太好了,我们将使用它。否则,我们会收集并保留必要的信息,以便通过其他方式结账。