IdentityServer4之自定义用户数据Claim
作者:互联网
IdentityServer4之自定义用户数据Claim
前面章节案例是使用TestUsers是用于测试的,而通常系统一般都要接入来源于数据库中的已有用户,需实现IProfileService和IResourceOwnerPasswordValidator接口。
1. 实现IResourceOwnerPasswordValidator接口
实现IResourceOwnerPasswordValidator接口,来定义我们自己的验证逻辑。
/// <summary>
/// 自定义 Resource owner password 验证器
/// </summary>
public class CustomResourceOwnerPasswordValidator: IResourceOwnerPasswordValidator
{
/// <summary>
/// 这里为了演示我们还是使用TestUser作为数据源,
/// 正常使用此处应当传入一个 用户仓储 等可以从
/// 数据库或其他介质获取我们用户数据的对象
/// </summary>
private readonly TestUserStore _users;
private readonly ISystemClock _clock;
public CustomResourceOwnerPasswordValidator(TestUserStore users, ISystemClock clock)
{
_users = users;
_clock = clock;
}
/// <summary>
/// 验证
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
//此处使用context.UserName, context.Password 用户名和密码来与数据库的数据做校验
if (_users.ValidateCredentials(context.UserName, context.Password))
{
var user = _users.FindByUsername(context.UserName);
//验证通过返回结果
//subjectId 为用户唯一标识 一般为用户id
//authenticationMethod 描述自定义授权类型的认证方法
//authTime 授权时间
//claims 需要返回的用户身份信息单元 此处应该根据我们从数据库读取到的用户信息 添加Claims 如果是从数据库中读取角色信息,那么我们应该在此处添加
context.Result = new GrantValidationResult(
user.SubjectId ?? throw new ArgumentException("Subject ID not set", nameof(user.SubjectId)),
OidcConstants.AuthenticationMethods.Password, _clock.UtcNow.UtcDateTime,
user.Claims);
}
else
{
//验证失败
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
}
return Task.CompletedTask;
}
}
2. 实现IProfileService接口
实现了IResourceOwnerPasswordValidator还不够,我们还需要实现IProfileService接口,他是专门用来装载我们需要的Claim信息的,比如在token创建期间和请求用户信息终结点是会调用它的GetProfileDataAsync方法来请求需要的Claim类型装载信息,下面是一个简单实现:
public class CustomProfileService: IProfileService
{
/// <summary>
/// The logger
/// </summary>
protected readonly ILogger Logger;
/// <summary>
/// The users
/// </summary>
protected readonly TestUserStore Users;
/// <summary>
/// Initializes a new instance of the <see cref="TestUserProfileService"/> class.
/// </summary>
/// <param name="users">The users.</param>
/// <param name="logger">The logger.</param>
public CustomProfileService(TestUserStore users, ILogger<TestUserProfileService> logger)
{
Users = users;
Logger = logger;
}
/// <summary>
/// 只要有关用户的身份信息单元被请求(例如在令牌创建期间或通过用户信息终点),就会调用此方法
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
public virtual Task GetProfileDataAsync(ProfileDataRequestContext context)
{
context.LogProfileRequest(Logger);
//判断是否有请求Claim信息
if (context.RequestedClaimTypes.Any())
{
//根据用户唯一标识查找用户信息
var user = Users.FindBySubjectId(context.Subject.GetSubjectId());
if (user != null)
{
//调用此方法以后内部会进行过滤,只将用户请求的Claim加入到 context.IssuedClaims 集合中 这样我们的请求方便能正常获取到所需Claim
context.AddRequestedClaims(user.Claims);
}
}
context.LogIssuedClaims(Logger);
return Task.CompletedTask;
}
/// <summary>
/// 验证用户是否有效 例如:token创建或者验证
/// </summary>
/// <param name="context">The context.</param>
/// <returns></returns>
public virtual Task IsActiveAsync(IsActiveContext context)
{
Logger.LogDebug("IsActive called from: {caller}", context.Caller);
var user = Users.FindBySubjectId(context.Subject.GetSubjectId());
context.IsActive = user?.IsActive == true;
return Task.CompletedTask;
}
}
IResourceOwnerPasswordValidator 是为了对接已有的用户数据,然后才是实现 IProfileService 以添加自定义 claim。
3. 添加用户接口
这里模拟从数据库读取用户数据的仓储接口IUserRepository及实现类UserRepository。
Claims = new List<Claim>(){new Claim(JwtClaimTypes.Role, "admin"),new Claim("性别","男"),new Claim(JwtClaimTypes.Address, "上海") |
public interface IUserRepository
{
List<TestUser> GetTestUsers();
TestUser GetTestUserByNamePassword(string name,string password);
}
public class UserRepository : IUserRepository
{
List<TestUser> TestUserList = new List<TestUser>
{
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password",
Claims = new List<Claim>(){new Claim(JwtClaimTypes.Role,"superadmin") }
},
new TestUser
{
SubjectId = "2",
Username = "bob",
Password = "password",
Claims = new List<Claim>(){new Claim(JwtClaimTypes.Role,"superadmin") }
},
new TestUser
{
SubjectId = "3",
Username = "yak",
Password = "yakpassword",
Claims = new List<Claim>(){new Claim(JwtClaimTypes.Role, "admin"),new Claim("性别","男"),new Claim(JwtClaimTypes.Address, "上海") }
}
};
public TestUser GetTestUserByNamePassword(string name, string password)
{
var qure = TestUserList.Where(i => i.Username == name && i.Password == password).FirstOrDefault();
return qure;
}
public List<TestUser> GetTestUsers()
{
return TestUserList;
}
}
4. 注册服务
在Startup类里启用我们自定义的ProfileService和ResourceOwnerValidator,并且在Startup配置Service时不再需要AddTestUsers,因为将使用我们自己的用户信息,而
TestUser对象由IdentityServer4.Test 提供的。
代码如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<IUserRepository, UserRepository>();
var builder = services.AddIdentityServer()
.AddInMemoryIdentityResources(Config.GetIdentityResources())
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.GetClients())
//.AddTestUsers(Config.GetUsers())
.AddProfileService<CustomProfileService>()
.AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>();
builder.AddDeveloperSigningCredential();
}
5. 运行
选择多个启动项目,运行。
6. 使用postman测试
获取token,然后获取用户的信息。
7. 使用WinForm测试
取天气
[HttpGet]
[Authorize(Roles = "admin")]
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
取用户信息
[Authorize(Roles = "admin")]
[HttpGet]
public IActionResult GetClaims()
{
return new JsonResult(from c in HttpContext.User.Claims select new { c.Type, c.Value });
}
鸣谢
https://gitee.com/github_mirrors/identityserver4_doc.zh-cn
https://www.cnblogs.com/stulzq/p/8726002.html
https://www.cnblogs.com/sheng-jie/p/9430920.html
标签:Claim,users,自定义,用户,context,new,public,IdentityServer4 来源: https://www.cnblogs.com/yakniu/p/16182094.html