在研究IdentityServer4的过程中遇到过两个问题:
正好发现一个项目是在 .Net Core写,并且IdentityServer4提供接口。项目地址:https://github.com/marklaygo/Angular-IdentityServer-WebAPI
首先来看Config.cs
public static class Config
{
//User
public static List<TestUser> GetUsers()
{
return new List<TestUser>{
new TestUser{
SubjectId="1",
Username="",
Password=""
}
};
}
//IdentityResource
public static IEnumerable<IdentityResource> GetIdentityResources()
{
return new IdentityResource[]{
new IdentityResources.OpenId(),
new IdentityResources.Profile();
};
}
//API
public static IEnumerable<ApiResource> GetApis()
{
return new ApiResource[]{
new ApiResource("api1", "webApi"),
new ApiResource("accountApi", "AccountApi") //这里的api给验证服务器自己用,因为验证服务器通过接口开发给外界
};
}
//Client
public static IEnumerable<Client> 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<ApplicationUser>
{
public class ApplicationDbContext(DbContextOptions<ApplicationDbContext> 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<ApplicationUser> _claimsFactory;
private readonly UserManager<ApplicationUser> _userManager;
public IdentityProfileService(IUserClaimsPrincipalFactory<ApplicaitonUser> claimsFactory, UserManager<ApplicationUser> 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<ApplicationDbContext>(options => options.UserSqlServer(Configuration.GetConnectionString("DefaultConnection")));
//Identity
servcies.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkeStore<ApplicationDbContext>()
.AddDefaultTokenProviders();
//有关Password的配置
services.COnfigure<IdentityOptions>(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<IProfileService, IdentityProfileService>();
//http://localhost:5000验证服务器的地址
//验证服务器的配置
var builder = service.AddIdentityServer()
.AddInMemoryIndentityRecouse(Config.GetIdenityResource())
.AddInMemoryApiResource(Config.GetApis())
.AddInMemoryClients(Config.GetClients())
.AddAspNetIdentity<ApplicationUser>()
.AddProfileService<IdentityProfileService>();
//验证服务器接口本身也需要验证
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<ApplicatonUser>();
var hased = passwordHasher.HashPassword(user,"");
user.PasswordHash = hased;
var userStore = new UserStore<ApplicationUser>(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<ApplicationUser> _userManager;
private readonly IUserClaimPrincipalFactory<ApplicatioinUser> _claimsFactory;
//构造函数略
[HttpPost("Register")]
public async Task<IActionResult> 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<IActionResult> 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();
}
}