• .Net Core官方的 JWT 授权验证


    什么是JWT?

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

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

    什么时候应该使用JWT?

    以下是JSON Web令牌有用的一些情况:

    • 授权:这是使用JWT的最常见方案。一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单一登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。

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

    JWT结构是什么?

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

    • 标头
    • 有效载荷
    • 签名

    因此,JWT通常如下所示。

    xxxxx.yyyyy.zzzzz

    标头:通常由两部分组成,令牌类型和使用的签名算法。

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

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

    • 预定义声明:包括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;
                }
            };
        });

     如果一个接口提供给很多的角色访问,那特性[Authorize]会写很多或者很长,也非常的麻烦,所以可以对不同的角色建立不同的策略,只需要在starup.cs中添加角色策略如下:

    services.AddAuthorization(option => {
        option.AddPolicy("adminOrSystem", policy => policy.RequireRole("admin", "system"));
    });

    之后赋值特性[Authorize]的Policy参数即可:

    [Authorize(Policy = "adminOrSystem")]
  • 相关阅读:
    C# 集合类 Array Arraylist List Hashtable Dictionary Stack Queue
    Javascript在IE与Firefox下的差异
    ASP.NET状态服务及session丢失问题解决方案总结
    在Silverlight中读取指定URL图片包数据
    Cookie记住滚动条
    IHttpModule 与IHttpHandler的区别
    页面内容随滚动条滚动动态载入JavaScript脚本。
    Asp.net MVC Action大全(转)
    简单的css横向菜单
    实线表格Html
  • 原文地址:https://www.cnblogs.com/xwc1996/p/14058115.html
Copyright © 2020-2023  润新知