在 ASP.NET Core WebAPI 中使用 JWT 验证

为了保护 WebAPI 仅提供合法的使用者存取,有很多机制可以做,透过 JWT (JSON Web Token) 便是其中一种方式,这篇示范如何使用官方所提供的 System.IdentityModel.Tokens.Jwt 扩充套件,处理呼叫 API 的来源是否为合法的使用者身分。

顺道一提,要产生 JWT Token 有很多套件可以帮助开发者快速建立,JWT 这个 NuGet 套件就是其中一个,但这裡我使用官方所提供的 System.IdentityModel.Tokens.Jwt 扩充套件来处理,虽然这是官方提供的版本,但写起来一点也不困难。

建立专案

使用 Visual Studio 2019 建立 ASP.NET Core WebAPI 专案后,首先修改 Startup.cs 中的 ConfigureServices 方法,设定这个 WebAPI 站台要使用哪种方式来验证 HTTP Request 是否合法,程式码如下:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
    // STEP1: 设定用哪种方式验证 HTTP Request 是否合法
    services
        // 检查 HTTP Header 的 Authorization 是否有 JWT Bearer Token
        .AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
        // 设定 JWT Bearer Token 的检查选项
        .AddJwtBearer(options =>
        {
            options.TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = true,
                ValidIssuer = Configuration["Jwt:Issuer"],
                ValidateAudience = true,
                ValidAudience = Configuration["Jwt:Issuer"],
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
            };
        });
}

这裡我们设定系统在验证 JWT Token 时,必须要符合以下 4 个条件:

  1. 相同的 Issuer 设定值
  2. 相同的 Audience 设定值
  3. 验证 Token 有效期限
  4. 符合对称式加密的签章

接者一样在 Startup.cs 中的 Configure 加入验证权限用的 Middleware,让每次进来的 HTTP Request 都会经过此层验证机制,程式码如下:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // 略...
    // STEP2: 使用验证权限的 Middleware
    app.UseAuthentication();
    app.UseHttpsRedirection();
    app.UseMvc();
}

如此一来网站的基本设定就搞定了。

取得 JWT Token

要如何产生 JWT Token 呢?这裡我们建立了一个 AuthController 控制器来产生所需要的 JWT Token,流程如下:

  1. 身分验证
  2. 建立使用者声明资讯
  3. 取得加密金钥
  4. 建立 JWT Token

身分验证是你的需求自行实作,验证后可将取得的使用者资讯(非机敏资讯)包进使用者的 Claim 声明中,这些资讯将会是 JWT Payload 的一部分,这裡的 Claim 声明资讯也可以根据你的需求客制增加。

我们知道 JWT 是用三部分 Header、PayloadSignature,并使用(.)将三个部分连结起来成为一个字串,Signature 这部分会是 Header、Payload 加上一组 Secret 做杂凑运算产生出来的,用来验证整个 JWT 资讯是没有被窜改过。加密金钥这段虽然是选用,但还是相当建议加上去,增加安全强度。

接著透过 System.IdentityModel.Tokens.Jwt 这个命名空间底下的 JwtSecurityTokenHandler 来产生 JWT Token,而 JWT Token 的内容描述则交由 SecurityTokenDescriptor 来组合,在 JWT Token 的内容描述中,请根据需求做调整。

如此一来就可以产生所需要的 JWT Token 了。

[Route("api/[controller]")]
[ApiController]
public class AuthController : ControllerBase
{
    private readonly IConfiguration _config;
    public AuthController(IConfiguration configuration)
    {
        _config = configuration;
    }
    // GET api/auth/login
    [HttpGet, Route("login")]
    public IActionResult Login(string name)
    {
        // STEP0: 在产生 JWT Token 之前,可以依需求做身分验证
        // STEP1: 建立使用者的 Claims 声明,这会是 JWT Payload 的一部分
        var userClaims = new ClaimsIdentity(new[] {
            new Claim(JwtRegisteredClaimNames.NameId, name),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim("CustomClaim", "Anything You Like")
        });
        // STEP2: 取得对称式加密 JWT Signature 的金钥
        // 这部分是选用,但此范例在 Startup.cs 中有设定 ValidateIssuerSigningKey = true 所以这裡必填
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_config["Jwt:Key"]));
        // STEP3: 建立 JWT TokenHandler 以及用于描述 JWT 的 TokenDescriptor
        var tokenHandler = new JwtSecurityTokenHandler();
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Issuer = _config["Jwt:Issuer"],
            Audience = _config["Jwt:Issuer"],
            Subject = userClaims,
            Expires = DateTime.Now.AddMinutes(30),
            SigningCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256)
        };
        // 产出所需要的 JWT Token 物件
        var securityToken = tokenHandler.CreateToken(tokenDescriptor);
        // 产出序列化的 JWT Token 字串
        var serializeToken = tokenHandler.WriteToken(securityToken);
        return new ContentResult() { Content = serializeToken };
    }
}

如何使用

使用上非常简单,只要挂上需要的装饰器即可,这裡建立了 ValuesController 做测试,分别挂上 [AllowAnonymous][Authorize],前者是给匿名登入使用,也就是不需要有 JWT Token 也能执行,后者则必须要在 HTTP 的 Authorization Header 必须设定合法的 JWT Bearer Token 才能使用。

[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    // GET api/values/anonymous
    /// <summary>使用匿名登入,无视于身分验证</summary>
    [AllowAnonymous]
    [HttpGet, Route("anonymous")]
    public IActionResult Anonymous()
    {
        return new ContentResult() { Content = $@"For all anonymous." };
    }
    // GET api/values/authorize
    /// <summary>使用身分验证,HTTP 的 Authorization Header 必须设定合法的 JWT Bearer Token 才能使用</summary>
    [Authorize]
    [HttpGet, Route("authorize")]
    public IActionResult All()
    {
        return new ContentResult() { Content = $@"For all client who authorize." };
    }
}

这个验证装饰器还可以有很多种玩法,例如根据所建立的验证 Policy 做验证,或根据使用者 Claim 声明的角色做验证,提供了很大的弹性来处理。

JwtRegisteredClaimNames 属性说明

在建立使用者的 Claims 声明时,我们会用到很多 JwtRegisteredClaimNames 结构型别,来取得是先定义好的字串,在 System.IdentityModel.Tokens.Jwt 命名空间中的 JwtRegisteredClaimNames 定义了很多 JWT 会用到的声明,但官方文件说明相当的少,自行整理了如下:

声明栏位说明连结
Jti表示 JWT ID,Token 的唯一识别码http://tools.ietf.org/html/rfc7519#section-4
Iss表示 Issuer,发送 Token 的发行者http://tools.ietf.org/html/rfc7519#section-4
Iat表示 Issued At,Token 的建立时间http://tools.ietf.org/html/rfc7519#section-4
Exp表示 Expiration Time,Token 的逾期时间http://tools.ietf.org/html/rfc7519#section-4
Sub表示 Subject,Token 的主体内容http://tools.ietf.org/html/rfc7519#section-4
Aud表示 Audience,接收 Token 的观众http://tools.ietf.org/html/rfc7519#section-4
Typ表示 Token 的类型,例如 JWT 表示 JSON Web Token 类型http://tools.ietf.org/html/rfc7519#section-4
Nbf表示 Not Before,定义在什麽时间之前,不可用http://tools.ietf.org/html/rfc7519#section-4
Actort识别执行授权的代理是谁http://tools.ietf.org/html/rfc7519#section-4
Prnhttp://tools.ietf.org/html/rfc7519#section-4
Noncehttp://tools.ietf.org/html/rfc7519#section-4
NameId使用者识别码http://tools.ietf.org/html/rfc7519#section-4
FamilyName使用者姓氏http://tools.ietf.org/html/rfc7519#section-4
GivenName使用者名字http://tools.ietf.org/html/rfc7519#section-4
Gender使用者性别http://tools.ietf.org/html/rfc7519#section-4
Email使用者的电子邮件http://tools.ietf.org/html/rfc7519#section-4
Birthdate使用者生日http://tools.ietf.org/html/rfc7519#section-4
Website使用者的网站http://tools.ietf.org/html/rfc7519#section-4
CHashhttp://tools.ietf.org/html/rfc7519#section-4
UniqueNamehttp://tools.ietf.org/html/rfc7519#section-4
AtHashhttp://openid.net/specs/openid-connect-core-1_0.html#CodeIDToken
Acrhttp://openid.net/specs/openid-connect-core-1_0.html#IDToken
Amrhttp://openid.net/specs/openid-connect-core-1_0.html#IDToken
Azphttp://openid.net/specs/openid-connect-core-1_0.html#IDToken
AuthTimehttp://openid.net/specs/openid-connect-core-1_0.html#IDToken
Sidhttp://openid.net/specs/openid-connect-frontchannel-1_0.html#OPLogout

有些定义的声明栏位很难找到说明,有找到相关资讯再陆续补充。

程式码

关于本篇文章完整的程式码发布于 GitHub:poychang/Demo-WebAPI-Jwt-Auth,请参考裡面的 SimpleJwtAuth 专案。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值