基于IdentityServer4示例的修改 - zLulus/My_Note GitHub Wiki
自己的业务与需要修改的内容
1.Client,Api Resource,Identity Resource从内存存储改为EF存储
2.将一个Client改为多个Client
3.将邮箱账号改为手机号账号
4.Api需要增加一个查询用户信息的接口,提供给Client
具体实施
Client,Api Resource,Identity Resource从内存存储改为EF存储
在IdentityServer项目的Startup.cs中进行数据库配置
public void ConfigureServices(IServiceCollection services)
{
const string connectionString = @"数据库连接字符串";
var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(Config.GetUsers())
//从数据库中添加配置数据 (clients, resources)
.AddConfigurationStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
})
//从数据库中添加操作数据 (codes, tokens, consents)
.AddOperationalStore(options =>
{
options.ConfigureDbContext = builder =>
builder.UseSqlServer(connectionString,
sql => sql.MigrationsAssembly(migrationsAssembly));
// this enables automatic token cleanup. this is optional.
options.EnableTokenCleanup = true;
options.TokenCleanupInterval = 30;
});
}
将一个Client改为多个Client
不同的Client,配置的时候,ClientId不同
其对应的ClientSecret,Scope也不同
注意:
在客户端的授权功能要求一致的情况下,也可以多个客户端使用同一个ClientId,比如单点登录,所有客户端要做的都是登录
则在IdentityServer眼中,认为两个客户端是相同的,不过不影响业务流程(登录)
将邮箱账号改为手机号账号
将前端校验和数据库数据结构约束改为手机号即可
Api需要增加一个查询用户信息的接口,提供给Client
IdentityServer4取消了原来的IUserService,将其拆分成了IResourceOwnerPasswordValidator和IProfileService,需要实现接口
IResourceOwnerPasswordValidator处理资源所有者密码凭证的验证,IProfileService允许IdentityServer连接到用户和配置文件存储
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
//用户表仓储
private readonly IUserRepository _userRepository;
public ResourceOwnerPasswordValidator(IUserRepository userRepository)
{
_userRepository = userRepository; //DI
}
//验证你的用户帐户,并提供令牌
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
try
{
var user = await _userRepository.FindAsync(context.UserName);
if (user != null)
{
//核实密码
if (user.Password == context.Password) {
context.Result = new GrantValidationResult(
subject: user.UserId.ToString(),
authenticationMethod: "custom",
claims: GetUserClaims(user));
return;
}
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Incorrect password");
return;
}
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "User does not exist.");
return;
}
catch (Exception ex)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "Invalid username or password");
}
}
//从用户数据中构建claims
public static Claim[] GetUserClaims(User user)
{
return new Claim[]
{
new Claim("user_id", user.UserId.ToString() ?? ""),
new Claim(JwtClaimTypes.Name, (!string.IsNullOrEmpty(user.Firstname) && !string.IsNullOrEmpty(user.Lastname)) ? (user.Firstname + " " + user.Lastname) : ""),
new Claim(JwtClaimTypes.GivenName, user.Firstname ?? ""),
new Claim(JwtClaimTypes.FamilyName, user.Lastname ?? ""),
new Claim(JwtClaimTypes.Email, user.Email ?? ""),
new Claim("some_claim_you_want_to_see", user.Some_Data_From_User ?? ""),
//roles
new Claim(JwtClaimTypes.Role, user.Role)
};
}
public class ProfileService : IProfileService
{
private readonly IUserRepository _userRepository;
public ProfileService(IUserRepository userRepository)
{
_userRepository = userRepository;
}
//调用/connect/userinfo时,根据声明获取用户配置文件日期
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
try
{
//取决于访问用户数据的范围
if (!string.IsNullOrEmpty(context.Subject.Identity.Name))
{
//从数据库中查询用户
var user = await _userRepository.FindAsync(context.Subject.Identity.Name);
if (user != null)
{
var claims = GetUserClaims(user);
//返回结果
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
else
{
//从context获得subject (在ResourceOwnerPasswordValidator.ValidateAsync中设置的)
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "sub");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
//查询用户
var user = await _userRepository.FindAsync(long.Parse(userId.Value));
if (user != null)
{
var claims = ResourceOwnerPasswordValidator.GetUserClaims(user);
context.IssuedClaims = claims.Where(x => context.RequestedClaimTypes.Contains(x.Type)).ToList();
}
}
}
}
catch (Exception ex)
{
//记录错误
}
}
//核实用户的IsActive是否为true
public async Task IsActiveAsync(IsActiveContext context)
{
try
{
//从context获得subject (在ResourceOwnerPasswordValidator.ValidateAsync中设置的),
var userId = context.Subject.Claims.FirstOrDefault(x => x.Type == "user_id");
if (!string.IsNullOrEmpty(userId?.Value) && long.Parse(userId.Value) > 0)
{
var user = await _userRepository.FindAsync(long.Parse(userId.Value));
if (user != null)
{
if (user.IsActive)
{
context.IsActive = user.IsActive;
}
}
}
}
catch (Exception ex)
{
//记录错误
}
}
}
在Startup.cs里面定义
public void ConfigureServices(IServiceCollection services)
{
//添加仓储
services.AddScoped<IUserRepository, UserRepository>();
services.AddIdentityServer()
//省略了一些内容
//添加个人资料服务
.AddProfileService<ProfileService>();
//注入服务
services.AddTransient<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>();
services.AddTransient<IProfileService, ProfileService>();
}
参考资料:
https://stackoverflow.com/questions/35304038/identityserver4-register-userservice-and-get-users-from-database-in-asp-net-core/
/connect/userinfo接口说明:
http://docs.identityserver.io/en/release/endpoints/userinfo.html#identitymodel