我有一个MVC 6项目(vNext),我正在玩ASP.NET身份。在我的情况下,我不想使用使用EF(SignInManager,UserManager,UserStore)的内置东西。我有一个外部数据库,我只想进行用户名/密码查找并返回一个有效的cookie。所以我开始写自己的课程。
public class MyUser
{
public string Id { get; set; }
public string UserName { get; set; }
public string Password { get; set; }
public string PasswordHash { get; set; }
}
public class MyUserStore : IUserStore<MyUser>, IUserPasswordStore<MyUser>
{
...
}
在MyUserStore
类中,我使用硬编码的用户列表作为我的商店(仅用于测试目的)。我重写了一些方法只是为了从硬编码的商店中返回数据。
public class MyUserManager : UserManager<MyUser>
{
public MyUserManager(
IUserStore<MyUser> store,
IOptions<IdentityOptions> optionsAccessor,
IPasswordHasher<MyUser> passwordHasher,
IEnumerable<IUserValidator<MyUser>> userValidators,
IEnumerable<IPasswordValidator<MyUser>> passwordValidators,
ILookupNormalizer keyNormalizer,
IdentityErrorDescriber errors,
IEnumerable<IUserTokenProvider<MyUser>> tokenProviders,
ILoggerFactory logger,
IHttpContextAccessor contextAccessor) :
base(store, optionsAccessor, passwordHasher, userValidators, passwordValidators, keyNormalizer, errors, tokenProviders, logger, contextAccessor)
{
}
}
在这里,我使用CheckPasswordAsync
和VerifyPasswordAsync
方法分别返回true
和PasswordVerificationResult.Success
进行测试。
public class MyClaimsPrincipleFactory : IUserClaimsPrincipalFactory<MyUser>
{
public Task<ClaimsPrincipal> CreateAsync(MyUser user)
{
return Task.Factory.StartNew(() =>
{
var identity = new ClaimsIdentity();
identity.AddClaim(new Claim(ClaimTypes.Name, user.UserName));
var principle = new ClaimsPrincipal(identity);
return principle;
});
}
}
public class MySignInManager : SignInManager<MyUser>
{
public MySignInManager(MyUserManager userManager, IHttpContextAccessor contextAccessor, IUserClaimsPrincipalFactory<MyUser> claimsFactory, IOptions<IdentityOptions> optionsAccessor = null, ILoggerFactory logger = null)
: base(userManager, contextAccessor, claimsFactory, optionsAccessor, logger)
{
}
public override Task<SignInResult> PasswordSignInAsync(string userName, string password, bool isPersistent, bool shouldLockout)
{
// here goes the external username and password look up
if (userName.ToLower() == "username" && password.ToLower() == "password")
{
return base.PasswordSignInAsync(userName, password, isPersistent, shouldLockout);
}
else
{
return Task.FromResult(SignInResult.Failed);
}
}
}
所有内容都连接在Startup
类中,如下所示:
services.AddIdentity<MyUser, MyRole>()
.AddUserStore<MyUserStore>()
.AddUserManager<MyUserManager>()
.AddDefaultTokenProviders();
因为我没有设法在MySignInManager
代码中创建Startup
对象以便将其添加到DI中(以便稍后在控制器和视图中注入),我在MyAccountController
中创建它。
public MyAccountController(IHttpContextAccessor httpContextAccessor, UserManager<MyUser> userManager, IOptions<IdentityOptions> optionsAccessor, ILoggerFactory logger)
{
SignInManager = new MySignInManager(userManager as MyUserManager, httpContextAccessor, new MyClaimsPrincipleFactory(), optionsAccessor, logger);
}
在我在MyLogin
控制器中的MyAccount
动作中,我正在调用PasswordSignInAsync
,我可以看到我正在获取带有编码声明的cookie(来自MyClaimsPrincipleFactory
)。当我尝试使用AuthorizeAttribute
调用其他一些操作时,我可以看到cookie位于请求标头中,但我未经授权(更确切地说,因为我没有删除内置的默认ASP.NET身份验证visual studio示例模板,我被重定向到Account / Login而不是)。
这是自定义ASP.NET身份的正确方法吗?我在这里缺少什么?
我也有尝试使用自定义SignInManager
的问题,结果实现起来真的很容易。
在Startup.cs
中,在默认执行services.Identity
之后
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
您只需要注入以下内置DI:
services.AddScoped<SignInManager<MyApplicationUser>, MySignInManager>();
默认的SignInManager
被自定义覆盖。
在我的项目中,我有一个不使用EF的身份的工作实现。我想也许你的实施超出了你的需要。 UserManager和SignInManager与EF无关。如果你愿意,你可以实现这些,但你不必只是为了摆脱EF。如果您需要验证硬编码密码以进行测试,您只需要实现UserStore和RoleStore以及PasswordHasher。
services.TryAdd(ServiceDescriptor.Scoped<IUserStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserPasswordStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserEmailStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserLoginStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserRoleStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserClaimStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserPhoneNumberStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserLockoutStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserTwoFactorStore<SiteUser>, UserStore<SiteUser>>());
services.TryAdd(ServiceDescriptor.Scoped<IRoleStore<SiteRole>, RoleStore<SiteRole>>());
services.TryAdd(ServiceDescriptor.Scoped<IUserClaimsPrincipalFactory<SiteUser>, SiteUserClaimsPrincipalFactory<SiteUser, SiteRole>>());
services.TryAdd(ServiceDescriptor.Transient<IPasswordHasher<SiteUser>, SitePasswordHasher<SiteUser>>());
services.AddIdentity<SiteUser, SiteRole>();
上面显示了我为了绕过Entity Framework而实现的项目,我的AccountController例如采用了构造函数参数
UserManager<SiteUser> userManager,
SignInManager<SiteUser> signInManager
这是标准的身份UserManager和SignInManager,我没有设置DI服务,他们为我注册了这条线:
services.AddIdentity<SiteUser, SiteRole>();
你可以see the code for that extension method here。它是身份的一部分,不属于EFIdentity。你可以看到我也实现了IUserClaimsPrincipalFactory。我实现的唯一原因是添加一些自定义声明,我不需要这样做就可以远离EF。
这里的问题是我没有提供我的AuthenticationType
对象的ClaimsIdentity
。 This blog post帮助了我。
要将IsAuthenticated设置为true,您需要在ctor中指定身份验证类型:
var id = new ClaimsIdentity(claim,“Custom”);
您可以在SignInManager
方法中使用IdentityBuilder.AddSignInManager
方法为依赖注入注册自定义Startup.ConfigureServices
,如下所示:
services.AddIdentity<MyUser, IdentityRole<int>>()
.AddUserStore<UserStore<MyUser, IdentityRole<int>, SqlContext, int>>()
.AddRoleStore<RoleStore<IdentityRole<int>, SqlContext, int>>()
.AddSignInManager<SignInManager<MyUser>>()
.AddDefaultTokenProviders();
您实施的SignInManager
没有理由不能以相同的方式注册DI。