IdentityServer4
IdentityServer4 是用于 ASP.NET Core 的 OpenID Connect 和 OAuth 2.0 框架
它为您的所有应用程序提供单点登录的功能。单个中央服务器,可以验证您的客户端应用程序,为各种类型的客户端颁发 API 的访问令牌,您可以根据自己的要求配置选项。
身份服务器将为您提供安全登录和 API 访问保护——您将通过安全令牌访问 api 资源,身份服务器建立在 openID connect 和 oauth2.0 之上,它将为您管理令牌。
不要忘记,所有登录代码和逻辑(当今几乎所有网络应用程序都需要)已经过数百次测试并随着时间的推移而改进。这使得它几乎没有错误,并且比自己编写相同的东西更可靠!
一、Terminology
IdentityServer:
IdentityServer 是一个 OpenID Connect 提供者——它实现了 OpenID Connect 和 OAuth 2.0 协议。
IdentityServer功能包括:
- 保护您的资源
- 使用本地帐户存储或通过外部身份提供者对用户进行身份验证
- 提供会话管理和单点登录
- 管理和验证客户端
- 向客户颁发身份和访问令牌
- 验证令牌
User:
使用注册客户端访问资源的人
Client:
从 IdentityServer 请求令牌的软件——用于验证用户(请求身份令牌)或访问资源(请求访问令牌)。客户端必须先向 IdentityServer 注册,然后才能请求令牌。一般是Web应用程序、移动程序或桌面应用、SPA、服务器进程等
Resources:
被IdentityServer保护的用户身份数据或者API,每个Resources都有唯一的名称,指定能访问那些资源
IdentityToken:
身份验证过程的结果,至少有用户的标识符、用户如何何时尽心身份验证的信息,还可以携带身份数据\
Account Token:
允许访问API资源,客户端请求访问令牌并转发到API。访问令牌包含有关客户端和用户的信息。API使用该信息授权访问其数据。
二、
模拟一种常见的IdentityServer方案:
使用IdentityServer保护API,定义一个IdentityServer、API、Client,Client从身份服务器请求访问令牌,然后使用访问令牌获取对API的访问权限。
这里创建一个空白解决方案,创建三个项目:IdentityServer、API、Client(模拟用户访问情况),使用的是 .NET6
IdentityServer项目
创建
新建项目——选择WebApi,命名:Sample.IdentityServer,删除默认生成的Model和Controller文件夹,成为以下模样
安装Nuget包:IdentityServer4
配置
创建静态配置文件Config
定义API Scope:
具体有哪些构造函数可以进入ApiScope类进行查看
public static IEnumerable ApiScopes =>
new List {
//构造函数方式(name,displayName)
new ApiScope("api1", "My API"),
new ApiScope
{
//标志api范围的名称
Name="sample_api",
//显示名称,对api范围进行描述
DisplayName = "Sample API"
}
};
定义Client:
定义可以访问API的客户端应用程序
//客户端应用
public static IEnumerable Clients => new[]
{
new Client
{
//客户端id
ClientId = "sample_client",
//客户端密钥
ClientSecrets =
{
//可以有多个 需要sha256加密
new Secret("sample_client_secret".Sha256())
},
//客户端凭据 指定授权类型
AllowedGrantTypes = GrantTypes.ClientCredentials,
//允许的api范围
AllowedScopes = { "sample_api" }
},
new Client
{
ClientId = "sample_pass_client",
ClientSecrets =
{
new Secret("sample_client_secret".Sha256())
},
//组合多个授权类型
AllowedGrantTypes = { GrantType.ClientCredentials, GrantType.ResourceOwnerPassword },
//多个允许的范围
AllowedScopes = { "sample_api" , "api1" }
}
};
定义Users:
//资源拥有者凭据授权
public static List Users => new List {
new TestUser
{
SubjectId ="1",
Username ="admin",
Password ="123"
}
};
配置IdentityServer:
在Program中添加代码就这样,首次启动时,IdentityServer会创建一个开发者签名密钥,tempkey.jwk,启动以后查看发现文档https://localhost:7199/.well-known/openid-configuration ,换成你自己的端口,Client和API要使用发现文档里的配置数据
using Sample.IdentityServer;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddIdentityServer()//依赖注入IdentityServer
.AddDeveloperSigningCredential()//添加签名凭据
.AddInMemoryApiScopes(Config.ApiScopes)//使用内存中的配置
.AddInMemoryClients(Config.Clients)//使用内存中的配置
.AddTestUsers(Config.Users);
var app = builder.Build();
app.UseIdentityServer();
app.Run();
\
Api项目
创建
在同一个解决方案中创建新的WebApi,删除默认创建的Model文件,创建Controller文件IdentityController
用来测试授权要求,需要添加Nuget包:Microsoft.AspNetCore.Authentication.JwtBearer
[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
配置
在Program中添加配置
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//注册认证服务
//Bearer认证方案
builder.Services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
//配置委托
//服务地址
options.Authority = "https://localhost:7199";
//验证参数 默认不需要吧
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiScope", builder =>
{
//是否通过前面的认证
builder.RequireAuthenticatedUser();
//鉴定api范围
builder.RequireClaim("scope", "sample_api");
});
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
//将身份验证中间件添加到管道中,以便在每次调用主机时自动执行身份验证。
app.UseAuthentication();
//添加授权中间件以确保匿名客户端无法访问我们的 API 端点。
app.UseAuthorization();
//所有控制器都要验证
app.MapControllers().RequireAuthorization("Apiscope");
app.Run();
Client项目
请求访问令牌的客户端,使用令牌访问API\
创建
创建个控制台程序,添加NuGet包:IdentityModel\
创建静态类ClientTest
添加ClientGet方法测试ClientCredentials请求
// 客户端模式请求测试
public static async Task ClientGet()
{
//通过IdentityServer获取token
var client = new HttpClient();
//获取发现文档
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:7199");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
var tokenResponse = await client.RequestClientCredentialsTokenAsync(
new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "sample_client",
ClientSecret = "sample_client_secret"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
//根据token调用api
var apiClient = new HttpClient();
//设置请求头
apiClient.SetBearerToken(tokenResponse.AccessToken);
//请求方法
var response = await apiClient.GetAsync("https://localhost:7078/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
return;
}
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
添加PassGet方法测试ResourceOwnerPassword请求
// 资源拥有者凭据授权请求测试
public static async Task PassGet()
{
//通过IdentityServer获取token
var client = new HttpClient();
//获取发现文档
var disco = await client.GetDiscoveryDocumentAsync("https://localhost:7199");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
var tokenResponse = await client.RequestPasswordTokenAsync(
new PasswordTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "sample_pass_client",
ClientSecret = "sample_client_secret",
UserName = "admin",
Password = "123"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
return;
}
Console.WriteLine(tokenResponse.Json);
//根据token调用api
var apiClient = new HttpClient();
//设置请求头
apiClient.SetBearerToken(tokenResponse.AccessToken);
//请求方法
var response = await apiClient.GetAsync("https://localhost:7078/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
return;
}
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
Program代码
// See https://aka.ms/new-console-template for more information
using Sample.Client;
Console.WriteLine("Hello, World!");
await ClientTest.ClientGet();
await ClientTest.PassGet();
Console.ReadLine();
启动
设置多个启动项目,邮件解决方案点击属性,启动完三个
设置运行API不默认启动浏览器,打开API项目的Properties的launchSettings.json将launchBrowser属性设置为false