3

微服务系列之授权认证(三) JWT - CL静淡

 1 year ago
source link: https://www.cnblogs.com/saltlight-wangchao/p/16715127.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

微服务系列之授权认证(三) 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。如:

846824-20220921104755500-210190313.png

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

846824-20220921105006781-732792137.png

注意:虽然签名过后的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) ,虽然没有解决,但是问题抛出的很细致,,我来说说我们现在正在使用的方案。

846824-20220921115402650-1049888251.png

其实也很简单,用户请求后端服务数据时,作为有效动作,并且触发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();//添加认证中间件

控制器代码

/// <summary>
        /// 编辑用户信息
        /// </summary>
        /// <param name="userId"></param>
        /// <param name="command"></param>
        /// <returns></returns>
        [HttpPost("{userId}/edit/userInfo")]
        [ProducesResponseType(typeof(Users), (int)HttpStatusCode.OK)]
        [ProducesResponseType(typeof(string), (int)HttpStatusCode.BadRequest)]
        [Authorize]//token认证标签,如果需要角色认证,[Authorize(Roles ="admin")]
        public async Task<IActionResult> 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是否一致,来判断是否是当前用户的操作。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK