再也不對IdentityServer4作相關介紹,博客園上已經有人出了相關的系列文章,不瞭解的能夠看一下:html
蟋蟀大神的:小菜學習編程-IdentityServer4git
曉晨Master:IdentityServer4github
以及Identity,Claim等相關知識:web
Savorboard:ASP.NET Core 之 Identity 入門(一),ASP.NET Core 之 Identity 入門(二)數據庫
建立一個名爲QuickstartIdentityServer的ASP.NET Core Web 空項目(asp.net core 2.0),端口5000編程
NuGet包:後端
修改Startup.cs 設置使用IdentityServer:api
public class Startup { public void ConfigureServices(IServiceCollection services) { // configure identity server with in-memory stores, keys, clients and scopes 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:app
public class Config { public static IEnumerable<IdentityResource> GetIdentityResourceResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), //必需要添加,不然報無效的scope錯誤 new IdentityResources.Profile() }; } // scopes define the API resources in your system public static IEnumerable<ApiResource> GetApiResources() { return new List<ApiResource> { new ApiResource("api1", "My API") }; } // clients want to access resources (aka scopes) public static IEnumerable<Client> GetClients() { // client credentials client return new List<Client> { new Client { ClientId = "client1", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1",IdentityServerConstants.StandardScopes.OpenId, //必需要添加,不然報forbidden錯誤 IdentityServerConstants.StandardScopes.Profile}, }, // resource owner password grant client new Client { ClientId = "client2", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1",IdentityServerConstants.StandardScopes.OpenId, //必需要添加,不然報forbidden錯誤 IdentityServerConstants.StandardScopes.Profile } } }; } }
由於要使用登陸的時候要使用數據中保存的用戶進行驗證,要實IResourceOwnerPasswordValidator接口:asp.net
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator { public ResourceOwnerPasswordValidator() { } public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context) { //根據context.UserName和context.Password與數據庫的數據作校驗,判斷是否合法 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"); } } //能夠根據須要設置相應的Claim 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接口:
public class ProfileService : IProfileService { public async Task GetProfileDataAsync(ProfileDataRequestContext context) { try { //depending on the scope accessing the user data. var claims = context.Subject.Claims.ToList(); //set issued claims to return context.IssuedClaims = claims.ToList(); } catch (Exception ex) { //log your error } } 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服務端校驗。
建立一個名爲API的ASP.NET Core Web 空項目(asp.net core 2.0),端口5001。
NuGet包:
修改Startup.cs 設置使用IdentityServer進行校驗:
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:
[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 :
[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
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; // 從claims取出用戶相關信息,到數據庫中取得用戶具有的權限碼,與當前Controller或Action標識的權限碼作比較 var userPermissions = "User_Edit"; if (!authorizeAttributes.Any(s => s.Permission.Equals(userPermissions))) { context.Result = new JsonResult("沒有權限"); } return; } }
新建TestAuthorizeAttribute
[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項目。
經過修改權限碼,驗證是否起做用
除了使用過濾器和特性結合使用,貌似還有別的方法,有空再研究。
本文中的源碼