|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- IdentityServer4有自己的验证逻辑,需要将需要的Claim放入验证结果,然后向API传递。实现`IResourceOwernerPasswordValidator`这个接口就可以。
-
- #在In-Memory中添加Claim
-
- 添加用户
- ```
- new TestUser{
- SubjectId="",
- Username="",
- Password="",
- Claims = new List<Claim>{new Claim(JwtClaimTypes.Role, "superadmin")}
- },
- new TestUser{
- SubjectId = "",
- Username="",
- Password="",
- Claims = new List<Claim>{new Claim(JwtClaimTypes.Role, "admin")}
- }
- ```
-
- 此时查看`HttpContext.User.Claims`却没有刚才Type是role的claim。为什么呢?
-
- --因为在验证服务器上管理的`ApiResource`中没有把自定义的claim设置进去。如何设置呢?
-
- ```
- public static IEnumerable<ApiResource> GetApiResources()
- {
- return new List<ApiResource>{
- new ApiResource("","",new List<string>(){JwtClaimTypes.Role})
- };
- }
- ```
-
- 为什么在`ApiResource`的构造函数把claim能传递出去呢? 再看`ApiResource`的源代码。
-
- ```
- public ApiResouce(string name, string displayName, IEnumerable<string> claimTypes)
- {
- if(name.IsMissing()) throw new ArgumentNullException(nameof(name));
-
- Name = name;
- DisplayName = displayName;
-
- //也就是IdentityServer4管理的ApiResource最终会以Scope的形式放在claim集合中
- Scopes.Add(new Scope(name, displayName));
-
- if(!claimTypes.IsNullOrEmpty())
- {
- foreach(var type in claimTypes)
- {
- UserClaim.Add(type);
- }
- }
- }
- ```
-
- 原来给`ApiResource`中的自定义claim给到了这里的`UserClaims`属性,一看这个属性就使用用来存放用户有关的所有claim,肯定是一个集合。
-
- ```
- public ICollection<string> UserClaims{get;set;} = new HashSet<string>();
- ```
-
- 在API控制器方法中定义不同的控制器方法给不同的角色。
- ```
- [Route("[controller]")]
- public class IdentityController : ControllerBase
- {
- [Authorize(Roles = "superadmin")]
- [HttpGet]
- public IActionResult Get()
- {
- return new JsonResult(from c in HttpContext.User.Claims select new {c.Type, c.Value});
- }
-
- [Authorize(Roles = "admin")]
- [Route("id")]
- [HttpGet]
- public string Get(int id)
- {
- return id.ToString();
- }
- }
- ```
-
- 在一个控制器客户端使用superadmin角色访问第二个控制器方法,即需要admin角色的控制器方法。
-
- ```
- response = await client.GetAsync("http://localhost:5001/identity/1");
- if(!response.IsSuccessStatusCode)
- {
- Console.WriteLine(response.StatusCode);
- Console.WriteLine("没有权限访问");
- }
- else
- {
- var content = response.Content.ReadAsStringAsync().Result;
- Console.WriteLine(content);
- }
- ```
- # 在生产环境下添加claim
-
- 需要实现`IResourceOwnerPasswordValidator`接口。
-
- ```
- public class CustomResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
- {
- private readonly TestUserStore _users;
- private readonly ISystemClock _clock;
-
- public CustomResourceOwnerPasswordValidator(TestUserStore users, ISystemClock clock)
- {
- _users = users;
- _clock = clock;
- }
-
- public Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
- {
- if(_users.ValidateCredentials(context.UserName, context.Password))//首先还是要验证用户名和密码
- {
- var user= _users.FindByUsername(context.UserName);
-
- context.Result = new GrantValidationResult(user.SubjectId ?? throw new ArgumentException(), OidcConstants.AuthenticationMethods.Password, _clock.UtcNow.UtcDateTime, user.claims);//这里把所有的claim返回假如验证逻辑
- }
- else
- {
- context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "");
- }
- return Task.CompletedTask;
- }
- }
- ```
-
- 需要在DI容器中配置。
- ```
- public class Startup
- {
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddIdentityServer()
- .AddDeveloperSigningCredential()
- .AddInMemoryApiResources(Config.GetApiResources())
- .AddInMemoryClients(Config.GetClients())
- .AddTestUsers(Config.GetUsers())
- .AddResourceOwnerValidator<CustomResourceOwnerPasswordValidator>();
- }
- }
- ```
-
- 以上已有用户的claim信息已经放到了验证逻辑中。接下来还需要`IProfileService`接口的帮忙。
-
- ```
- public class CustomProfileService : IProfileService
- {
- protected readonly ILogger Logger;
- protected readoly TestUserStore Users;
-
- public CustomProfileService(TestUserStore users, ILogger<TestUserProfilerService> logger)
- {
- Users = users;
- Logger = logger;
- }
-
- //这里的方法在创建令牌期间会被调用
- public virutal Task GetProfileDataAsync(ProfileDataRequestContext context)
- {
- context.LogProfileRequest(Logger);
-
- if(context.ReqeustedClaimTypes.Any())
- {
- var user = Users.FindBySubjectId(context.Subject.GetSubjectId());
- if(user != null)
- {
- //这里只将用户请求的claim加入到context.IssuedClaims集合中去
- context.AddRequestedClaims(user.Claims);
- }
- }
-
- context.LogIssuedClaims(Logger);
-
- return Task.CompletedTask;
- }
- }
- ```
-
- 以上只返回客户端请求的claims,但是如果用以下写法会把所有的claims都返回给客户端。
-
- ```
- context.IssuedClaims.AddRange(user.Claims);
- ```
-
- `IProfileService`也需要放到容器中。
-
- ```
- servces.AddProfileService<CustomProfileService>();
- ```
-
- 以上所有的claim返回就少了控制,不见得是好事。
-
- 如果想控制发出的claims,一种方式是通过身份资源。身份资源会放到token的Scope参数中。
-
- ```
- public static IEnumerable<IdentiyResource> GetIdentityResourceResources()
- {
- var customProfile = new IdentityResource{
- "custom.profile","",new []{"role"}
- };
-
- return new List<IdentityResource>{
- new IdentityResource.OpenId(),
- new IdentityResource.Profile(),
- customProfile
- }
- }
- ```
-
- 需要注意的是IdentityResource需要和Client中的AllowdScopes配合起来用。
- ```
- new Client
- {
- ClientId="",
- AllowdGrantTypes = GrantTypes.ResourceOwnerPassword,
- ClientSecrets = {
- new Secret()
- },
- //包括APIresource, IdentityResource
- AllowdScopes = {"api1", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstatns.StandardScopes.Profile, "custom.profile"}
- }
- ```
-
- 另外在Client资源里有一个Claims属性,这里的设置会被直接添加到token中去。
- ```
- new Client
- {
- Claims = new List<Claim>{
- new Claim(JwtCliamTypes.Role, "admin");
- }
- }
- ```
- 放在这里的claim会在token中以`client_role`出现,但在使用的时候还是`Authorize(Roles="Admin")`.
-
-
- > 总结时刻
-
- 为API提供自定义claim,可以尝试如下切入点:
-
- - 实现`IResourceOwnerPasswordValidator`接口
- - 实现`IProfileService`接口
- - `IdentiyResource`和`Client`的`AllowedScopes`配合
- - `IdentiyResource`和`Client`的`Claims`配合
|