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

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

微服务系列之授权认证(三) 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) ,虽然没有解决,但是问题抛出的很细致,,我来说说我们现在正在使用的方案。

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

其实也很简单,用户请求后端服务数据时,作为有效动作,并且触发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是否一致,来判断是否是当前用户的操作。

发表评论

评论已关闭。

相关文章