接上一篇:IdentityServer4 初識,上一篇介紹了客戶端模式保護API訪問。這一篇講IdentityServer4 使用用戶名和密碼模式保護API訪問。 添加用戶:要用到用戶名稱密碼當然得添加用戶,在IdentityServer項目的Config類中的新增一個方法,GetUsers。返回一 ...
接上一篇:IdentityServer4 初識,上一篇介紹了客戶端模式保護API訪問。這一篇講IdentityServer4 使用用戶名和密碼模式保護API訪問。
- 添加用戶:要用到用戶名稱密碼當然得添加用戶,在IdentityServer項目的Config類中的新增一個方法,GetUsers。返回一個TestUser的集合。
public static List<TestUser> GetUsers() { return new List<TestUser>() { new TestUser() { //用戶名 Username="apiUser", //密碼 Password="apiUserPassword", //用戶Id SubjectId="0" } }; }
添加好用戶還需要要將用戶註冊到IdentityServer4,修改IdentityServer項目的Startup類ConfigureServices方法
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); //添加IdentityServer var builder = services.AddIdentityServer() //身份信息授權資源 .AddInMemoryIdentityResources(Config.GetIdentityResources()) //API訪問授權資源 .AddInMemoryApiResources(Config.GetApis()) //客戶端 .AddInMemoryClients(Config.GetClients()) //添加用戶 .AddTestUsers(Config.GetUsers()); if (Environment.IsDevelopment()) { builder.AddDeveloperSigningCredential(); } else { throw new Exception("need to configure key material"); } }
- 添加一個客戶端,用於用戶名和密碼模式的訪問。客戶端(Client)定義里有一個AllowedGrantTypes的屬性,這個屬性決定了Client可以被那種模式被訪問,GrantTypes.ClientCredentials為客戶端憑證模式,GrantTypes.ResourceOwnerPassword為用戶名密碼模式。上一節添加的Client是客戶端憑證模式,所以還需要添加一個Client用於支持用戶名密碼模式。
public static IEnumerable<Client> GetClients() { return new Client[] { new Client() { //客戶端Id ClientId="apiClientCd", //客戶端密碼 ClientSecrets={new Secret("apiSecret".Sha256()) }, //客戶端授權類型,ClientCredentials:客戶端憑證方式 AllowedGrantTypes=GrantTypes.ClientCredentials, //允許訪問的資源 AllowedScopes={ "secretapi" } }, new Client() { //客戶端Id ClientId="apiClientPassword", //客戶端密碼 ClientSecrets={new Secret("apiSecret".Sha256()) }, //客戶端授權類型,ClientCredentials:客戶端憑證方式 AllowedGrantTypes=GrantTypes.ResourceOwnerPassword, //允許訪問的資源 AllowedScopes={ "secretapi" } } }; }
至此,服務端工作完成。轉到IentityApi項目。
- 在後臺獲取Token:IdentityModel為支持用戶名密碼模式,對HttpClient做了一個擴展方法:RequestPasswordTokenAsync,修改一下IdentityController控制器的getToken介面,讓它支持用戶名密碼模式獲取Token
[HttpGet] [Route("api/getToken")] public async Task<object> GetCdTokenAsync(string type,bool? request) { var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); TokenResponse resp = null; switch (type) { case "cd": resp = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientCd", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi" }); break; case "pass": resp = await client.RequestPasswordTokenAsync(new PasswordTokenRequest() { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientPassword", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi", //用戶名 UserName = "apiUser", //密碼 Password = "apiUserPassword" }); break; }
//如果request為true,直接利用token訪問被保護的api if (request??false&&null!=resp) {
//添加Bearer認證頭 client.SetBearerToken(resp.AccessToken); var reslut =await client.GetStringAsync("https://localhost:5001/api/identity"); JArray json = JArray.Parse(reslut); return json; } return resp?.Json; }
- 同樣,也可以通過HTTP請求獲取
獲取到Token後,訪問受保護的API和通過客戶端模式一樣。
到目前為止,昨們還沒有搞清這兩個模式有什麼區別,如果僅僅是為了能訪問這個API,那加不加用戶名和密碼有什麼區別呢。昨們對比下這兩種模式取得Token後訪問api返回的數據,可以發現用戶名密碼模式返回的Claim的數量要多一些。Claim是什麼呢,簡爾言之,是請求方附帶在Token中的一些信息。但客戶端模式不涉及到用戶信息,所以返回的Claim數量會少一些。在IdentityServer4中,TestUser有一個Claims屬性,允許自已添加Claim,有一個ClaimTypes枚舉列出了可以直接添加的Claim。添加一個ClaimTypes.Role試試。
IdentityServer.Config.GetUsers
public static List<TestUser> GetUsers() { return new List<TestUser>() { new TestUser() { //用戶名 Username="apiUser", //密碼 Password="apiUserPassword", //用戶Id SubjectId="0", Claims=new List<Claim>(){ new Claim(ClaimTypes.Role,"admin") } } }; }
這時如果啟動兩個項目,採用用戶密碼和密碼模式獲取Token訪問Api,返回的值依然是沒有role:admin的Claim的。這時又要用到ApiResouce,ApiResouce的構造函數有一個重載支持傳進一個Claim集合,用於允許該Api資源可以攜帶那些Claim。
IdentityServer.Config.GetApis
public static IEnumerable<ApiResource> GetApis() { return new ApiResource[] { //secretapi:標識名稱,Secret Api:顯示名稱,可以自定義 new ApiResource("secretapi","Secret Api",new List<string>(){ ClaimTypes.Role}) }; }
現在可以啟動IdentityApi和IdentityServer兩個項目測試一下,可以發現已經可以返回role這個claim了。
Role(角色)這個Claim很有用,可以用來做簡單的許可權管理。
首先修改下被保護Api的,使其支持Role驗證
IdentityApi.Controllers.IdentityController.GetUserClaims
[HttpGet] [Route("api/identity")] [Microsoft.AspNetCore.Authorization.Authorize(Roles ="admin")] public object GetUserClaims() { return User.Claims.Select(r => new { r.Type, r.Value }); }
然後在IdentityServer端添加一個來賓角色用戶
IdentityServer.Config.GetUsers
public static List<TestUser> GetUsers() { return new List<TestUser>() { new TestUser() { //用戶名 Username="apiUser", //密碼 Password="apiUserPassword", //用戶Id SubjectId="0", Claims=new List<Claim>(){ new Claim(ClaimTypes.Role,"admin") } }, new TestUser() { //用戶名 Username="apiUserGuest", //密碼 Password="apiUserPassword", //用戶Id SubjectId="1", Claims=new List<Claim>(){ new Claim(ClaimTypes.Role,"guest") } } }; }
再回到IdentityApi,修改下測試介面,把用戶名和密碼參數化,方便調試
IdentityApi.Controllers.IdentityController.getCdTokenAsync
[HttpGet] [Route("api/getToken")] public async Task<object> GetCdTokenAsync(string type,bool? request,string username,string password) { var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); TokenResponse resp = null; switch (type) { case "cd": resp = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientCd", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi" }); break; case "pass": resp = await client.RequestPasswordTokenAsync(new PasswordTokenRequest() { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientPassword", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi", //用戶名 UserName = username, //密碼 Password = password }); break; } if (request??false&&null!=resp) { //添加Bearer認證頭 client.SetBearerToken(resp.AccessToken); var reslut =await client.GetStringAsync("https://localhost:5001/api/identity"); JArray json = JArray.Parse(reslut); return json; } return resp?.Json; }
分別用apiUser和apiUserGuest訪問
apiUserGuest訪問被拒絕。
上邊是添加ClaimTypes枚舉里定義好的Claim,但如果要定義的Claim不在Claim枚舉里應該怎麼辦呢,比如我想所有用戶都有一個項目編號,要添加一個名為prog的Claim。
先在ApiResouce里允許攜帶名為prog.Claim
IdentityServer.Config.GetApis
public static IEnumerable<ApiResource> GetApis() { return new ApiResource[] { //secretapi:標識名稱,Secret Api:顯示名稱,可以自定義 new ApiResource("secretapi","Secret Api",new List<string>(){ ClaimTypes.Role,ClaimTypes.Name,"prog"}) }; }
在用戶定義的Claims屬性里添加prog信息
IdentityServer.Config.
GetUsers
public static List<TestUser> GetUsers() { return new List<TestUser>() { new TestUser() { //用戶名 Username="apiUser", //密碼 Password="apiUserPassword", //用戶Id SubjectId="0", Claims=new List<Claim>(){ new Claim(ClaimTypes.Role,"admin"), new Claim("prog","正式項目"), } }, new TestUser() { //用戶名 Username="apiUserGuest", //密碼 Password="apiUserPassword", //用戶Id SubjectId="1", Claims=new List<Claim>(){ new Claim(ClaimTypes.Role,"guest"), new Claim("prog","測試項目"), } } }; }
使用apiUser訪問
用戶密碼和密碼模式就講到這,兩種模式講完,Config類里的IdentityResource還一點都沒用上,它倒底有什麼用呢,下一節講另外二種模式:授權碼模式和隱藏模式會用到它。