JWT 身份验证与授权实战

一、前言:给 API 装个"门禁卡"

在前后端分离的架构中,JWT (JSON Web Token) 是目前最流行的认证方案。它就像一张"电子门禁卡"。用户登录成功后,服务器发给他一张卡(Token),以后每次请求都要带着这张卡,服务器只需验证卡的真伪,而不需要每次都去查数据库。

二、JWT 到底是什么?

刚子不喜欢背书,你只需要理解三个核心点:

它由三部分组成(用 . 分隔):

  1. Header(头) :说明这是什么卡,用什么加密算法。
  2. Payload(载荷)重点 。里面存着用户信息(如用户ID、用户名、角色)。注意:这里的数据只是 Base64 编码,不要存密码
  3. Signature(签名)核心安全层。服务器用只有自己知道的密钥,对前两部分进行签名。黑客如果篡改了 Payload,签名就对不上了,服务器会直接拒绝。

三、实战准备:安装 NuGet 包

在项目中安装以下 NuGet 包:

csharp 复制代码
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

这会引入处理 JWT 验证的核心中间件。

四、第一步:打造"发卡处"

用户登录时,我们需要核实账号密码,然后生成 Token 发给他。为了演示方便,我们假设有一个模拟的用户库。

4.1 定义配置模型和模拟用户

Program.cs 顶部定义模型:

csharp 复制代码
// 定义用户模型
public class User
{
    public string Username { get; set; }
    public string Password { get; set; } // 实际项目中应存储哈希值
    public string Role { get; set; } // 角色:Admin, User
}

// 模拟数据库用户
public static class UserStore
{
    public static List<User> Users = new List<User>
    {
        new User { Username = "admin", Password = "123456", Role = "Admin" },
        new User { Username = "gangzi", Password = "123456", Role = "User" }
    };
}

4.2 编写登录接口

这个接口负责"发卡"。

csharp 复制代码
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

// ... builder 配置 ...

app.MapPost("/login", (User login) =>
{
    // 1. 验证账号密码
    var user = UserStore.Users.FirstOrDefault(u => u.Username == login.Username && u.Password == login.Password);
    if (user == null)
    {
        return Results.Unauthorized(); // 401 未授权
    }

    // 2. 创建 Claims(声明) - 也就是 Payload 里要存的数据
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, user.Username),
        new Claim(ClaimTypes.Role, user.Role), // 存入角色,用于授权
        new Claim("MyCustomClaim", "SomeData") // 也可以存自定义数据
    };

    // 3. 生成签名密钥(实际项目中应从 appsettings.json 读取)
    // 密钥至少要 16 个字符
    var secretKey = "This_Is_A_Very_Secret_Key_For_JWT_2026!";
    var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey));
    var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

    // 4. 生成 Token
    var token = new JwtSecurityToken(
        issuer: "MyTodoApp", // 签发者
        audience: "MyTodoAppUsers", // 接收者
        claims: claims,
        expires: DateTime.Now.AddHours(1), // 过期时间:1小时
        signingCredentials: creds
    );

    var tokenString = new JwtSecurityTokenHandler().WriteToken(token);

    return Results.Ok(new { Token = tokenString });
});

刚子敲黑板 : 这里的 secretKey 是服务器的"底牌"。绝对不能泄露!如果黑客拿到了这个 Key,他就能伪造任意用户的 Token。在生产环境中,一定要放在 appsettings.json 或环境变量里,并且长度要足够长。

五、第二步:配置"安检门"

有了发卡的逻辑,还需要配置中间件,让系统自动验证每个请求带来的 Token。

5.1 注册认证与授权服务

Program.csbuilder.Services 部分添加:

ini 复制代码
// 注册认证服务
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    // 配置 Token 验证参数
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        
        ValidIssuer = "MyTodoApp",
        ValidAudience = "MyTodoAppUsers",
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("This_Is_A_Very_Secret_Key_For_JWT_2026!"))
    };
});

// 注册授权服务
builder.Services.AddAuthorization();

5.2 启用中间件

app.Build() 之后,确保顺序正确:UseAuthentication 必须在 UseAuthorization 之前。

scss 复制代码
var app = builder.Build();

app.UseAuthentication(); // 开启认证:识别身份
app.UseAuthorization();  // 开启授权:检查权限

// ... 其他中间件 ...

六、第三步:保护你的 API

现在安检门装好了,我们给之前的 Todo 接口加上"锁"。

6.1 普通保护:登录才能访问

在 Minimal API 中,使用 RequireAuthorization() 扩展方法。

dart 复制代码
// 只有带了有效 Token 才能访问
app.MapGet("/todos", async (AppDbContext db) =>
{
    return await db.Todos.ToListAsync();
}).RequireAuthorization();

6.2 角色保护:管理员才能删除

假设删除操作只有 "Admin" 角色才能执行。

dart 复制代码
app.MapDelete("/todos/{id}", async (int id, AppDbContext db) =>
{
    // ... 删除逻辑 ...
    return Results.NoContent();
}).RequireAuthorization("Admin"); // 这是错误的写法!

修正:Minimal API 的角色授权需要配合策略,或者简写为:

dart 复制代码
// 正确写法:定义一个策略,或者使用 Claims
app.MapDelete("/todos/{id}", async (int id, AppDbContext db) =>
{
    // ... 删除逻辑 ...
}).RequireAuthorization(policy => policy.RequireRole("Admin"));

七、实战测试:完整流程演示

让我们用 Postman 或 Swagger 演示整个流程。

场景一:未登录直接访问

  • 访问 GET /todos
  • 结果:401 Unauthorized。系统拒绝访问。

场景二:登录获取 Token

  • 访问 POST /login,Body 传入 {"username": "admin", "password": "123456"}
  • 结果:返回一个 JSON,包含 Token 字符串。

场景三:携带 Token 访问

  1. 复制刚才的 Token。
  2. 在请求头中添加:Authorization: Bearer <你的Token>。(注意 Bearer 后有个空格)
  3. 再次访问 GET /todos
  4. 结果:200 OK,成功返回数据!

场景四:权限不足

  1. gangzi 用户登录(角色是 User)。
  2. 携带 Token 访问 DELETE /todos/1
  3. 结果:403 Forbidden。虽然登录了,但权限不够。

八、刚子小贴士:安全避坑指南

  1. HTTPS 是必须的:JWT 默认不加密,Token 在网络传输中如果被截获,黑客就能冒充用户。唯一的防御手段就是 HTTPS 加密传输。
  2. 不要存敏感信息:Payload 任何人都能解码看到,千万别把密码、身份证号存进去。
  3. Token 有效期:设置合理的过期时间(如 2 小时)。为了体验,通常会配合"Refresh Token"(刷新令牌)机制,这属于进阶话题,感兴趣的同学可以自行研究。
  4. 配置文件管理 :不要把密钥硬编码在代码里,请迁移到 appsettings.jsonJwt:Key 配置项中,并使用 IConfiguration 读取。

九、总结

在这篇文章中,我们完成了系统的安全闭环:

  1. 理解了 JWT 的原理:Header、Payload、Signature。
  2. 实现了 /login 接口颁发 Token。
  3. 配置了中间件验证 Token。
  4. 学会了 RequireAuthorization 和基于角色的权限控制。
相关推荐
碎碎念_4927 小时前
SpringBoot + Vue 前后端分离从 0 到 1 完整环境配置流程
vue.js·spring boot·后端
shuaijie05188 小时前
强制修改调用接口的api地址。
javascript·vue.js·ecmascript
katiua12 小时前
Vue-i18n TypeScript 补全提示
vue.js
Fuzio12 小时前
用 Spring Boot + Vue + Fuzio 构建现代 Java 桌面应用
vue.js·spring boot
kisshyshy12 小时前
从0到1彻底理解流式输出:先读懂Vue的“乐高积木”,再拧开LLM的“数据水龙头”
javascript·vue.js·人工智能
饮茶三千12 小时前
电子保函模板编辑器实战二:HTML→FTL 编译转换的设计与实现
前端·vue.js·设计模式
英勇无比的消炎药12 小时前
表单筛选利器:TinyVue BaseSelect 基础选择器实战
vue.js
先吃饱再说13 小时前
从零理解 Vue 3:组件化、数据绑定与响应式完全指南
vue.js·前端框架
gis开发之家14 小时前
《Vue3 从入门到大神29篇》单元测试与 E2E 测试 —— 保障 Vue3 项目的质量
前端·javascript·vue.js·单元测试·前端框架·vue3
forever_world15 小时前
# 💧 从前端视角,彻底搞懂 AI 流式输出 —— 从"接根水管"到 Vue 3 实战
前端·vue.js