IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可)
IdentityServer4之Resource Owner Password Credentials(资源拥有者密码凭据许可)
前言
IdentityServer4简称IDS4,是为ASP.NET Core系列量身打造的一款基于 OpenID Connect 和 OAuth 2.0 认证框架。
允许一个客户端发送用户名和密码到令牌服务并获得一个表示该用户的访问令牌,就是我们通常所说的access_token。
该规范建议仅对“受信任”应用程序使用资源所有者密码授权。 一般来说,当您要验证用户并请求访问令牌时,通常使用交互式OpenID Connect流会更好,在IdentityServer中可以快速引入用户。
环境
Visual Studio 2019 + .net 5.0
IdentityServer4 (4.0.0)
Microsoft.AspNetCore.Authentication.JwtBearer (5.0.0)
IdentityModel (6.0.0)
实现步骤
创建IDS4项目
dotnet CLI快速安装模板Identityserver模板
dotnet new -i IdentityServer4.Templates
创建IdentityServery 项目
dotnet new is4empty -n IdentityServer_ResourceOwnerPasswordCredentials
注意项目名不可以取IdentityServery 4,不然会和IdentityServery 4.Dll引用重名。
引用IdentityServer4依赖
这里使用4.0.0版本,如图:
修改端口
此模板中使用的协议是https,在 Kestrel 上运行时端口设置为 5001 或在 IISExpress 上运行时设置为随机端口。可以在Properties\launchSettings.json文件中更改它。
{
"profiles": {
? "SelfHost": {
? "commandName": "Project",
? "launchBrowser": true,
? "environmentVariables": {
? "ASPNETCORE_ENVIRONMENT": "Development"
? },
? "applicationUrl": "https://localhost:5001;http://localhost:5000"
? }
}
}
这里修改为:
"applicationUrl": "https://localhost:5001;http://localhost:5000"
定义API接口的活动范围****API Scope
API 是系统中要保护的资源。资源定义可以通过多种方式加载。模板已经创建了 Config.cs。打开它,将代码更新为如下所示:
public static IEnumerable ApiScopes =>
? new ApiScope[]
? { new ApiScope("api1", "My API")};
添加用户
就像基于内存存储的资源(即范围 Scopes)和客户端一样,对于用户也可以这样做。
TestUser类型表示一个测试用户及其身份信息。 Config.cs文件中添加以下代码以创建用户:
?
public static List
? {
? return new List
? {
? new TestUser
? {
? SubjectId = "1",
? Username = "alice",
? Password = "password"
? },
? new TestUser
? {
? SubjectId = "2",
? Username = "bob",
? Password = "password"
? },
? new TestUser
? {
? SubjectId = "3",
? Username = "yak",
? Password = "yakpassword"
? }
? };
? }
配置身份服务器
在Startup文件中配置身份服务器,加载资源和客户端,然后将测试用户注册到 IdentityServer中。代码如下:
public void ConfigureServices(IServiceCollection services)
? {
? var builder = services.AddIdentityServer()
? .AddInMemoryIdentityResources(Config.GetIdentityResources())
? .AddInMemoryApiScopes(Config.ApiScopes)
? .AddInMemoryClients(Config.GetClients())
? .AddTestUsers(Config.GetUsers());
? builder.AddDeveloperSigningCredential();
? }
AddTestUsers扩展方法在背后做了以下几件事:
(1)为资源所有者密码授权添加支持
(2)添加对用户相关服务的支持,这服务通常为登录 UI 所使用
(3)为基于测试用户的身份信息服务添加支持
添加客户端****定义
可以通过修改AllowedGrantTypes属性简单地添加对已有客户端授权类型的支持。
通常要为资源所有者用例创建独立的客户端,添加以下代码到你配置中的客户端定义中
public static IEnumerable
? {
? return new List
? {
? // resource owner password grant client
? new Client
? {
? ClientId = "yakclient",
? AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
? ClientSecrets =
? {
? new Secret("yaksecret".Sha256())
? },
? AllowedScopes = { "api1", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile }//需要额外添加
? }
? };
? }
项目生成后会生成jwk加密文件,如图:
使用Postman测试工具测试
调用接口后返回access_token,测试结果如下
grant_type | password | ||
client_id | yakclient | ||
client_secret | yaksecret | ||
username | yak | ||
password | yakpassword |
新建WebAPI接口
新建接口,命名为Api,同时引用依赖:
Microsoft.AspNetCore.Authentication.JwtBearer.dll
注册认证相关组件和配置
在Startup.cs文件中修改ConfigureServices方法,代码如下:
public void ConfigureServices(IServiceCollection services)
? {
? services.AddControllers();
? // 注册认证相关组件和配置defaultScheme为Bearer
? services.AddAuthentication("Bearer")
? .AddJwtBearer("Bearer", options =>
? {
? options.Authority = "http://localhost:5000";
? // 在验证token时,不验证Audience
? options.TokenValidationParameters = new TokenValidationParameters
? {
? ValidateAudience = false
? };
? options.RequireHttpsMetadata = false; // 不适用Https
? });
? services.AddAuthorization(options =>
? {
? options.AddPolicy("api1Scope", policy =>
? {
? policy.RequireAuthenticatedUser();
? policy.RequireClaim("scope", "api1");
? });
? });
? }
注意Configure方法中添加认证和授权
app.UseAuthentication();
app.UseAuthorization();
新建测试客户端
新建一个WinForm客户端,添加按钮及引用IdentityModel依赖,这里使用6.0.0版本。
按钮事件代码如下:
private void button1_Click(object sender, EventArgs e)
? {
? Task.FromResult(MainAsync());
? }
? private async Task MainAsync()
? {
? // discover endpoints from metadata
? var client = new HttpClient();
? var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000");
? if (disco.IsError)
? {
? textBox1.Text = disco.Error;
? return;
? }
? textBox2.Text = disco.TokenEndpoint + "\r\n";
? // request token
? var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest
? {
? Address = disco.TokenEndpoint,
? ClientId = "yakclient",
? ClientSecret = "yaksecret",
? UserName = "yak",
? Password = "yakpassword",
? Scope = "api1"
? });
? if (tokenResponse.IsError)
? {
? textBox1.Text = tokenResponse.Error;
? return;
? }
? textBox2.Text += tokenResponse.Json.ToString();
? // call api
? var apiClient = new HttpClient();
? apiClient.SetBearerToken(tokenResponse.AccessToken);
? var response = await apiClient.GetAsync("http://localhost:4571/weatherforecast");
? if (!response.IsSuccessStatusCode)
? {
? textBox1.Text += response.StatusCode.ToString();
? }
? else
? {
? var content = response.Content.ReadAsStringAsync().Result;
? textBox1.Text += content;
? }
? }
启动程序
启动多个程序项目,注意IdentityServer_ResourceOwnerPasswordCredentials项目先启动。
测试结果如下
使用Postman测试如下:
总结
本想写简单点,可是不知不觉写了好多,因为在做的过程中可能很小的细节就可能导致项目运行不了,为了方便大家了解,还是尽可能详细吧!读者照着步骤做,印象会更深。
鸣谢
https://www.cnblogs.com/stulzq/p/7509648.html#top
http://www.identityserver.com.cn/Home/Detail/shiyongmimabaohuapi
https://identityserver4.readthedocs.io/en/latest/endpoints/authorize.html
源码
https://github.com/yandaniugithub/NETCore