当前位置:首页 > 技术 > 正文内容

JWT 在 .NET 中的实现与微服务认证实践

访客 技术 2026年6月17日 1

一、JWT 核心概念

JSON Web Token 是一种基于 JSON 的开放标准(RFC 7519),用于在网络应用间安全传输声明信息。其特点在于紧凑、自包含,且可通过数字签名验证信息真实性。

1.1 令牌结构解析

JWT 由三个 Base64Url 编码的部分通过点号连接而成:

  • Header:声明签名算法与令牌类型
  • Payload:承载实际业务数据
  • Signature:对前两部分进行加密签名

典型示例:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ

1.2 认证方案演进对比

方案机制适用场景
传统 Session服务端存储会话状态,Cookie 传递标识单体应用
分布式 SessionRedis 集中管理会话数据集群部署
JWT 认证客户端持有令牌,服务端无状态验证微服务/分布式架构

JWT 方案优势:跨语言支持、减少数据库查询、天然适配分布式系统、传输高效。

二、基础操作实现

2.1 生成令牌

public static string BuildToken()
{
    var identityClaims = new List<Claim>();
    identityClaims.Add(new Claim(ClaimTypes.NameIdentifier, "10086"));
    identityClaims.Add(new Claim(ClaimTypes.Name, "TechExplorer"));
    identityClaims.Add(new Claim(ClaimTypes.Role, "Editor"));
    identityClaims.Add(new Claim(ClaimTypes.Role, "Reviewer"));
    identityClaims.Add(new Claim("DepartmentCode", "RD-2024"));

    var secret = "YourSecretKeyMustBeAtLeast16Chars!";
    var expiry = DateTime.Now.AddHours(2);

    var keyBytes = Encoding.UTF8.GetBytes(secret);
    var symmetricKey = new SymmetricSecurityKey(keyBytes);
    var signingCredentials = new SigningCredentials(symmetricKey, SecurityAlgorithms.HmacSha256);

    var tokenDescriptor = new JwtSecurityToken(
        claims: identityClaims,
        expires: expiry,
        signingCredentials: signingCredentials
    );

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

2.2 解析载荷(无需密钥)

public static void ExtractPayload(string tokenString)
{
    var segments = tokenString.Split('.');
    
    var decodedHeader = Base64UrlDecode(segments[0]);
    var decodedPayload = Base64UrlDecode(segments[1]);
    
    Console.WriteLine($"Header: {decodedHeader}");
    Console.WriteLine($"Payload: {decodedPayload}");
}

private static string Base64UrlDecode(string input)
{
    input = input.Replace('-', '+').Replace('_', '/');
    switch (input.Length % 4)
    {
        case 2: input += "=="; break;
        case 3: input += "="; break;
    }
    var bytes = Convert.FromBase64String(input);
    return Encoding.UTF8.GetString(bytes);
}

安全提示:Payload 仅为 Base64 编码,任何人均可解码查看。切勿将敏感信息直接存入其中。

2.3 签名验证与数据提取

public static Dictionary<string, string> VerifyAndExtract(string token)
{
    var secretKey = "YourSecretKeyMustBeAtLeast16Chars!";
    var handler = new JwtSecurityTokenHandler();
    
    var validationParams = new TokenValidationParameters
    {
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secretKey)),
        ValidateIssuer = false,
        ValidateAudience = false,
        ValidateLifetime = true
    };

    var claimsPrincipal = handler.ValidateToken(token, validationParams, out _);
    
    return claimsPrincipal.Claims.ToDictionary(
        c => c.Type, 
        c => c.Value
    );
}

三、.NET Core 集成方案

3.1 配置定义

appsettings.json 中添加:

{
  "AuthSettings": {
    "Secret": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a",
    "Issuer": "MyAppPlatform",
    "Audience": "MyAppClient",
    "AccessTokenLifetime": 30
  }
}

3.2 配置实体

public class AuthSettings : IOptions<AuthSettings>
{
    public AuthSettings Value => this;
    
    public string Secret { get; set; }
    public string Issuer { get; set; }
    public string Audience { get; set; }
    public int AccessTokenLifetime { get; set; }
    
    public DateTime IssuedAt => DateTime.UtcNow;
    public DateTime Expiration => IssuedAt.AddMinutes(AccessTokenLifetime);
    
    private SecurityKey GetSecurityKey() => 
        new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret));
    
    public SigningCredentials GetSigningCredentials() => 
        new SigningCredentials(GetSecurityKey(), SecurityAlgorithms.HmacSha256);
}

3.3 令牌服务

public class TokenService
{
    private readonly AuthSettings _settings;

    public TokenService(IOptions<AuthSettings> options)
    {
        _settings = options.Value;
    }

    public string IssueToken(UserProfile user)
    {
        var claims = new List<Claim>();
        claims.Add(new Claim("mobile", user.MobileNumber));
        claims.Add(new Claim(ClaimTypes.NameIdentifier, user.UserId));
        claims.Add(new Claim(ClaimTypes.Name, user.DisplayName));
        
        foreach (var permission in user.Permissions.Split(','))
        {
            claims.Add(new Claim(ClaimTypes.Role, permission.Trim()));
        }

        var token = new JwtSecurityToken(
            issuer: _settings.Issuer,
            audience: _settings.Audience,
            claims: claims,
            notBefore: _settings.IssuedAt,
            expires: _settings.Expiration,
            signingCredentials: _settings.GetSigningCredentials()
        );

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

3.4 认证中间件配置

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<TokenService>();
    services.Configure<AuthSettings>(Configuration.GetSection("AuthSettings"));

    var authConfig = new AuthSettings();
    Configuration.GetSection("AuthSettings").Bind(authConfig);

    services.AddAuthentication(options =>
    {
        options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
    })
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,
            ValidIssuer = authConfig.Issuer,
            ValidAudience = authConfig.Audience,
            IssuerSigningKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(authConfig.Secret)),
            ValidateLifetime = true,
            ClockSkew = TimeSpan.FromSeconds(10)
        };
    });
}

public void Configure(IApplicationBuilder app)
{
    app.UseAuthentication();
    app.UseAuthorization();
}

3.5 API 控制器应用

[Route("api/[controller]")]
[ApiController]
public class AccountController : ControllerBase
{
    private readonly TokenService _tokenService;

    public AccountController(TokenService tokenService)
    {
        _tokenService = tokenService;
    }

    [HttpPost("sign-in")]
    [AllowAnonymous]
    public IActionResult Authenticate([FromBody] LoginRequest request)
    {
        // 执行身份验证逻辑...
        
        var profile = new UserProfile
        {
            UserId = request.UserId,
            DisplayName = request.UserName,
            MobileNumber = request.Phone,
            Permissions = "editor,reviewer"
        };

        var accessToken = _tokenService.IssueToken(profile);
        return Ok(new { token = accessToken });
    }

    [HttpGet("profile")]
    [Authorize(Roles = "editor")]
    public IActionResult GetProfile()
    {
        var userInfo = new UserProfile
        {
            UserId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value,
            DisplayName = User.FindFirst(ClaimTypes.Name)?.Value,
            Permissions = string.Join(",", 
                User.FindAll(ClaimTypes.Role).Select(r => r.Value))
        };
        
        return Ok(userInfo);
    }

    [HttpGet("admin-only")]
    [Authorize(Roles = "admin")]
    public IActionResult AdminEndpoint()
    {
        return Ok("Authorized");
    }
}

3.6 Swagger 集成认证

services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo { Title = "API", Version = "v1" });
    
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "请输入: Bearer {token}",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });

    options.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

四、关键注意事项

  • 密钥长度必须达到算法要求(HMAC-SHA256 建议至少 32 字节)
  • 令牌载荷虽可读取但不可信任,必须通过签名验证其完整性
  • 合理设置过期时间,结合 Refresh Token 机制实现安全与体验的平衡
  • 对于高安全性要求的场景,考虑使用 RSA 等非对称加密算法

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。