开发环境
系统版本:win10
.NET SDK: NET8
开发工具:vscode
参考引用:使用 dotnet user-jwts 管理开发中的 JSON Web 令牌
注意:以下示例中的端口、token等需替换成你的环境中的信息
创建项目
运行以下命令来创建一个空的 Web 项目,并添加 Microsoft.AspNetCore.Authentication.JwtBearer NuGet 包:
dotnet new web -o MyJWT | |
cd MyJWT | |
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer |
将 Program.cs 的内容替换为以下代码(略微改动):
using System.Security.Claims; | |
var builder = WebApplication.CreateBuilder(args); | |
builder.Services.AddAuthorization(); | |
// 默认的Scheme是Bearer | |
// builder.Services.AddAuthentication("Bearer").AddJwtBearer(); | |
builder.Services.AddAuthentication().AddJwtBearer(); | |
var app = builder.Build(); | |
app.UseAuthorization(); | |
app.MapGet("/", () => "Hello, World!"); | |
app.MapGet("/secret", (ClaimsPrincipal user) => $"Hello {user.Identity?.Name}. My secret") | |
.RequireAuthorization(); | |
app.Run(); |
运行项目并访问接口返回以下内容
PS D:\Learn\MyJWT> curl.exe -i http:///localhost:5276 | |
HTTP/1.1 200 OK | |
Content-Type: text/plain; charset=utf-8 | |
Date: Mon, 04 Dec 2023 00:43:03 GMT | |
Server: Kestrel | |
Transfer-Encoding: chunked | |
Hello, World! |
创建 JWT
PS D:\Learn\MyJWT> dotnet user-jwts create | |
New JWT saved with ID 'c28b968'. | |
Name: Lingpeng | |
Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkxpbmdwZW5nIiwic3ViIjoiTGluZ3BlbmciLCJqdGkiOiJjMjhiOTY4IiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6Mjk3NTQiLCJodHRwczovL2xvY2FsaG9zdDo0NDM2MCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTI3NiIsImh0dHBzOi8vbG9jYWxob3N0OjcyNTMiXSwibmJmIjoxNzAxNjQ5Nzk2LCJleHAiOjE3MDk1MTIxOTYsImlhdCI6MTcwMTY0OTc5NiwiaXNzIjoiZG90bmV0LXVzZXItand0cyJ9.l52s9_7oNjIKL96TysgdE0k970fUS9FoLTu2xRs-IPo |
这个命令做了3件事:
1、更新项目的 appsettings.Development.json
,添加了Authentication节点
2、更新项目的 MyJWT.csproj
,添加了UserSecretsId 配置
3、创建了机密文件 %APPDATA%\Microsoft\UserSecrets\<secrets_GUID>\user-jwts.json
与%APPDATA%\Microsoft\UserSecrets\<secrets_GUID>\secrets.json
,机密管理参考我们看下这两个机密文件 user-jwts.json
{ | |
"c28b968": { | |
"Id": "c28b968", | |
"Scheme": "Bearer", | |
"Name": "Lingpeng", | |
"Audience": "http://localhost:29754, https://localhost:44360, http://localhost:5276, https://localhost:7253", | |
"NotBefore": "2023-12-04T00:29:56+00:00", | |
"Expires": "2024-03-04T00:29:56+00:00", | |
"Issued": "2023-12-04T00:29:56+00:00", | |
"Token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkxpbmdwZW5nIiwic3ViIjoiTGluZ3BlbmciLCJqdGkiOiJjMjhiOTY4IiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6Mjk3NTQiLCJodHRwczovL2xvY2FsaG9zdDo0NDM2MCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTI3NiIsImh0dHBzOi8vbG9jYWxob3N0OjcyNTMiXSwibmJmIjoxNzAxNjQ5Nzk2LCJleHAiOjE3MDk1MTIxOTYsImlhdCI6MTcwMTY0OTc5NiwiaXNzIjoiZG90bmV0LXVzZXItand0cyJ9.l52s9_7oNjIKL96TysgdE0k970fUS9FoLTu2xRs-IPo", | |
"Scopes": [], | |
"Roles": [], | |
"CustomClaims": {} | |
} | |
} |
secrets.json
{ | |
"Authentication:Schemes:Bearer:SigningKeys": [ | |
{ | |
"Id": "ff20683d", | |
"Issuer": "dotnet-user-jwts", | |
"Value": "lDOFmIuEDelFKU0zAaLoT2qYOFDRZGDDTv5FyTa36V8=", | |
"Length": 32 | |
} | |
] | |
} |
测试JWT我们重新运行程序,用直接访问与携带token两种方式访问/secret接口
PS D:\Learn\MyJWT> curl.exe -i http://localhost:5276/secret | |
HTTP/1.1 401 Unauthorized | |
Content-Length: 0 | |
Date: Mon, 04 Dec 2023 00:43:25 GMT | |
Server: Kestrel | |
WWW-Authenticate: Bearer | |
PS D:\Learn\MyJWT> | |
PS D:\Learn\MyJWT> | |
PS D:\Learn\MyJWT> curl.exe -i -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6IkxpbmdwZW5nIiwic3ViIjoiTGluZ3BlbmciLCJqdGkiOiJjMjhiOTY4IiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6Mjk3NTQiLCJodHRwczovL2xvY2FsaG9zdDo0NDM2MCIsImh0dHA6Ly9sb2NhbGhvc3Q6NTI3NiIsImh0dHBzOi8vbG9jYWxob3N0OjcyNTMiXSwibmJmIjoxNzAxNjQ5Nzk2LCJleHAiOjE3MDk1MTIxOTYsImlhdCI6MTcwMTY0OTc5NiwiaXNzIjoiZG90bmV0LXVzZXItand0cyJ9.l52s9_7oNjIKL96TysgdE0k970fUS9FoLTu2xRs-IPo" http://localhost:5276/secret | |
HTTP/1.1 200 OK | |
Content-Type: text/plain; charset=utf-8 | |
Date: Mon, 04 Dec 2023 00:45:42 GMT | |
Server: Kestrel | |
Transfer-Encoding: chunked | |
Hello Lingpeng. My secret |
至此我们已经实现了JwtBearer的初步使用
一点点改动
示例采用了机密管理,我们也可以把机密文件中的内容迁移至项目中(推荐用机密管理),我们修改MyJWT.csproj
与appsettings.Development.json
如下
<Project Sdk="Microsoft.NET.Sdk.Web"> | |
<PropertyGroup> | |
<TargetFramework>net8.0</TargetFramework> | |
<Nullable>enable</Nullable> | |
<ImplicitUsings>enable</ImplicitUsings> | |
<!-- <UserSecretsId>88d7c163-def1-4747-b01f-cefed382beae</UserSecretsId> --> | |
</PropertyGroup> | |
<ItemGroup> | |
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.0" /> | |
</ItemGroup> | |
</Project> | |
{ | |
"Logging": { | |
"LogLevel": { | |
"Default": "Information", | |
"Microsoft.AspNetCore": "Warning" | |
} | |
}, | |
"Authentication": { | |
"Schemes": { | |
"Bearer": { | |
"ValidAudiences": [ | |
"http://localhost:29754", | |
"https://localhost:44360", | |
"http://localhost:5276", | |
"https://localhost:7253" | |
], | |
"ValidIssuer": "dotnet-user-jwts", | |
"SigningKeys": [ | |
{ | |
"Id": "ff20683d", | |
"Issuer": "dotnet-user-jwts", | |
"Value": "lDOFmIuEDelFKU0zAaLoT2qYOFDRZGDDTv5FyTa36V8=", | |
"Length": 32 | |
} | |
] | |
} | |
} | |
} | |
} |
修改完成后实现相同的功能
JWT Token生成示例
app.MapGet("/login", (string UserName, string Password, [FromServices] IOptionsMonitor<JwtBearerOptions> optionsMonitor) => | |
{ | |
// 1. 密码验证 | |
// TODO | |
// 2. 生成 | |
var parameters = optionsMonitor.Get(JwtBearerDefaults.AuthenticationScheme).TokenValidationParameters; | |
var signingKey = parameters.IssuerSigningKeys.First(); | |
var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256Signature); | |
var header = new JwtHeader(signingCredentials); | |
var payload = new JwtPayload { | |
{ JwtRegisteredClaimNames.UniqueName, UserName }, | |
{ JwtRegisteredClaimNames.Iss, parameters.ValidIssuers.First() }, | |
{ JwtRegisteredClaimNames.Aud, parameters.ValidAudiences }, | |
{ JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds() }, | |
{ JwtRegisteredClaimNames.Nbf, DateTimeOffset.UtcNow.ToUnixTimeSeconds() }, | |
{ JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMinutes(30).ToUnixTimeSeconds() } | |
}; | |
var jwtSecurityToken = new JwtSecurityToken(header, payload); | |
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler(); | |
var token = jwtSecurityTokenHandler.WriteToken(jwtSecurityToken); | |
return token; | |
}); |
进行一下验证
PS D:\Learn\MyJWT> curl.exe -i "http://localhost:5276/login?username=admin&password=1111" | |
HTTP/1.1 200 OK | |
Content-Type: text/plain; charset=utf-8 | |
Date: Mon, 04 Dec 2023 05:03:36 GMT | |
Server: Kestrel | |
Transfer-Encoding: chunked | |
eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwiaXNzIjoiZG90bmV0LXVzZXItand0cyIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjI5NzU0IiwiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNjAiLCJodHRwOi8vbG9jYWxob3N0OjUyNzYiLCJodHRwczovL2xvY2FsaG9zdDo3MjUzIl0sImlhdCI6MTcwMTY2NjIxNiwibmJmIjoxNzAxNjY2MjE2LCJleHAiOjE3MDE2NjgwMTZ9.P9t7vIFfM7cddRPs4OQUTVVdo57nWTLt_ea2UynGUpo | |
PS D:\Learn\MyJWT> | |
PS D:\Learn\MyJWT> | |
PS D:\Learn\MyJWT> curl.exe -i -H "Authorization: Bearer eyJhbGciOiJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNobWFjLXNoYTI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6ImFkbWluIiwiaXNzIjoiZG90bmV0LXVzZXItand0cyIsImF1ZCI6WyJodHRwOi8vbG9jYWxob3N0OjI5NzU0IiwiaHR0cHM6Ly9sb2NhbGhvc3Q6NDQzNjAiLCJodHRwOi8vbG9jYWxob3N0OjUyNzYiLCJodHRwczovL2xvY2FsaG9zdDo3MjUzIl0sImlhdCI6MTcwMTY2NjIxNiwibmJmIjoxNzAxNjY2MjE2LCJleHAiOjE3MDE2NjgwMTZ9.P9t7vIFfM7cddRPs4OQUTVVdo57nWTLt_ea2UynGUpo" http://localhost:5276/secret | |
HTTP/1.1 200 OK | |
Content-Type: text/plain; charset=utf-8 | |
Date: Mon, 04 Dec 2023 05:03:50 GMT | |
Server: Kestrel | |
Transfer-Encoding: chunked | |
Hello admin. My secret |