第五章:JWT 认证与用户系统

博客v1.0系列教程(Csharp)博客 v1.0 系列教程 (C#)

5.1 JWT 配置

// appsettings.json
{
  "JWT": {
    "Secret": "your-256-bit-secret-key-here-must-be-long-enough",
    "Issuer": "Blog",
    "Audience": "Blog",
    "ExpireMinutes": 1440
  }
}

5.2 Token 服务

public class TokenService : ITokenService
{
    private readonly IConfiguration _config;

    public string GenerateToken(UserModel user)
    {
        var claims = new[]
        {
            new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
            new Claim(ClaimTypes.Role, user.Role.ToString()),
            new Claim(ClaimTypes.Name, user.Username ?? "")
        };

        var key = new SymmetricSecurityKey(
            Encoding.UTF8.GetBytes(_config["JWT:Secret"]));
        var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);

        var token = new JwtSecurityToken(
            issuer: _config["JWT:Issuer"],
            audience: _config["JWT:Audience"],
            claims: claims,
            expires: DateTime.UtcNow.AddMinutes(expireMinutes),
            signingCredentials: creds
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    public ClaimsPrincipal? ValidateToken(string token)
    {
        var handler = new JwtSecurityTokenHandler();
        return handler.ValidateToken(token, GetValidationParameters(), out _);
    }
}

5.3 登录接口

app.MapPost("/api/user/login", async (LoginDto dto, IUserService userService) =>
{
    var user = await userService.GetByUsernameAsync(dto.Username);
    if (user == null || !BCrypt.Net.BCrypt.Verify(dto.Password, user.Password))
        return Results.Unauthorized();

    var token = tokenService.GenerateToken(user);
    return Results.Ok(new { Token = token, User = user });
});

5.4 Token 黑名单(Redis)

public class RedisTokenBlacklist
{
    private readonly ConnectionMultiplexer _redis;

    public async Task AddToBlacklistAsync(string token, TimeSpan expiry)
    {
        var db = _redis.GetDatabase();
        await db.StringSetAsync($"blacklist:{token}", "true", expiry);
    }

    public async Task<bool> IsBlacklistedAsync(string token)
    {
        var db = _redis.GetDatabase();
        return await db.KeyExistsAsync($"blacklist:{token}");
    }
}

5.5 授权过滤器

public class AuthorizeFilter : IEndpointFilter
{
    public async ValueTask<object?> InvokeAsync(
        EndpointFilterInvocationContext context,
        EndpointFilterDelegate next)
    {
        var token = context.HttpContext.Request.Headers["Authorization"]
            .FirstOrDefault()?.Replace("Bearer ", "");

        if (string.IsNullOrEmpty(token))
            return Results.Unauthorized();

        var principal = tokenService.ValidateToken(token);
        if (principal == null)
            return Results.Unauthorized();

        context.HttpContext.User = principal;
        return await next(context);
    }
}

下一章将实现文章 CRUD 与 Markdown 支持。

csharpjwtauthsecurity