【发布时间】: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 正确显示为身份
有人能指出为什么会这样吗?当然,我确实将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 实现:stackoverflow.com/questions/21418902/…。
标签: c# asp.net-mvc asp.net-mvc-5 asp.net-identity owin