最近在折騰IdentityServer4,爲了簡單,直接使用了官方給的QuickStart示例項目做爲基礎進行搭建。有一說一,爲了保護一個API,感受花費的時間比寫一個API還要多。html
本文基於ASP.NET CORE 3.1, IdentityServer4 3.1.3。代碼皆爲關鍵代碼,貼全了太多了。web
好不容易跑起來了,最終的任務要落實到受權的工做上來。在API中使用Authorize
用來限制用戶的訪問。數據庫
[Route("api/[controller]")] [Authorize(Roles = "Administrator")] [ApiController] public class UserInfoController : ControllerBase { /// <summary> /// 無參GET請求 /// </summary> /// <returns></returns> [HttpGet()] [ProducesResponseType(typeof(ReturnData<IEnumerable<UserInfo>>), Status200OK)] public async Task<ActionResult> Get() { var info = new Info<UserInfo>(); return Ok(new ReturnData<IEnumerable<UserInfo>>(await info.Get())); }
然而在使用的時候,雖然正確取得受權,可是卻沒法正常訪問API,一直提示401沒有受權錯誤。仔細檢查,發現IdentityServer4返回的內容並無返回role的JwtClaimTypes
,沒有它,Authorize
沒法正常工做。json
{ "nbf": 1587301921, "exp": 1587305521, "iss": "http://localhost:5000", "aud": "MonitoringSystemApi", "client_id": "webClient", "sub": "c6c18d4d-c28e-4de5-86dd-779121216204", "auth_time": 1587301921, "idp": "local", "scope": [ "roles", "MonitoringSystemApi", "offline_access" ], "amr": [ "pwd" ] }
查看Config.cs,IdentityServer4默認只返回兩種IdentityResource:openid和profile。按照官方的說法,這個東西定義的內容會返回到用戶的token。參考。那麼就果斷給它安排。c#
public static IEnumerable<IdentityResource> Ids => new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResource ("roles", new List<string> { JwtClaimTypes.Role }){ Required = true} }; public static IEnumerable<Client> Clients => new List<Client> { new Client { ClientId = "webClient", ClientSecrets = { new Secret("secret".Sha256()) }, AllowOfflineAccess = true, AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // scopes that client has access to AllowedScopes = { "roles", "MonitoringSystemApi" } },
執行以前,須要確保數據庫中的用戶數據,已經包含role的Claim。api
//添加用戶代碼 bob = new ApplicationUser { UserName = "bob" }; var result = userMgr.CreateAsync(bob, "Pass123$").Result; if (!result.Succeeded) { throw new Exception(result.Errors.First().Description); } result = userMgr.AddClaimsAsync(bob, new Claim[]{ new Claim(JwtClaimTypes.Role, "Administrator"), new Claim(JwtClaimTypes.Name, "Bob Smith"),
運行程序,返回值依舊沒有任何變化,很挫敗,只能繼續折騰。
有研究經過實現IProfileService
達到自定義Cliams。文章寫的很詳細,我這就不重複了,我實際試驗過,能夠成功。async
可是文章末尾的注意,很重要。ide
「那麼, 經過profileservice頒發的claims, 任意clients都能拿到」ui
說明這個優先級是很是高的,能夠覆蓋全部的行爲,固然咱們能夠在IProfileService
的實現上對權限進行進一步的設置,不過仍是挺麻煩的。參考實現,參考官方.net
做爲懶人,必然不想再費勁去折騰權限的問題,那麼是否有簡單點的辦法呢?
網上有一些問答說到了能夠經過設置Scopes來達到目的。不過過於久遠,IdentityServer4已經沒有這個獨立的類了,說是已經被ApiResource
取代了。
直覺上這個東西應該是指示要保護的API的相關內容的,好像和這個沒啥關係,不過也只能死馬當活馬醫了。修改config.cs,最終以下內容:
public static IEnumerable<ApiResource> Apis => new List<ApiResource> { new ApiResource("pls", new[]{ "role"}), }; public static IEnumerable<Client> Clients => new List<Client> { new Client { ClientId = "webClient", ClientSecrets = { new Secret("secret".Sha256()) }, AllowOfflineAccess = true, AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, // scopes that client has access to AllowedScopes = { "pls" } },
返回結果以下:
{ "nbf": 1587301799, "exp": 1587305399, "iss": "http://localhost:5000", "aud": "pls", "client_id": "webClient", "sub": "c6c18d4d-c28e-4de5-86dd-779121216204", "auth_time": 1587301799, "idp": "local", "role": "Administrator", "scope": [ "pls", "offline_access" ], "amr": [ "pwd" ] }
終於看見心心念唸的自定義Claim(role),能夠去訪問API了。
注意,在Client中也有個Claims,添加了role而且設置
AlwaysSendClientClaims
和AlwaysIncludeUserClaimsInIdToken
以後,會在token中添加client_roie
字段,這個是沒辦法用與受權的,能夠理解爲IdentityServer4直接指定了Client角色,並非Identity中的角色概念。
回過頭來仔細看官方的文檔,ApiResource中的UserClaims就是用來幹這個的,折騰了半天,不如當時仔細看看文檔了。