接上一篇:IdentityServer4 實現OAuth2.0四種模式之客戶端模式,這一篇講IdentityServer4 使用密碼模式保護API訪問。html
要用到用戶名稱密碼固然得添加用戶,在IdentityServer項目的Config類中的新增一個方法,GetUsers。返回一個TestUser的集合。json
public static List<TestUser> GetUsers() { return new List<TestUser>() { new TestUser() { //用戶名 Username="apiUser", //密碼 Password="apiUserPassword", //用戶Id SubjectId="0" } }; }
添加好用戶還須要要將用戶註冊到IdentityServer4,修改IdentityServer項目的Startup類ConfigureServices方法api
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用於支持用戶名密碼模式。async
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" } } }; }
修改GetData控制器,使其支持密碼模式訪問ide
public async Task<IActionResult> GetData(string type) { type = type ?? "client"; var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); if (disco.IsError) return new JsonResult(new { err=disco.Error}); TokenResponse token = null; switch (type) { case "client": token = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest() { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientCd", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi" }); break; case "password": token = await client.RequestPasswordTokenAsync(new PasswordTokenRequest() { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientPassword", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi", UserName = "apiUser", Password = "apiUserPassword" }); break; } if (token.IsError) return new JsonResult(new { err = token.Error }); client.SetBearerToken(token.AccessToken); string data = await client.GetStringAsync("https://localhost:5001/api/identity"); JArray json = JArray.Parse(data); return new JsonResult(json); }
運行三個項目後訪問:https://localhost:5002/home/getdata?type=password函數
獲取access_token測試
獲取到Token後,訪問受保護的API和經過客戶端模式同樣。ui
到目前爲止,昨們尚未搞清這兩個模式有什麼區別,若是僅僅是爲了能訪問這個API,那加不加用戶名和密碼有什麼區別呢。昨們對比下這兩種模式取得Token後訪問api返回的數據,能夠發現用戶名密碼模式返回的Claim的數量要多一些。Claim是什麼呢,簡爾言之,是請求方附帶在Token中的一些信息。但客戶端模式不涉及到用戶信息,因此返回的Claim數量會少一些。在IdentityServer4中,TestUser有一個Claims屬性,容許自已添加Claim,有一個ClaimTypes枚舉列出了能夠直接添加的Claim。添加一個ClaimTypes.Role試試。調試
IdentityServer.Config.GetUsershtm
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}) }; }
如今能夠啓動項目測試一下,能夠發現已經能夠返回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") } } }; }
再回到IdentityMvc項目,修改下獲取數據的測試接口GetData,把用戶名和密碼參數化,方便調試
IdentityMvc.HomeContoller.GetData
public async Task<IActionResult> GetData(string type,string userName,string password) { type = type ?? "client"; var client = new HttpClient(); var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000"); if (disco.IsError) return new JsonResult(new { err=disco.Error}); TokenResponse token = null; switch (type) { case "client": token = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest() { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientCd", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi" }); break; case "password": token = await client.RequestPasswordTokenAsync(new PasswordTokenRequest() { //獲取Token的地址 Address = disco.TokenEndpoint, //客戶端Id ClientId = "apiClientPassword", //客戶端密碼 ClientSecret = "apiSecret", //要訪問的api資源 Scope = "secretapi", UserName =userName, Password = password }); break; } if (token.IsError) return new JsonResult(new { err = token.Error }); client.SetBearerToken(token.AccessToken); string data = await client.GetStringAsync("https://localhost:5001/api/identity"); JArray json = JArray.Parse(data); return new JsonResult(json); }
分別用apiUser和apiUserGuest訪問,用apiUserGuest訪問時請求被拒絕
https://localhost:5002/home/getdata?type=password&userName=apiUserGuest&password=apiUserPassword
上邊是添加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訪問
https://localhost:5002/home/getdata?type=password&userName=apiUser&password=apiUserPassword
密碼模式不多被用,由於須要知道用戶的密碼。我反正不肯意把本身的密碼信息交給第三方用,下一篇講的隱藏模式就解決了這個問題。