Skip to content

认证鉴权

Art Admin 使用 Reference Token(引用令牌) 而非 JWT 进行认证,Token 存储在数据库 + Redis 缓存中。

为什么不用 JWT?

JWTReference Token
Token 内容携带用户信息(可解码)不透明字符串(sc_ + GUID)
即时吊销❌ 无法及时失效✅ 服务端删除即失效
Token 大小大(包含 Payload)小(32 字符)
服务端状态无状态有状态(需要存储)

Reference Token 的优势:可以随时吊销、不泄露用户信息、Token 长度可控。

Token 生命周期

用户登录


创建 RefreshToken(180天有效)── 存入 token_refresh 表


创建 AccessToken(1小时有效)── 存入 token_access 表
  │                             Token 格式:sc_{GUID}

返回给客户端

  ├─── 正常请求 ──► Authorization: Bearer sc_xxx
  │                   │
  │                   ▼
  │              鉴权中间件:Redis 缓存 → DB 查询

  ├─── Token 过期 ──► 用 RefreshToken 刷新
  │                   创建新 AccessToken + 更新 RefreshToken

  └─── 退出登录 ──► 将 Token 过期时间设为当前时间

鉴权中间件流程

csharp
public async Task Invoke(HttpContext context, ArtDbContext dbContext,
    RequestContext requestContext, RedisClient cache)
{
    // 1. 获取路由元数据(是否需要鉴权)
    var apiMeta = context.GetEndpoint()?.Metadata.GetMetadata<ApiMeta>();

    // 2. 提取 Token
    var token = ExtractToken(context); // Authorization: Bearer sc_xxx

    // 3. 提取租户信息
    ExtractTenantId(context, requestContext);

    // 4. 如果需要鉴权
    if (requiredTokenType != TokenType.无)
    {
        // Token → MD5 Hash → Redis 缓存查询 → DB 查询
        var userInfo = await GetUserInfo(token, dbContext, cache);
        PopulateContext(requestContext, userInfo);
    }

    // 5. 异步更新用户活跃时间(不阻塞主流程)
    _ = UpdateLastActiveTimeAsync(tokenType, userId);

    await _next(context);
}

RequestContext

鉴权成功后,用户信息填充到 RequestContext(Scoped 生命周期):

csharp
public class RequestContext
{
    public long Id { get; set; }         // 用户 ID
    public string? Name { get; set; }    // 用户名
    public string? Account { get; set; } // 账号
    public bool IsSuper { get; set; }    // 是否超管
    public string TenantId { get; set; } // 租户 ID
    public string RequestId { get; set; }// 请求追踪 ID
    public string? RequestIp { get; set; }
}

在 Service 中直接注入使用:

csharp
[Service(ServiceLifetime.Scoped)]
public class SysUserService
{
    private readonly RequestContext _user;

    public async Task<UserInfoResponse> GetCurrentUserAsync()
    {
        // 直接使用当前登录用户的 ID
        var user = await _db.SysUser
            .Where(x => x.Id == _user.Id)
            .FirstOrDefaultAsync();
    }
}

多端 Token 隔离

Token 类型路由前缀鉴权接口
平台端 Token/admin/*IAdminRouterBase
客户端 Token/app/*IAppRouterBase
无需 Token/common/*ICommonRouterBase

平台端和客户端使用不同的 TokenType,互相不能混用。