.Net Core官方的 JWT 受权验证

什么是JWT?

JSON Web令牌(JWT)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,用于在各方之间安全地传输信息做为JSON对象。因为此信息是通过数字签名的,所以能够被验证和信任。可使用秘密(使用HMAC算法)或使用RSAECDSA的公钥/私钥对对JWT进行签名html

尽管能够对JWT进行加密以提供双方之间的保密性,但咱们将重点关注已签名的令牌。签名的令牌能够验证其中包含的声明完整性,而加密的令牌则将这些声明隐藏在其余方的面前。当使用公钥/私钥对对令牌进行签名时,签名还证实只有持有私钥的一方才是对其进行签名的一方。因为缺少安全性,因此不能把如密码等敏感信息放在令牌中。web

何时应该使用JWT?

如下是JSON Web令牌有用的一些状况:算法

  • 受权:这是使用JWT的最多见方案。一旦用户登陆,每一个后续请求将包括JWT,从而容许用户访问该令牌容许的路由,服务和资源。单一登陆是当今普遍使用JWT的一项功能,由于它的开销很小而且能够在不一样的域中轻松使用。数据库

  • 信息交换:JSON Web令牌是在各方之间安全传输信息的好方法。由于能够对JWT进行签名(例如,使用公钥/私钥对),因此您能够肯定发件人是他们所说的人。此外,因为签名是使用标头和有效负载计算的,所以您还能够验证内容是否未被篡改。json

JWT结构是什么?

JSON Web令牌以紧凑的形式由三部分组成,这些部分由点(.分隔,分别是:api

  • 标头
  • 有效载荷
  • 签名

所以,JWT一般以下所示。跨域

xxxxx.yyyyy.zzzzz安全

标头:一般由两部分组成,令牌类型和使用的签名算法。服务器

{
  "alg": "HS256",
  "typ": "JWT"
}

有效载荷:有三种说明类型,预约义声明、公共声明和私有声明。声明名称仅是三个字符,由于JWT是紧凑的cookie

  • 预约义声明:包括iss(发出者), exp(到期时间), sub(主题), aud(受众)等,是推荐的可是不是强制的能够没有。
  • 公共声明:公共声明,这个部分能够随便定义,可是要注意和 IANA JSON Web Token 冲突。
  • 私有声明:这个部分是共享被认定信息中自定义部分。

签名要建立签名部分,您必须获取编码的标头,编码的有效负载,机密,标头中指定的算法,并对其进行签名。

例如,若是要使用HMAC SHA256算法,则将经过如下方式建立签名:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

签名用于验证消息在此过程当中没有更改,而且对于使用私钥进行签名的令牌,它还能够验证JWT的发送者是它所说的真实身份。

组合在一块儿以下为输出是三个由点分隔的Base64-URL字符串:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUyMDAiLCJhdWQiOlsiYXBpIiwiYXBpIl0sIkd1aWQiOiIzM2NhZmJkNS1jZWEyLTRjOWMtYWZlYS01MDYyZjM3YWUyOTAiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOlsic3lzdGVtIiwiYWRtaW4iXSwiZXhwIjoxNjA2NjU2NjI0fQ.
JvDHuowbOnWiyxMIFc9gG5rw1LSSc0xx68L31oRfxS0

如何使用JWT

每当用户想要访问受保护的路由或资源时,用户代理都应发送JWT,一般使用承载模式Authorization标头中发送JWT 标头的内容应以下所示:

Authorization: Bearer <token>

在某些状况下,这能够是无状态受权机制。服务器的受保护路由将在Authorization标头中检查有效的JWT ,若是存在,则将容许用户访问受保护的资源。若是JWT包含必要的数据,则能够减小查询数据库中某些操做的需求,尽管这种状况并不是老是如此。

若是令牌是在Authorization标头中发送的,则跨域资源共享(CORS)不会成为问题,由于它不使用cookie。

下图显示了如何获取JWT并将其用于访问API或资源:

JSON Web令牌如何工做

  1. 应用程序或客户端向受权服务器请求受权。这是经过不一样的受权流程之一执行的。例如,典型的符合OpenID Connect的Web应用程序将/oauth/authorize使用受权代码流经过端点
  2. 授予受权后,受权服务器会将访问令牌返回给应用程序。
  3. 该应用程序使用访问令牌来访问受保护的资源(例如API)。

请注意,使用签名的令牌,令牌中包含的全部信息都会暴露给用户或其余方,即便他们没法更改它。这意味着您不该将机密信息放入令牌中。

.net core的JWT验证受权

咱们直接新建一个.net core webapi的项目,我这里版本是3.1的

1. 先使用nuget安装:Microsoft.AspNetCore.Authentication.JwtBearer。注意版本和.net core版本的兼容。net5的支持5.0.0+的版本,不然就用对应能够用的低版本吧。

2. 在appsettings.json配置文件中写好咱们的 JWT 的配置参数以下:

  "Jwt": {
    "Secret": "your-256-bit-secret",
    "Iss": "https://localhost:44355",
    "Aud": "api"
  }

3. 在Startup.cs的ConfigureServices方法中添加受权认证以下:

var jwtConfig = Configuration.GetSection("Jwt");
//生成密钥
var symmetricKeyAsBase64 = jwtConfig.GetValue<string>("Secret");
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
//认证参数
services.AddAuthentication("Bearer")
    .AddJwtBearer(o =>
    {
        o.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuerSigningKey = true,//是否验证签名,不验证的画能够篡改数据,不安全
            IssuerSigningKey = signingKey,//解密的密钥
            ValidateIssuer = true,//是否验证发行人,就是验证载荷中的Iss是否对应ValidIssuer参数
            ValidIssuer = jwtConfig.GetValue<string>("Iss"),//发行人
            ValidateAudience = true,//是否验证订阅人,就是验证载荷中的Aud是否对应ValidAudience参数
            ValidAudience = jwtConfig.GetValue<string>("Aud"),//订阅人
            ValidateLifetime = true,//是否验证过时时间,过时了就拒绝访问
            ClockSkew = TimeSpan.Zero,//这个是缓冲过时时间,也就是说,即便咱们配置了过时时间,这里也要考虑进去,过时时间+缓冲,默认好像是7分钟,你能够直接设置为0
            RequireExpirationTime = true,
        };
    });

在Configure方法中添加 app.UseAuthentication() 和 app.UseAuthorization() 注意位置须要放置的位置:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}
    app.UseHttpsRedirection();
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseEndpoints(endpoints =>{ endpoints.MapControllers();});
}

4. 生成jwt令牌,在默认生成的控制器 WeatherForecastController 中添加以下生成令牌的方法:

[HttpPost]
public IActionResult Authenticate()
{
    var jwtConfig = Configuration.GetSection("Jwt");
    //秘钥,就是标头,这里用Hmacsha256算法,须要256bit的密钥
    var securityKey = new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtConfig.GetValue<string>("Secret"))), SecurityAlgorithms.HmacSha256);
    //Claim,JwtRegisteredClaimNames中预约义了好多种默认的参数名,也能够像下面的Guid同样本身定义键名.
    //ClaimTypes也预约义了好多类型如role、email、name。Role用于赋予权限,不一样的角色能够访问不一样的接口
    //至关于有效载荷
    var claims = new Claim[] {
                new Claim(JwtRegisteredClaimNames.Iss,jwtConfig.GetValue<string>("Iss")),
                new Claim(JwtRegisteredClaimNames.Aud,jwtConfig.GetValue<string>("Aud")),
                new Claim("Guid",Guid.NewGuid().ToString("D")),
                new Claim(ClaimTypes.Role,"system"),
                new Claim(ClaimTypes.Role,"admin"),
    };
    SecurityToken securityToken = new JwtSecurityToken(
        signingCredentials: securityKey,
        expires: DateTime.Now.AddMinutes(2),//过时时间
        claims: claims
    );
    //生成jwt令牌
    return Content(new JwtSecurityTokenHandler().WriteToken(securityToken));
}

5. 使用jwt控制接口的访问,咱们在一个接口上添加一个特性 [Authorize(Roles ="admin")],表示须要有admin这个角色的jwt令牌才能访问,没有roles参数的话表示只要是可用的令牌就能够访问,多个role角色能够叠加多个特性:

[HttpGet]
[Authorize(Roles = "admin")]
[Authorize(Roles = "system")]
public IEnumerable<WeatherForecast> Get()
{
    var rng = new Random();
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = rng.Next(-20, 55),
        Summary = Summaries[rng.Next(Summaries.Length)]
    })
    .ToArray();
}

6.测试,而后咱们用postman就能够测试了,能够看到很是成功有数据。

 

 

 进阶

认证的时候能够添加事件,以下面的认证失败事件、接收参数事件能够获取url上的参数做为令牌而不是经过http的请求头的Authorization

services.AddAuthentication("Bearer")
    .AddJwtBearer(o =>
    {
        o.Events = new JwtBearerEvents()
        {
            OnMessageReceived = context =>
            {
                context.Token = context.Request.Query["access_token"];
                return Task.CompletedTask;
            },
            OnAuthenticationFailed = context =>
            {
                // 若是过时,则把<是否过时>添加到,返回头信息中
                if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                {
                    context.Response.Headers.Add("Token-Expired", "true");
                }
                return Task.CompletedTask;
            }
        };
    });
相关文章
相关标签/搜索