ASP.NET Core Identity
怎么用
StartUp
ConfigureServices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17public void ConfigureServices(IServiceCollection services)
{
//EF 配置
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
//注入Identity依赖
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//以下不重要....跟Identity无关
//Add application services.
services.AddTransient<IEmailSender, EmailSender>();
services.AddMvc();
}Configure
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
三大核心对象
SignInManager: 主要处理注册登录相关业务逻辑。
e.g.
用户密码登录
1
var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
注册并登录
1
2
3
4
5
6
7
8
9
10
11
12
13var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
_logger.LogInformation("User created a new account with password.");
var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
var callbackUrl = Url.EmailConfirmationLink(user.Id, code, Request.Scheme);
await _emailSender.SendEmailConfirmationAsync(model.Email, callbackUrl);
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation("User created a new account with password.");
}
UserManager: 处理用户相关添加删除,修改密码,添加删除角色等。
e.g.
创建用户
1
2var user = new ApplicationUser { UserName = model.Email, Email = model.Email };
var result = await _userManager.CreateAsync(user, model.Password);查询用户
1
var user = await _userManager.FindByIdAsync(userId);
RoleManager:角色相关添加删除更新等。
原理
StartUp
ConfigureServices
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40services.AddIdentity<ApplicationUser, IdentityRole>();
//以上代码主要实现如下:
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddCookie(IdentityConstants.ApplicationScheme, o =>
{
o.LoginPath = new PathString("/Account/Login");
o.Events = new CookieAuthenticationEvents
{
OnValidatePrincipal = SecurityStampValidator.ValidatePrincipalAsync
};
})
.AddCookie(IdentityConstants.ExternalScheme, o =>
{
o.Cookie.Name = IdentityConstants.ExternalScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
})
.AddCookie(IdentityConstants.TwoFactorRememberMeScheme,
o => o.Cookie.Name = IdentityConstants.TwoFactorRememberMeScheme)
.AddCookie(IdentityConstants.TwoFactorUserIdScheme, o =>
{
o.Cookie.Name = IdentityConstants.TwoFactorUserIdScheme;
o.ExpireTimeSpan = TimeSpan.FromMinutes(5);
});
//..........................................................
//错误信息描述
services.TryAddScoped<IdentityErrorDescriber>();
//..........................................................
services.TryAddScoped<UserManager<TUser>, AspNetUserManager<TUser>>();
services.TryAddScoped<SignInManager<TUser>, SignInManager<TUser>>();
services.TryAddScoped<RoleManager<TRole>, AspNetRoleManager<TRole>>();services.AddAuthentication
主要注入了IAuthenticationService
,IAuthenticationHandlerProvider
,IAuthenticationSchemeProvider
几个重要的实例1
2
3
4services.TryAddScoped<IAuthenticationService, AuthenticationService>();
services.TryAddScoped<IAuthenticationHandlerProvider, AuthenticationHandlerProvider>();
services.TryAddSingleton<IAuthenticationSchemeProvider, AuthenticationSchemeProvider>();IAuthenticationService
用来实现
HttpContext
下的几个扩展方法:Authenticate/Challenge/Forbid/SignIn/SignOut
IAuthenticationSchemeProvider
IAuthenticationService
会先根据IAuthenticationSchemeProvider
获取指定的AuthenticationScheme
(认证方案)AuthenticationScheme
有三个主要属性Name
(方案名称) ,DisplayName
,HandlerType
(AuthenticationHandler
的类型)IAuthenticationHandlerProvider
IAuthenticationHandlerProvider
根据指定的AuthenticationScheme
(认证方案) 的HandlerType
属性获取到AuthenticationHandler
以下是一个
AuthenticationService
中SignInAsync
的实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public virtual async Task SignInAsync(HttpContext context, string scheme, ClaimsPrincipal principal, AuthenticationProperties properties)
{
if (principal == null)
{
throw new ArgumentNullException(nameof(principal));
}
if (scheme == null)
{
//通过 AuthenticationSchemeProvider 获取 DefaultSignInScheme
var defaultScheme = await Schemes.GetDefaultSignInSchemeAsync();
scheme = defaultScheme?.Name;
if (scheme == null)
{
throw new InvalidOperationException($"No authenticationScheme was specified, and there was no DefaultSignInScheme found.");
}
}
//通过 AuthenticationHandlerProvider 获取到 DefaultSignInScheme 对应的AuthenticationHandlerProvider
var handler = await Handlers.GetHandlerAsync(context, scheme) as IAuthenticationSignInHandler;
if (handler == null)
{
throw new InvalidOperationException($"No IAuthenticationSignInHandler is configured to handle sign in for the scheme: {scheme}");
}
await handler.SignInAsync(principal, properties);
}
AddCookie
AddCookie
会将CookieAuthenticationHandler
作为AuthenticationScheme
的HandlerType
1
2
3
4
5public static AuthenticationBuilder AddCookie(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<CookieAuthenticationOptions> configureOptions)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<CookieAuthenticationOptions>, PostConfigureCookieAuthenticationOptions>());
return builder.AddScheme<CookieAuthenticationOptions, CookieAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
}将
AuthenticationScheme
添加到AuthenticationOptions
中。AuthenticationOptions
会将AuthenticationScheme
添加到Schemes
和SchemeMap
两个属性。AuthenticationSchemeProvider
也是通过这两个属性获取到AuthenticationScheme
的。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//
// Summary:
// Returns the schemes in the order they were added (important for request handling
// priority)
public IEnumerable<AuthenticationSchemeBuilder> Schemes { get; }
//
// Summary:
// Maps schemes by name.
public IDictionary<string, AuthenticationSchemeBuilder> SchemeMap { get; }
//
// Summary:
// Adds an Microsoft.AspNetCore.Authentication.AuthenticationScheme.
//
// Parameters:
// name:
// The name of the scheme being added.
//
// configureBuilder:
// Configures the scheme.
public void AddScheme(string name, Action<AuthenticationSchemeBuilder> configureBuilder);
Configure
三大核心对象
实现原理
先看下
SignManager
下SignIn/Out
的实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public virtual async Task SignInAsync(TUser user, AuthenticationProperties authenticationProperties, string authenticationMethod = null)
{
var userPrincipal = await CreateUserPrincipalAsync(user);
// Review: should we guard against CreateUserPrincipal returning null?
if (authenticationMethod != null)
{
userPrincipal.Identities.First().AddClaim(new Claim(ClaimTypes.AuthenticationMethod, authenticationMethod));
}
await Context.SignInAsync(IdentityConstants.ApplicationScheme,
userPrincipal,
authenticationProperties ?? new AuthenticationProperties());
}
/// <summary>
/// Signs the current user out of the application.
/// </summary>
public virtual async Task SignOutAsync()
{
await Context.SignOutAsync(IdentityConstants.ApplicationScheme);
await Context.SignOutAsync(IdentityConstants.ExternalScheme);
await Context.SignOutAsync(IdentityConstants.TwoFactorUserIdScheme);
}没错,最终都是调用
HttpContext
下的扩展方法实现,而这些扩展方法是通过AuthenticationService
实现的,AuthenticationService
又是根据AuthenticationScheme
中的AuthenticationHandler
来实现。最终,这里是由CookieAuthenticationHandler
来实现怎么实现数据持久化
通过
Microsoft.AspNetCore.Identity.EntityFrameworkCore
1
services.AddEntityFrameworkStores<ApplicationDbContext>()
本地化提示信息
可以在
AddIdentity
的实现中找到以下代码,IdentityErrorDescriber
用于错误提示。(e.g. 用户名和密码不匹配的提示)。现在全都是英文提示,注意到注入方法
TryAddScoped
(如果已经注入过此类型,不再注入),可以在AddIdentity
之前注入自己重写的IdentityErrorDescriber
,比如ChineseIdentityErrorDescriber
1
2//错误信息描述
services.TryAddScoped<IdentityErrorDescriber>();