ABP - JWT 鉴权(JWT Authentication)[AbpJwtBearerModule、JwtBearerOptions]

JWT 鉴权(JWT Authentication)

核心辅助类

  • AbpJwtBearerModule:JWT集成模块。
  • JwtBearerOptions:JWT配置选项。

JWT(JSON Web Token)是ABP框架中常用的无状态鉴权方案,核心作用是“让客户端携带Token访问接口,服务器验证Token合法性”,避免每次请求都传递账号密码。以下结合AbpJwtBearerModuleJwtBearerOptions,用零基础能懂的例子讲解配置与使用。

一、JWT鉴权的核心流程(通俗理解)

可以把JWT想象成“电子门禁卡”:

  1. 客户端登录:用户输入账号密码,服务器验证通过后,生成一张“门禁卡”(JWT Token)并返回给客户端;
  2. 客户端访问接口:每次请求时,客户端在请求头里带上“门禁卡”(Authorization: Bearer 你的Token);
  3. 服务器验证:服务器收到请求后,先检查“门禁卡”是否有效(是否伪造、是否过期),有效则允许访问,无效则拒绝。

二、核心类说明

类名 作用 通俗理解
AbpJwtBearerModule ABP集成JWT的核心模块,提供JWT验证能力 相当于“门禁系统的基础框架”,必须引入才能用JWT
JwtBearerOptions JWT的配置选项(如Token密钥、有效期) 相当于“门禁卡的制作规则”(用什么密钥加密、卡多久过期)

三、实操步骤:实现JWT鉴权

ABP实现JWT鉴权分3步:引入模块→配置JWT→使用Token访问接口,下面完整演示。

步骤1:引入JWT模块(基础准备)

首先在项目的核心模块(如MyAppWebModule)中,通过[DependsOn]引入AbpJwtBearerModule,开启JWT功能。

using Volo.Abp.Modularity;
using Volo.Abp.AspNetCore.Authentication.JwtBearer; // 引入JWT模块

// 声明依赖JWT模块
[DependsOn(
    typeof(AbpAspNetCoreModule),
    typeof(AbpJwtBearerModule) // 关键:引入JWT集成模块
)]
public class MyAppWebModule : AbpModule
{
    // 后续配置JWT的代码写在这里
}

步骤2:配置JWT(核心步骤)

在模块的ConfigureServices方法中,通过JwtBearerOptions配置JWT的“制作规则”,比如密钥、有效期、发行人等。

完整配置代码

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;

public override void ConfigureServices(ServiceConfigurationContext context)
{
    var services = context.Services;

    // 1. 配置JWT鉴权
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) // 指定默认鉴权方案为JWT
        .AddJwtBearer(options =>
        {
            // 2. 配置JWT验证规则(JwtBearerOptions核心参数)
            options.TokenValidationParameters = new TokenValidationParameters
            {
                // 2.1 必须验证的内容(确保Token合法)
                ValidateIssuer = true, // 是否验证“发行人”(谁发的Token)
                ValidIssuer = "MyApp", // 合法的发行人(自定义,要和生成Token时一致)
                
                ValidateAudience = true, // 是否验证“受众”(Token发给谁)
                ValidAudience = "MyApp.Users", // 合法的受众(自定义,要和生成Token时一致)
                
                ValidateLifetime = true, // 是否验证Token有效期(避免用过期的Token)
                ValidateIssuerSigningKey = true, // 是否验证“签名密钥”(避免Token被伪造)

                // 2.2 签名密钥(核心!必须和生成Token时用的密钥一致,且要足够复杂)
                IssuerSigningKey = new SymmetricSecurityKey(
                    Encoding.UTF8.GetBytes("MyApp_JWT_Secret_Key_123456") // 密钥字符串(生产环境要换成更长的随机字符串)
                ),

                // 2.3 可选配置:Token过期后的容错时间(允许30秒内的过期Token继续使用,避免网络延迟问题)
                ClockSkew = TimeSpan.FromSeconds(30)
            };

            // 3. 可选:配置Token在请求中的传递方式(默认从请求头的Authorization字段获取)
            // 若客户端用其他方式传递(如Query参数),可在这里扩展
            options.Events = new JwtBearerEvents
            {
                // 示例:从Query参数(?token=xxx)获取Token(适合移动端或特殊场景)
                OnMessageReceived = context =>
                {
                    var token = context.Request.Query["token"];
                    if (!string.IsNullOrEmpty(token))
                    {
                        context.Token = token; // 将Query中的token赋值给上下文,供后续验证
                    }
                    return Task.CompletedTask;
                }
            };
        });

    // 4. 其他配置(如启用授权中间件)
    services.AddAuthorization();
}

// 5. 在应用初始化时启用鉴权和授权中间件
public override void OnApplicationInitialization(ApplicationInitializationContext context)
{
    var app = context.GetApplicationBuilder();

    // 必须在UseRouting之后、UseEndpoints之前添加
    app.UseAuthentication(); // 启用鉴权(验证Token)
    app.UseAuthorization();  // 启用授权(验证用户是否有权限访问接口)
}

步骤3:生成JWT Token(登录接口)

配置好JWT后,需要一个“登录接口”来生成Token。通常在AccountAppService中实现,通过IJwtTokenGenerator生成Token。

登录接口代码

using Volo.Abp.Application.Services;
using Volo.Abp.Identity;
using Volo.Abp.Security.Claims;
using Microsoft.AspNetCore.Identity;
using System.Security.Claims;

public class AccountAppService : ApplicationService, IAccountAppService
{
    // 注入生成Token的工具
    private readonly IJwtTokenGenerator _jwtTokenGenerator;
    // 注入ABP内置的用户管理器(用于验证账号密码)
    private readonly UserManager<IdentityUser> _userManager;

    public AccountAppService(IJwtTokenGenerator jwtTokenGenerator, UserManager<IdentityUser> userManager)
    {
        _jwtTokenGenerator = jwtTokenGenerator;
        _userManager = userManager;
    }

    // 登录接口:输入账号密码,返回Token
    public async Task<LoginResultDto> LoginAsync(LoginInputDto input)
    {
        // 1. 验证账号是否存在
        var user = await _userManager.FindByNameAsync(input.UserName);
        if (user == null)
        {
            throw new UserFriendlyException("用户名不存在!");
        }

        // 2. 验证密码是否正确
        var passwordValid = await _userManager.CheckPasswordAsync(user, input.Password);
        if (!passwordValid)
        {
            throw new UserFriendlyException("密码错误!");
        }

        // 3. 获取用户的角色(可选:把角色信息放到Token中,方便后续权限判断)
        var roles = await _userManager.GetRolesAsync(user);

        // 4. 生成JWT Token(核心:用IJwtTokenGenerator按配置生成Token)
        var token = await _jwtTokenGenerator.GenerateTokenAsync(
            userId: user.Id.ToString(), // 用户ID
            userName: user.UserName,    // 用户名
            email: user.Email,          // 邮箱(可选,放到Token的“声明”中)
            roles: roles                 // 角色(可选,放到Token的“声明”中)
        );

        // 5. 返回Token给客户端
        return new LoginResultDto
        {
            Token = token,
            ExpirationTime = DateTime.Now.AddHours(2) // Token有效期(2小时,要和配置一致)
        };
    }
}

// 登录输入DTO(客户端传参)
public class LoginInputDto
{
    public string UserName { get; set; } // 用户名
    public string Password { get; set; } // 密码
}

// 登录返回DTO(给客户端的结果)
public class LoginResultDto
{
    public string Token { get; set; } // JWT Token
    public DateTime ExpirationTime { get; set; } // Token过期时间
}

步骤4:用Token访问接口(客户端操作)

客户端拿到Token后,每次访问需要鉴权的接口时,都要在请求头中携带Token。

1. 前端示例(Axios)

// 1. 登录获取Token(调用上面的Login接口)
async function login(userName, password) {
  const response = await axios.post('/api/app/account/login', {
    userName: userName,
    password: password
  });
  const token = response.data.token;
  // 把Token存到本地存储(localStorage),方便后续使用
  localStorage.setItem('myAppToken', token);
  return token;
}

// 2. 携带Token访问接口(比如获取用户信息)
async function getUserInfo() {
  const token = localStorage.getItem('myAppToken');
  const response = await axios.get('/api/app/user/info', {
    headers: {
      // 关键:在Authorization头中携带Token,格式为“Bearer + 空格 + Token”
      'Authorization': `Bearer ${token}`
    }
  });
  return response.data;
}

2. 接口鉴权控制(服务端)

在需要鉴权的接口上添加[Authorize]特性,未携带有效Token的请求会被拒绝:

using Microsoft.AspNetCore.Authorization;

// 添加[Authorize]特性:只有携带有效Token的请求才能访问
[Authorize]
public class UserAppService : ApplicationService
{
    // 需鉴权的接口:获取当前登录用户信息
    public async Task<UserDto> GetCurrentUserInfoAsync()
    {
        // 通过CurrentUser获取当前登录用户的信息(Token中包含的用户ID、角色等)
        var userId = CurrentUser.Id;
        var userName = CurrentUser.UserName;
        // 后续查询用户信息并返回...
    }
}

四、关键配置详解

1. JwtBearerOptions.TokenValidationParameters核心参数

参数名 作用 注意事项
ValidateIssuer 是否验证Token的“发行人”(Issuer) 必须设为true,避免其他系统发的Token混入
ValidIssuer 合法的发行人名称 要和IJwtTokenGenerator生成Token时的发行人一致
ValidateAudience 是否验证Token的“受众”(Audience) 必须设为true,确保Token是发给当前系统的
ValidAudience 合法的受众名称 要和生成Token时的受众一致
ValidateLifetime 是否验证Token有效期 必须设为true,避免过期Token被滥用
IssuerSigningKey 验证Token签名的密钥 密钥要足够复杂(建议32位以上随机字符串),生产环境要存在配置文件中,不要硬编码

2. Token的“声明”(Claims)

Token中可以包含“声明”(比如用户ID、角色、邮箱),这些信息会在服务器验证Token后解析出来,通过CurrentUser获取:

// 在需要的地方通过CurrentUser获取Token中的声明信息
public async Task DoSomethingAsync()
{
    var userId = CurrentUser.Id; // 获取用户ID(来自Token的声明)
    var userName = CurrentUser.UserName; // 获取用户名
    var isAdmin = CurrentUser.IsInRole("Admin"); // 判断是否为Admin角色(来自Token的角色声明)
}

五、新手避坑指南

  1. 密钥硬编码问题:示例中密钥是硬编码的MyApp_JWT_Secret_Key_123456,生产环境必须放到配置文件(如appsettings.json)中,避免泄露;

    // appsettings.json中配置密钥
    "Jwt": {
      "SecretKey": "Your_Super_Long_Random_Secret_Key_1234567890",
      "Issuer": "MyApp",
      "Audience": "MyApp.Users",
      "ExpirationHours": 2
    }
    

    然后在代码中读取配置:

    var jwtConfig = context.Services.GetConfiguration().GetSection("Jwt");
    options.TokenValidationParameters.IssuerSigningKey = new SymmetricSecurityKey(
        Encoding.UTF8.GetBytes(jwtConfig["SecretKey"])
    );
    
  2. Token有效期不一致:生成Token时的有效期(如2小时)要和JwtBearerOptions中的配置一致,避免Token没过期却被判定为无效;

  3. 忘记启用中间件:必须在OnApplicationInitialization中添加app.UseAuthentication()app.UseAuthorization(),且顺序不能错(先鉴权,再授权)。

总结

  • 核心流程:登录生成Token→客户端携带Token请求→服务器验证Token→允许/拒绝访问;
  • 关键类AbpJwtBearerModule(开启JWT)、JwtBearerOptions(配置验证规则);
posted @ 2025-10-24 21:32  【唐】三三  阅读(2)  评论(0)    收藏  举报