在研究IdentityServer4的过程中遇到过两个问题: - 在IdentityServer4通常的资料是针对Web的,而不是通过接口 - .Net Core 2.1 2.0有些地方和.NET和.Net Core 2.0都不一样 正好发现一个项目是在 .Net Core写,并且IdentityServer4提供接口。项目地址:https://github.com/marklaygo/Angular-IdentityServer-WebAPI 首先来看`Config.cs` ``` public static class Config { //User public static List GetUsers() { return new List{ new TestUser{ SubjectId="1", Username="", Password="" } }; } //IdentityResource public static IEnumerable GetIdentityResources() { return new IdentityResource[]{ new IdentityResources.OpenId(), new IdentityResources.Profile(); }; } //API public static IEnumerable GetApis() { return new ApiResource[]{ new ApiResource("api1", "webApi"), new ApiResource("accountApi", "AccountApi") //这里的api给验证服务器自己用,因为验证服务器通过接口开发给外界 }; } //Client public static IEnumerable GetClients() { renturn new Client[] { new Client { ClientId="Angular", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, RequireClientSecret = fasle, AllowAccessTokenViaBrowser = true, AccessTokenLifetime = 300, AllowOfflineAccess = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId,//客户端要的和IdentityServer4给的保持一致 IdentityServerConstatns.StandardScopes.Profile,//客户端要的和IdentityServer4给的保持一致 "api1",//api告诉Client "accountApi"//api告诉Client } } }; } } ``` 再来看上下文,一定是继承`IdentityDbContext`。 ``` public class ApplicationDbContext : IdentityDbContext { public class ApplicationDbContext(DbContextOptions options) : base(options){ } protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder); } } public class ApplicationUser : IdentityUser { } ``` 有关对外开放的claim,通过实现`IProfileService`接口。 ``` public class IdentityProfileSerivce : IProfileService { private readonly IUserClaimsPrincipalFactory _claimsFactory; private readonly UserManager _userManager; public IdentityProfileService(IUserClaimsPrincipalFactory claimsFactory, UserManager userManager) { _claimsFactory = claimsFactory; _userManager = userManager; } public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var subjectId = context.Subject.GetSubjectId(); var user = awati _userManager.FindByIdAsync(subjectId); var principal = await _claimsFactory.CreateAsync(user); var claims = principal.Claims.ToList(); claims.Add(new Claim(JwtClaimTypes.Role, "test_role")); context.IssuedClaims = claim; } public async Task IsActiveAsync(IsActiveContext context) { var subjectId = context.Subject.GetSubjectId(); var user = awati _userManager.FindByIdAsync(subjectId); context.IsActive = user != null; } } ``` `Startup.cs`配置文件 ``` public class Startup { public IConfiguration Configuration{get;} public IHostingEnvironment Environment{get;} public Startup(IConfiguraiotn configuraiotn, IHostingEnvironment environment) { Configuraiton = configuration; Envrionment = environment; } public void ConfigureService(IServiceCollection services) { //上下文 services.AddDbContext(options => options.UserSqlServer(Configuration.GetConnectionString("DefaultConnection"))); //Identity servcies.AddIdentity() .AddEntityFrameworkeStore() .AddDefaultTokenProviders(); //有关Password的配置 services.COnfigure(options => { options.Password.RequrieDigit = false; options.Password.RequrieLength =4; options.Password.ReqruieNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequrieLowercase = false; }) //mvc services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); //对外开发的claim services.AddTransient(); //http://localhost:5000验证服务器的地址 //验证服务器的配置 var builder = service.AddIdentityServer() .AddInMemoryIndentityRecouse(Config.GetIdenityResource()) .AddInMemoryApiResource(Config.GetApis()) .AddInMemoryClients(Config.GetClients()) .AddAspNetIdentity() .AddProfileService(); //验证服务器接口本身也需要验证 services.AddAuthetnication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = "http://localhost:5000"; options.RequireHttpsMetadata = false; options.Audience = "accountApi"; options.TokenValidatioinParamsters = new TokenValidationParameters() { ClockSkew = TimeSpan.FromMinutes(0) } }); //cors services.AddCors(options => { options.AddPolicy("default", policy => { policy.WithOrigin("http://localhost:4200") .AllowAnyHeader() .AllowAnyMethod(); }); }); if(Environment.IsDevelopment()) { builder.AddDeveloperSigningCredential(); } else { throw new Exception("need to configure key material"); } } public void Configure(IApplicationBuilder app, ApplicationDbContext context) { if(Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseCors("default"); app.UseIdentityServer(); app.UseAuthentication(); app.UseMvc(); //种子化数据 try { context.Database.Migrate(); context.Users.RemoveRange(contexts.Users.ToList()); context.SaveChanges(); var user = new ApplicationUser { UserName = "", NormalizedUserName = "", Email = "", NormalizedEmail = "", EmailConfirmed = true, LockoutEnabled = fasle, SecurityStamp = Guid.NewGuid().ToString() }; if(!context.Users.Any(u =>u.UserName == user.UserName)) { var passwordHasher = new PasswordHasher(); var hased = passwordHasher.HashPassword(user,""); user.PasswordHash = hased; var userStore = new UserStore(context); userStore.CreateAsync(user); } } catch(Exception ex) { } } } ``` 模型 ``` public class RegisterViewModel { [Required] [EmailAddress] publci string Email{get;set;} [Requried] [DataType(DataType.Password)] public string Password{get;set;} } public class ChangePasswordViewModel { [Required] [DataType(DataType.EmailAddress)] public string Email{get;set;} [Required] [DataType(DataType.Password)] public string OldPassword{get;set;} [Requried] [DataType(DataType.Password)] public string NewPassword{get;set;} } ``` 最后来到有关用户管理的控制器。 ``` [Route("api/[controller]")] publci class AccountController : ControllerBase { private readonly UserManager _userManager; private readonly IUserClaimPrincipalFactory _claimsFactory; //构造函数略 [HttpPost("Register")] public async Task Register([FromBody]RegisterViewModel model) { if(ModelState.IsValid) { var user = new ApplicationUser {UserName = model.Email, Email = model.Email}; var result = awati _userManager.CreateAsync(user, model.Password); return new JsonResult(result); } return BadRequest(); } [HttpPost("ChangePassword")] [Authorize(AuthenticationSchemes = "Bearer")] public async Task ChangePassword([FromBody]ChangePasswordViewModel model) { if(ModelState.IsValid) { var user = await _userManager.FindByEmailAsync(model.Email); if(user!=null) { var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword); return new JsonResult(result); } } return BadRequest(); } } ```