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

json 复制代码
{
  "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)进行加密生成的。

scss 复制代码
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的方法

csharp 复制代码
 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);
        }
    }

启动类认证注入

ini 复制代码
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"]))//与创建者密钥一致
                    };
                });
scss 复制代码
  app.UseAuthentication();//添加认证中间件

控制器代码

csharp 复制代码
/// <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的声明

csharp 复制代码
 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是否一致,来判断是否是当前用户的操作。

相关推荐
天天扭码2 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
凡人的AI工具箱2 小时前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
运维&陈同学3 小时前
【zookeeper01】消息队列与微服务之zookeeper工作原理
运维·分布式·微服务·zookeeper·云原生·架构·消息队列
码上有前17 小时前
解析后端框架学习:从单体应用到微服务架构的进阶之路
学习·微服务·架构
gjh120820 小时前
什么是微服务?
微服务
问窗1 天前
微服务中Spring boot的包扫描范围
java·spring boot·微服务
聂 可 以1 天前
IDEA一键启动多个微服务
java·微服务·intellij-idea
晴子呀1 天前
微服务系列概览
微服务·云原生·架构
天草二十六_简村人2 天前
Java语言编程,通过阿里云mongo数据库监控实现数据库的连接池优化
java·jvm·数据库·mongodb·阿里云·微服务·云计算
编程广角镜2 天前
三十一、构建完善微服务——API 网关
运维·网络·微服务