相关知识点 不再对IdentityServer4做相关介绍,博客园上已经有人出了相关的系列文章,不了解的可以看一下:
蟋蟀大神的:小菜学习编程-IdentityServer4
晓晨Master:IdentityServer4
以及Identity,Claim等相关知识:
Savorboard: ASP.NET Core 之 Identity 入门(一) ,ASP.NET Core 之 Identity 入门(二)
创建IndentityServer4 服务 创建一个名为QuickstartIdentityServer的ASP.NET Core Web 空项目(asp.net core 2.0),端口5000
NuGet包:
修改Startup.cs 设置使用IdentityServer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Startup { public void ConfigureServices (IServiceCollection services ) { services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryIdentityResources(Config.GetIdentityResourceResources()) .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddResourceOwnerValidator<ResourceOwnerPasswordValidator>() .AddProfileService<ProfileService>(); } public void Configure (IApplicationBuilder app, IHostingEnvironment env ) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); } }
添加Config.cs配置IdentityResource,ApiResource以及Client:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class Config { public static IEnumerable<IdentityResource> GetIdentityResourceResources ( ) { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }; } public static IEnumerable<ApiResource> GetApiResources ( ) { return new List<ApiResource> { new ApiResource("api1" , "My API" ) }; } public static IEnumerable<Client> GetClients ( ) { return new List<Client> { new Client { ClientId = "client1" , AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret" .Sha256()) }, AllowedScopes = { "api1" ,IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile}, }, new Client { ClientId = "client2" , AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret" .Sha256()) }, AllowedScopes = { "api1" ,IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile } } }; } }
因为要使用登录的时候要使用数据中保存的用户进行验证,要实IResourceOwnerPasswordValidator接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { public ResourceOwnerPasswordValidator ( ) { } public async Task ValidateAsync (ResourceOwnerPasswordValidationContext context ) { if (context.UserName=="wjk" &&context.Password=="123" ) { context.Result = new GrantValidationResult( subject: context.UserName, authenticationMethod: "custom" , claims: GetUserClaims()); } else { context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential" ); } } private Claim[] GetUserClaims ( ) { return new Claim[] { new Claim("UserId" , 1. ToString()), new Claim(JwtClaimTypes.Name,"wjk" ), new Claim(JwtClaimTypes.GivenName, "jaycewu" ), new Claim(JwtClaimTypes.FamilyName, "yyy" ), new Claim(JwtClaimTypes.Email, "977865769@qq.com" ), new Claim(JwtClaimTypes.Role,"admin" ) }; } }
IdentityServer提供了接口访问用户信息,但是默认返回的数据只有sub,就是上面设置的subject: context.UserName,要返回更多的信息,需要实现IProfileService接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public class ProfileService : IProfileService { public async Task GetProfileDataAsync (ProfileDataRequestContext context ) { try { var claims = context.Subject.Claims.ToList(); context.IssuedClaims = claims.ToList(); } catch (Exception ex) { } } public async Task IsActiveAsync (IsActiveContext context ) { context.IsActive = true ; }
context.Subject.Claims就是之前实现IResourceOwnerPasswordValidator接口时claims: GetUserClaims()给到的数据。 另外,经过调试发现,显示执行ResourceOwnerPasswordValidator 里的ValidateAsync,然后执行ProfileService 里的IsActiveAsync,GetProfileDataAsync。
启动项目,使用postman进行请求就可以获取到token:
再用token获取相应的用户信息:
token认证服务一般是与web程序分开的,上面创建的QuickstartIdentityServer项目就相当于服务端,我们需要写业务逻辑的web程序就相当于客户端。当用户请求web程序的时候,web程序拿着用户已经登录取得的token去IdentityServer服务端校验。
创建web应用 创建一个名为API的ASP.NET Core Web 空项目(asp.net core 2.0),端口5001。
NuGet包:
修改Startup.cs 设置使用IdentityServer进行校验:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 public class Startup { public void ConfigureServices (IServiceCollection services ) { services.AddMvcCore(option=> { option.Filters.Add(new TestAuthorizationFilter()); }).AddAuthorization() .AddJsonFormatters(); services.AddAuthentication("Bearer" ) .AddIdentityServerAuthentication(options => { options.Authority = "http://localhost:5000" ; options.RequireHttpsMetadata = false ; options.ApiName = "api1" ; }); } public void Configure (IApplicationBuilder app ) { app.UseAuthentication(); app.UseMvc(); } }
创建IdentityController:
1 2 3 4 5 6 7 8 9 10 11 [Route("[controller]" ) ] public class IdentityController : ControllerBase { [HttpGet ] [Authorize ] public IActionResult Get ( ) { return new JsonResult("Hello Word" ); } }
分别运行QuickstartIdentityServer,API项目。用生成的token访问API:
通过上述程序,已经可以做一个前后端分离的登录功能。
实际上,web应用程序中我们经常需要获取当前用户的相关信息进行操作,比如记录用户的一些操作日志等。之前说过IdentityServer提供了接口/connect/userinfo来获取用户的相关信息。之前我的想法也是web程序中拿着token去请求这个接口来获取用户信息,并且第一次获取后做相应的缓冲。但是感觉有点怪怪的,IdentityServer不可能没有想到这一点,正常的做法应该是校验通过会将用户的信息返回的web程序中。问题又来了,如果IdentityServer真的是这么做的,web程序该怎么获取到呢,查了官方文档也没有找到。然后就拿着”Claim”关键字查了一通(之前没了解过ASP.NET Identity),最后通过HttpContext.User.Claims取到了设置的用户信息:
修改IdentityController :
1 2 3 4 5 6 7 8 9 10 11 [Route("[controller]" ) ] public class IdentityController : ControllerBase { [HttpGet ] [Authorize ] public IActionResult Get ( ) { return new JsonResult(from c in HttpContext.User.Claims select new { c.Type, c.Value }); } }
权限控制 IdentityServer4 也提供了权限管理的功能,大概看了一眼,没有达到我想要(没耐心去研究)。 我需要的是针对不同的模块,功能定义权限码(字符串),每个权限码对应相应的功能权限。当用户进行请求的时候,判断用户是否具备相应功能的权限(是否赋予了相应的权限字符串编码),来达到权限控制。
IdentityServer的校验是通过Authorize特性来判断相应的Controller或Action是否需要校验。这里也通过自定义特性来实现权限的校验,并且是在原有的Authorize特性上进行扩展。可行的方案继承AuthorizeAttribute,重写。可是在.net core中提示没有OnAuthorization方法可进行重写。最后参考的ABP的做法,过滤器和特性共同使用。
新建TestAuthorizationFilter.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 public class TestAuthorizationFilter : IAuthorizationFilter { public void OnAuthorization (AuthorizationFilterContext context ) { if (context.Filters.Any(item => item is IAllowAnonymousFilter)) { return ; } if (!(context.ActionDescriptor is ControllerActionDescriptor)) { return ; } var attributeList = new List<object >(); attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.GetCustomAttributes(true )); attributeList.AddRange((context.ActionDescriptor as ControllerActionDescriptor).MethodInfo.DeclaringType.GetCustomAttributes(true )); var authorizeAttributes = attributeList.OfType<TestAuthorizeAttribute>().ToList(); var claims = context.HttpContext.User.Claims; var userPermissions = "User_Edit" ; if (!authorizeAttributes.Any(s => s.Permission.Equals(userPermissions))) { context.Result = new JsonResult("没有权限" ); } return ; } }
新建TestAuthorizeAttribute
1 2 3 4 5 6 7 8 9 10 11 12 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true) ] public class TestAuthorizeAttribute : AuthorizeAttribute { public string Permission { get ; set ; } public TestAuthorizeAttribute (string permission ) { Permission = permission; } }
将IdentityController [Authorize]改为[TestAuthorize(“User_Edit”)],再运行API项目。
通过修改权限码,验证是否起作用
除了使用过滤器和特性结合使用,貌似还有别的方法,有空再研究。
本文中的源码