接上一篇: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
密码模式不多被用,由于须要知道用户的密码。我反正不肯意把本身的密码信息交给第三方用,下一篇讲的隐藏模式就解决了这个问题。