Controller.User 在 ASP.NET MVC 5 上由 ASP.NET Identity 设置不一致

Posted

技术标签:

【中文标题】Controller.User 在 ASP.NET MVC 5 上由 ASP.NET Identity 设置不一致【英文标题】:Controller.User is inconsistently set by ASP.NET Identity on ASP.NET MVC 5 【发布时间】:2014-04-30 12:50:44 【问题描述】:

我正在尝试将我的身份验证与我的控制器分离,因此我制作了一个 AuthenticationService,我使用 Ninject 将其注入到我的身份验证控制器 (DefaultController) 中。下面是AuthenticationService的实现:

public sealed class AuthenticationService 
    private IAuthenticationManager AuthenticationManager  get; set; 
    private UserManager<Employee, int> EmployeeManager  get; set; 

    public AuthenticationService(
        IAuthenticationManager authenticationManager,
        UserManager<Employee, int> employeeManager) 
        this.AuthenticationManager = authenticationManager;
        this.EmployeeManager = employeeManager;
    

    public bool SignIn(
        CredentialsInput credentials) 
        Employee employee = this.EmployeeManager.Find(credentials.Email, credentials.Password);

        if (employee != null) 
            ClaimsIdentity identityClaim = this.EmployeeManager.CreateIdentity(employee, DefaultAuthenticationTypes.ApplicationCookie);

            if (identityClaim != null) 
                this.AuthenticationManager.SignIn(new AuthenticationProperties(), identityClaim);

                return true;
            
        

        return false;
    

    public void SignOut() 
        this.AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie);
    

下面是我如何配置 Ninject 来进行注射:

private static void RegisterServices(
    IKernel kernel) 
    kernel.Bind<IUserStore<Employee, int>>().To<EmployeeStore>().InRequestScope();
    kernel.Bind<IRoleStore<Role, int>>().To<RoleStore>().InRequestScope();
    kernel.Bind<IAuthenticationManager>().ToMethod(
        c =>
            HttpContext.Current.GetOwinContext().Authentication).InRequestScope();

这是我配置 OWIN 的方式:

public sealed class StartupConfig 
    public void Configuration(
        IAppBuilder app) 
        this.ConfigureAuthentication(app);
    

    public void ConfigureAuthentication(
        IAppBuilder app) 
        app.UseCookieAuthentication(new CookieAuthenticationOptions 
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            LoginPath = new PathString("/"),
            ExpireTimeSpan = new TimeSpan(0, 60, 0)
        );
    

哦,这里是DefaultController

public sealed class DefaultController : Controller 
    private AuthenticationService AuthenticationService  get; set; 

    public DefaultController(
        AuthenticationService authenticationService) 
        this.AuthenticationService = authenticationService;
    

    [HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
    public RedirectToRouteResult Default(
        [Bind(Prefix = "Credentials", Include = "Email,Password")] CredentialsInput credentials) 
        if (base.ModelState.IsValid
            && this.AuthenticationService.SignIn(credentials)) 
            //  Place of error. The IsInRole() method doesn't return
            //  the correct answer because the
            //  IUserStore<>.FindByIdAsync() method is not setting
            //  the correct data.
            if (this.User.IsInRole("Technician")) 
                return this.RedirectToAction<TechniciansController>(
                    c =>
                        c.Default());
            

            return this.RedirectToAction(
                c =>
                    c.Dashboard());
        

        return this.RedirectToAction(
            c =>
                c.Default());
    

    [HttpGet]
    public RedirectToRouteResult SignOut() 
        this.AuthenticationService.SignOut();

        return this.RedirectToAction(
            c =>
                c.Default());
    

嗯,这一切都有效。我遇到的问题是UserManager 设置的会话不一致。例如,如果我构建应用程序然后运行它(无论是否调试),会发生这种情况:

构建并运行 以 user1@email.com 身份登录 退出 以 user2@email.com 身份登录 在第一次发布请求时,user1 仍然显示为身份 在第二次发布请求(刷新)时,user2 正确显示为身份

有人能指出为什么会这样吗?当然,我确实将SignInSignOut 方法中的代码从它所在的控制器中提取到了AuthenticationService 中,但我并不完全相信这是导致不一致的原因。由于所有程序集都由 MVC 应用程序处理,因此它们应该都可以正常工作,对吧?非常感谢您的帮助。

更新

在谷歌搜索时发现了这个,虽然它被标记为已解决,但我对解决方案有点困惑。 https://katanaproject.codeplex.com/workitem/201 我不认为这与我的问题有任何关系了。

更新 2

我认为问题的根源在于调用管道中某处的异步,特别是在我注入到UserManager&lt;Employee, int&gt;EmployeeStore 中。我有一个名为EmployeeStoreUserStore 的自定义实现。它实现了IQueryableUserStore&lt;Employee, int&gt;IUserStore&lt;Employee, int&gt;IUserPasswordStore&lt;Employee, int&gt;IUserRoleStore&lt;Employee, int&gt;

当我调试FindByIdAsync(int employeeId) 方法时,我看到它出于某种原因触发了两次。它第一次触发时,我看到正确的employeeId 传入了它。第二次触发时,employeeId 设置为 0。我认为这是它搞砸的地方,因为它随后不会调用 IsInRoleAsync(Employee employee, string roleName) 方法。

当我在页面抛出 UserId 未找到的异常后刷新页面时,FindByIdAsync(...) 方法再次被调用两次,但这一次employeeId 都是正确的,并且然后它继续调用IsInRoleAsync(...) 方法。

以下是这两种方法的代码:

public Task<Employee> FindByIdAsync(
    int employeeId) 
    this.ThrowIfDisposed();

    return this.Repository.FindSingleOrDefaultAsync<Employee, int>(employeeId);


public Task<bool> IsInRoleAsync(
    Employee employee,
    string roleName) 
    this.ThrowIfDisposed();

    if (employee == null) 
        throw new ArgumentNullException("employee");
    

    if (String.IsNullOrEmpty(roleName)) 
        throw new ArgumentNullException("roleName");
    

    return Task.FromResult<bool>(employee.Roles.Any(
        r =>
            (r.Name == roleName)));

【问题讨论】:

只是为了清楚起见——ASP.NET Identity 与在 MVC 中设置 User 属性无关。这就是 Katana 身份验证中间件的工作。 ASP.NET Identity 在数据库中管理用户的凭据和身份数据。 我建议不要使用 Task 中的异步方法。任务中的异步方法使用 IIS 线程池中的线程,这意味着用于服务 HTTP 请求的线程更少。此外,由于方法 IsInRoleAsync() 的具体细节是用于授权,因此调用线程在继续(授予/拒绝)之前无论如何都应该等待结果,这意味着使方法 Async 实际上会使过程变慢。 完全同意@ErikPhilips。您不应该使用不同的线程进行身份验证。他打我的时候只是在打字 @ErikPhilips,我同意你的观点,但是接口的所有方法都是异步的,所以我没有选择。我通过使用 ILSpy 探索 EF Identity 实现来创建我的实现,并且大部分与他们所做的相匹配。我唯一不同的是使用我已经存在的存储库。话虽如此,我在我的登录和注销方法中使用了同步扩展方法,它们只是我认为应该强制它们同步的异步方法的包装器? 对于那些感兴趣的人,我已经在这个问题的答案中描述了我的 ASP.NET Identity 实现:***.com/questions/21418902/…。 【参考方案1】:

好吧,我没有真正的解决方案,但我有一个可行的解决方案。由于在登录过程中没有设置User,我重定向到另一个名为DefaultRedirect 的操作,现在我可以完全访问User 属性,并且可以从那里重定向我真正想要的方式。这是一个使事情正常进行的肮脏作弊,但这不是真正的解决方案。我仍然认为 ASP.NET Identity 无法正常工作。无论如何,这是我的作弊:

    [HttpPost, AllowAnonymous, ValidateAntiForgeryToken]
    public RedirectToRouteResult Default(
        [Bind(Prefix = "Credentials", Include = "Email,Password")] CredentialsInput credentials) 
        if (base.ModelState.IsValid
            && this.AuthenticationService.SignIn(credentials)) 
            return this.RedirectToAction(
                c =>
                    c.DefaultRedirect());
        

        return this.RedirectToAction(
            c =>
                c.Default());
    

    [HttpGet]
    public RedirectToRouteResult DefaultRedirect() 
        if (this.User.IsInRole("Technician")) 
            return this.RedirectToAction<TechniciansController>(
                c =>
                    c.Default());
        

        return this.RedirectToAction(
            c =>
                c.Dashboard());
    

如果有人知道,我仍在寻找真正的解决方案!

【讨论】:

以上是关于Controller.User 在 ASP.NET MVC 5 上由 ASP.NET Identity 设置不一致的主要内容,如果未能解决你的问题,请参考以下文章

在 ASP.NET 中接受信用卡的最佳方式是啥? (在 ASP.NET 和 Authorize.NET 之间)

ASP.NET Core与ASP.NET区别

ASP.NET_基础

ASP.NET和ASP的区别是啥?

ASP.NET 生命周期 – ASP.NET 应用生命周期

在 ServiceFabric 中托管旧版 ASP.NET(不是 ASP.NET Core)