使用ResourceOwnerPassword流程来保护API
OAuth2.0中的ResourceOwnerPassword授权流程允许一个客户端发送username和password到token服务上面,以便获取一个代表用户的access token。
”规范“中建议只对受信任的客户端使用这种授权类型。通常情况来讲,在有用户交互的场景下,你应该优先使用OpenID Connect协议中的其中一个流程(有authorization code 、implicit、hybrid)来对用户进行认证,并获取access token。
话虽如此,这种授权类型引入了IdentityServer中的用户的概念,这也是我们要展现它的唯一原因。
添加用户
就像内存中的资源(或者叫做scopes)和客户端,也可以创建内存中的用户。
TestUser类代表了一个测试用户和它的一些声明(claim)。我们现在在Config类中创建一些用户:
using IdentityServer4.Test; public static List<TestUser> GetUsers() { return new List<TestUser> { new TestUser { SubjectId = "1", Username = "alice", Password = "password" }, new TestUser { SubjectId = "2", Username = "bob", Password = "password" } }; }
然后在ConfigureService方法中注入:
public void ConfigureServices(IServiceCollection services) { // configure identity server with in-memory stores, keys, clients and scopes services.AddIdentityServer() .AddDeveloperSigningCredential() .AddInMemoryApiResources(Config.GetApiResources()) .AddInMemoryClients(Config.GetClients()) .AddTestUsers(Config.GetUsers()); }
AddTestUsers扩展方法在后台做了这么几件事:
- 添加了对resource owner password这种授权类型的支持
- 添加了通常在login ui中使用的用户相关的服务。
- 在testuser上添加了profile service。
为resource owner password这种授权类型创建一个相应的客户端
如果你想要客户端对于这两种授权类型都支持,你可以在现有的客户端上面通过修改AllowedGrantTypes属性的值来添加对这种授权类型的支持。
通常情况下你只是想要创建一个单独的客户端作为resource owner password这种授权类型的场景下使用,在Config类的GetClients方法中添加以下的代码:
public static IEnumerable<Client> GetClients() { return new List<Client> { // other clients omitted... // resource owner password grant client new Client { ClientId = "ro.client", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "api1" } } }; }
使用password的授权类型来请求token
上面定义的那个客户端看起来和我们先前定义的client credentials客户端看起来很像。最主要的不同在于现在客户端会收集用户的密码,并在请求token的过程中将他连同其他东西一起发送到token service上面。
再次使用IdentityModel的TokenCLient来帮助我们实现这个请求:
// request token var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.client", "secret"); var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("alice", "password", "api1"); if (tokenResponse.IsError) { Console.WriteLine(tokenResponse.Error); return; } Console.WriteLine(tokenResponse.Json); Console.WriteLine(" ");
当你向API发送token的时候,你会发现一个非常小而又非常重要的改变(相对于clientcredential这种授权类型来说):access token现在包含了一个”sub“的claim,这个claim就是用的唯一标识(在Config类中定义的TestUser的subjectid属性就是这里的东西),这个不同会通过api的方法返回的json里面发现。我在这里展示1下,我通过postman:
首先是通过client credential这种授权获取的token来访问api的,获取的结果如下:
[ { "claimType": "nbf", "claimValue": "1532508154" }, { "claimType": "exp", "claimValue": "1532511754" }, { "claimType": "iss", "claimValue": "http://localhost:5000" }, { "claimType": "aud", "claimValue": "http://localhost:5000/resources" }, { "claimType": "aud", "claimValue": "api1" }, { "claimType": "client_id", "claimValue": "firstClient" }, { "claimType": "scope", "claimValue": "api1" } ]
看一看出没有sub这个声明。
然后使用resource owner password这个授权类型来搞到token,再用这个token访问一下api:
[ { "claimType": "nbf", "claimValue": "1532511508" }, { "claimType": "exp", "claimValue": "1532515108" }, { "claimType": "iss", "claimValue": "http://localhost:5000" }, { "claimType": "aud", "claimValue": "http://localhost:5000/resources" }, { "claimType": "aud", "claimValue": "api1" }, { "claimType": "client_id", "claimValue": "secondClient" }, { "claimType": "sub", "claimValue": "2" }, { "claimType": "auth_time", "claimValue": "1532511508" }, { "claimType": "idp", "claimValue": "local" }, { "claimType": "scope", "claimValue": "api1" }, { "claimType": "amr", "claimValue": "pwd" } ]