• 微服务系列之授权认证(三) JWT


    1.JWT简介

      官方定义:JWT是JSON Web Token的缩写,JSON Web Token是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,可以将各方之间的信息作为JSON对象安全地传输。该信息可以被验证和信任,因为它是经过加密的。

      实际上,Oauth2.0中的access token一般就是jwt格式。

      token由三部分组成,通过"."分隔,分别是:

          ● 标头

          ● 有效载荷

          ● 签名

      所以JWT表示为:aaaaa.bbbbb.ccccc组成。

      1)标头,Header通常由两部分组成:使用的加密算法 "alg" 以及Token的种类 "typ"。如下:

      

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

      此JSON被Base64Url编码以形成JWT的第一部分。

      2)有效荷载,Payload主要包含了声明Claims,声明实际就是key:value数据,主要包含以下三种声明:

      Registered Claims: 注册声明,为IANA JSON Web Token 注册表中预先定义好的声明,这些声明非强制性,但是建议使用,如

    • ●  iss(issuer):签发人

      ●  exp(expiration time) :过期时间

      ●  sub(subject):主题

      ●  aud(audience):受众

      ●  nbf(not befaore):生效时间

      ●  lat(issued at):签发时间

      ●  jti(jwt id):编号

      Public Claims:公共声明,名称可以被任意定义。为了防止重复,任何新的Claim名称都应该被定义在IANA JSON Web Token Registry中或者使用一个包含不易重复命名空间的URI。

      Private Claims:私有声明,是在团队中约定使用的自定义Claims,既不属于Registered也不属于Public。

      此JSON进行Base64Url编码形成JWT的第二部分

      3)Signature,签名是将第一部分(header)、第二部分(payload)、密钥(key)通过指定算法(HMAC、RSA)进行加密生成的。

      

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

    生成的签名就是JWT的第三部分。

    将这三部分拼接在一起并使用"."分隔后形成的字符串就是Token。如:

    可以使用jwt.io的Debugger解码,验证或生成JWT。

     

    注意:虽然签名过后的Token可以防止篡改,但是Token的信息是公开的,任何人都可以读取,所以尽量不要在有效载荷或标头传递敏感信息(如密码)。

    2.使用场景

    1)目前来说,几乎所有之前使用cookie,session的地方,都可以换成jwt。

    2)标准的对C或者对B的微服务系统,这个和之前讲的Oauth2.0协议最大的不同之处,jwt只是一个传输令牌,oauth2.0是一个授权协议,jwt可以理解为是oauth2.0的一部分。。。

    3)信息安全交换,由于签名防篡改机制,可以验证其发行人和收件人。

    3.Jwt的优势

    1)无需存储,无服务器压力,轻量级使用,简单上手。

    2)无视跨域,可多端使用,不像cookie、session依赖浏览器。

    4.前端滑动登录状态管理方案

    jwt token的过期时间如果短了,很影响前端用户操作体验,所以一般情况都是中长期的,以前的session管理登录状态,是滑动的,而现在jwt是无状态的,那么怎么才能做到滑动管理呢,具体细节分析请看这篇文章.NET Core WebAPI 认证授权之JWT(四):JWT续期问题 - 不落阁 (leo96.com) ,虽然没有解决,但是问题抛出的很细致,,我来说说我们现在正在使用的方案。

    其实也很简单,用户请求后端服务数据时,作为有效动作,并且触发2小时的计时器,没到2小时的定时器清除并且重新启动,如果2小时内用户没有任何动作,认为是可以退出登录。

    5..net core使用jwt

    nuget安装System.IdentityModel.Tokens.Jwt

    新建一个service写一个生成token的方法

    复制代码
     public class TokenService : ITokenService
        {
            private IConfiguration configuration;
    
            public TokenService(IConfiguration configuration)
            {
                this.configuration = configuration;
            }
    
            public async Task<string> MakeJwtToken(long userId)
            {
                var claims = new[]
                    {
                     // 角色需要在这里填写
             new Claim(ClaimTypes.Role, "Admin"),
             // 多个角色可以重复写,生成的 JWT 会是一个数组
                      new Claim(ClaimTypes.Role, "SuperAdmin"),
                      //其他声明
                      new Claim("uid", userId.ToString())
                    };
                //私钥,验证方也需要使用这个进行验证。
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Auth:SecurityKey"]));
                //加密方式
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                var token = new JwtSecurityToken(
                    issuer: "AESCR",//发行人
                    audience: "AESCR",//接收人
                    claims: claims,
                    expires: DateTime.Now.AddMonths(30),//过期时间
                    signingCredentials: creds);
                var res = new JwtSecurityTokenHandler().WriteToken(token);
                return await Task.FromResult<string>(res);
            }
        }
    复制代码

    启动类认证注入

    复制代码
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                    .AddJwtBearer(options => {
                        options.TokenValidationParameters = new TokenValidationParameters
                        {
                            ValidateIssuer = true,//是否验证发行人
                            ValidateAudience = true,//是否验证收件人
                            ValidateLifetime = true,//是否验证失效时间
                            ValidateIssuerSigningKey = true,//是否验证SecurityKey
                            ValidAudience = "AESCR",//Audience
                            ValidIssuer = "AESCR",//Issuer,这两项和后面签发jwt的设置一致
                            ClockSkew = TimeSpan.Zero, // // 默认允许 300s  的时间偏移量,设置为0
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Auth:SecurityKey"]))//与创建者密钥一致
                        };
                    });
    复制代码
      app.UseAuthentication();//添加认证中间件

    控制器代码

    复制代码
    /// 
            /// 编辑用户信息
            /// 
            /// 
            /// 
            /// 
            [HttpPost("{userId}/edit/userInfo")]
            [ProducesResponseType(typeof(Users), (int)HttpStatusCode.OK)]
            [ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
            [Authorize]//token认证标签,如果需要角色认证,[Authorize(Roles ="admin")]
            public async Task EditUserInfo([FromRoute] long userId, [FromBody] EditUserInfoCommand command)
            {
                if (!this.CheckUser(command.UserId))
                    return BadRequest("您没有权限访问");
    
                var result = await userService.EditUserInfo(command);
                if (!result.IsSuccess)
                    return BadRequest(result.FailureReason);
                return Ok(result.GetData());
            }
    复制代码

    以上代码就完事了,简单吧,在请求的时候,带上token就可以了。

    这里有一个细节问题,由于我们这里用户ID,都是在jwt的Payload中,那么是否还需要请求接口的时候在参数中传输呢?个人理解是这样:

    1.首先先看下token验证过后,怎么取claims的声明

    复制代码
     public static class ControllerExtensions
        {
            public static long GetUserId(this ControllerBase controllerBase)
            {
                var claim = controllerBase.User.Claims.Where(p => p.Type == "uid").FirstOrDefault();
                if (claim == null)
                    return 0;
                long res = 0;
                long.TryParse(claim.Value, out res);
                return res;
            }
    
            public static bool CheckUser(this ControllerBase controllerBase, long userId)
            {
                return controllerBase.GetUserId() == userId;
            }
        }
    复制代码

    我可以从payload中获取到创建token时带进去的用户id---uid,回到问题,我认为即使可以拿到用户ID,也需要从接口参数中传递过来,因为安全认证是一个切面拦截,我们的服务如果去掉切面,要保障正常运行,我们只需要在加一个传递参数中的uid和声明里的uid是否一致,来判断是否是当前用户的操作。

  • 相关阅读:
    C++ 练气期之一文看懂字符串
    windows开机自启动和忘记密码-备忘
    Qt笔记-解决子控制大小获取不正确(width和height)需要重制窗体后,才能获得正确的值
    设置 height: auto 却无法触发 transition 动画的解决方案
    股票交易接口的分类webService接口
    Matlab论文插图绘制模板第56期—曲面图(Surf)
    【完美世界】战王之殇特别篇定档,11月3日播,云曦受辱石昊杀红眼了
    2022pycharm:虚拟环境的启用与删除
    安全防御—— Virtual Private Network
    北方直播卖货搞钱“第一城“,竟然是临沂,200万人发家快手电商
  • 原文地址:https://www.cnblogs.com/saltlight-wangchao/p/16715127.html