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
设置的会话不一致。例如,如果我构建应用程序然后运行它(无论是否调试),会发生这种情况:
有人能指出为什么会这样吗?当然,我确实将SignIn
和SignOut
方法中的代码从它所在的控制器中提取到了AuthenticationService
中,但我并不完全相信这是导致不一致的原因。由于所有程序集都由 MVC 应用程序处理,因此它们应该都可以正常工作,对吧?非常感谢您的帮助。
更新
在谷歌搜索时发现了这个,虽然它被标记为已解决,但我对解决方案有点困惑。 https://katanaproject.codeplex.com/workitem/201 我不认为这与我的问题有任何关系了。
更新 2
我认为问题的根源在于调用管道中某处的异步,特别是在我注入到UserManager<Employee, int>
的EmployeeStore
中。我有一个名为EmployeeStore
的UserStore
的自定义实现。它实现了IQueryableUserStore<Employee, int>
、IUserStore<Employee, int>
、IUserPasswordStore<Employee, int>
和IUserRoleStore<Employee, int>
。
当我调试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 设置不一致的主要内容,如果未能解决你的问题,请参考以下文章