【问题标题】:Controller.User is inconsistently set by ASP.NET Identity on ASP.NET MVC 5Controller.User 在 ASP.NET MVC 5 上由 ASP.NET Identity 设置不一致
【发布时间】: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 实现:stackoverflow.com/questions/21418902/…

标签: c# asp.net-mvc asp.net-mvc-5 asp.net-identity owin


【解决方案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());
    }

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-07-23
    • 2015-12-09
    • 2014-02-18
    • 2020-05-08
    • 2013-11-10
    • 2020-06-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多