jwt测试

1引用包IdentityModel和System.IdentityModel.Tokens.Jwt和Microsoft.AspNetCore.Authorization

2在appsetting设置相关参数(根据自己需求)

"JwtSettings": {
    "PrivateKey": "TheKeyOfPrivate",
    "Issuer": "https://localhost:5000",
    "Audience": "https://localhost:5001",
    "SecurityKey": "Hellokeydfasdfasoajfaspa",
    "ExpireSeconds": 2000

  }

3创建一个帮助类

 public class JwtSetting
    {
        /// <summary>
        /// 私钥
        /// </summary>
        public string PrivateKey { get; set; }

        /// <summary>
        /// token是谁颁发的
        /// </summary>
        public string Issuer { get; set; }
                      
        /// <summary>
        /// token可以给哪些客户端使用
        /// </summary>
        public string Audience { get; set; }

        /// <summary>
        /// 加密的key(SecurityKey
        /// 必须大于16个,是大于,不是大于等于)
        /// </summary>
        public string SecurityKey { get; set; }

        /// <summary>
        /// 过期时间
        /// </summary>
        public int ExpireSeconds { get; set; }


    }

4在startup中注册

在swagger中添加

 options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
                {
                    Description = "JWT Authorization header using the Bearer scheme.",
                    Name = "Authorization",
                    In = ParameterLocation.Header,     //net core 3.1和net core2.2 在这里有差距
                    Scheme = "bearer",
                    Type = SecuritySchemeType.Http,   //这里也是一样
                    BearerFormat = "JWT"

                });
 options.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference=new OpenApiReference{ Type=ReferenceType.SecurityScheme, Id="Bearer"}
                        },
                        new List<string>()
                    }
                });

再注册

 //将appsettings.json中的JwtSettings部分文件读取到JwtSettings中,这是给其他地方用的
            services.Configure<JwtSetting>(Configuration.GetSection("JwtSettings"));
            //由于初始化的时候我们就需要用,所以使用Bind的方式读取配置
            //将配置绑定到JwtSettings实例中
            var jwtSetting = new JwtSetting();
            Configuration.Bind("JwtSettings", jwtSetting);
            //添加身份验证
            services.AddAuthentication(options =>
            {
                //认证middleware配置
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            })
            .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
             {
                 
                 //jwt token参数设置   验证参数是否一样
                 o.TokenValidationParameters = new TokenValidationParameters
                 {
                     NameClaimType = JwtClaimTypes.Name,
                     RoleClaimType = JwtClaimTypes.Role,
                     ValidIssuer = jwtSetting.Issuer,
                     ValidAudience = jwtSetting.Audience,
                     IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)),
                     /***********************************TokenValidationParameters的参数默认值***********************************/
                     // RequireSignedTokens = true,
                     // SaveSigninToken = false,
                     // ValidateActor = false,
                     // 将下面两个参数设置为false,可以不验证Issuer和Audience,但是不建议这样做。
                     // ValidateAudience = true,
                     // ValidateIssuer = true, 
                     // ValidateIssuerSigningKey = false,
                     // 是否要求Token的Claims中必须包含Expires
                     // RequireExpirationTime = true,
                     // 允许的服务器时间偏移量
                     // ClockSkew = TimeSpan.FromSeconds(300),
                     // 是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                     // ValidateLifetime = true

                 };
             });
services.AddCors(options => options.AddPolicy("AllowCors",     ///添加跨域
                builder =>
                {
                    builder.AllowAnyOrigin()
                           .AllowAnyMethod()
                           .AllowAnyHeader();
                }));

在configure

            //身份授权认证
            app.UseAuthentication();
            app.UseAuthorization();
            //跨域
            app.UseCors("AllowCors");  //这里是webapi ,mvc模式要放mvc前面

5创建service

 public string GetToken(string privateKey)
        {

            if (privateKey != _jwtSetting.PrivateKey)
            {
                return null;
            }
            var claims = new List<Claim>
            { 
                //jwt的唯一身份标识,主要用来作为一次性token,从而避免重放攻击
                new Claim(JwtRegisteredClaimNames.Jti,privateKey),
                // 令牌颁发时间
                new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                //定义在什么时间之前,该jwt都是不可用的
                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                 // 过期时间 100秒
                new Claim(JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(_jwtSetting.ExpireSeconds)).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Iss,"API"), // 签发者
                new Claim(JwtRegisteredClaimNames.Aud,"User") // 接收者
                
            };

            //sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
            var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtSetting.SecurityKey));
            var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
            //.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
            /**
             * Claims (Payload)
                Claims 部分包含了一些跟这个 token 有关的重要信息。 JWT 标准规定了一些字段,下面节选一些字段:

                iss: The issuer of the token,token 是给谁的
                sub: The subject of the token,token 主题
                exp: Expiration Time。 token 过期时间,Unix 时间戳格式
                iat: Issued At。 token 创建时间, Unix 时间戳格式
                jti: JWT ID。针对当前 token 的唯一标识
                除了规定的字段外,可以包含其他任何 JSON 兼容的字段。
             * */
            var token = new JwtSecurityToken(
                 
                issuer: _jwtSetting.Issuer,  //这里有默认值null,如果有值,它会把claim中的iss对应的值加上,其他的也一样
                audience: _jwtSetting.Audience,
                claims: claims,
                notBefore: DateTime.Now,
                expires: DateTime.Now.AddHours(1),
                signingCredentials: creds);
            string returnToken = new JwtSecurityTokenHandler().WriteToken(token);


            _memoryCacheHelper.Set(privateKey, returnToken, System.TimeSpan.FromSeconds(1800.00));  //这里设置缓存,不需要的可以去掉
            return returnToken;
        }

解析

 public string SerializeJwt(string jwtStr)
        {
            var token = _memoryCacheHelper.Get("TheKeyOfPrivate");
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
            object role;
            try
            {
                jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
            }
            catch(Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
            var secret = jwtToken.Payload["jti"];
            //测试
            foreach (var item in jwtToken.Claims)
            {
                var value = item.Value;
                Console.WriteLine(value);
            }
            
            return "test";
        }

6在cotroller中调用service中的生成方法,生成token

7在swagger中的authorize把生成的token加入,有的前面需要加Bearer +空格+token ,这里直接保存token的值就行

7在其他需要鉴权的cotroller中下action添加 [Authorize]  就行

 

刷新refreshing Token

 var jwtTokenHandler = new JwtSecurityTokenHandler();
            try
            {
                // Validation 1 - Validation JWT token format
                // 此验证功能将确保 Token 满足验证参数,并且它是一个真正的 token 而不仅仅是随机字符串
                var tokenInVerification = jwtTokenHandler.ValidateToken(tokenRequest.Token, tokenValidationParams, out var validatedToken);

                // Validation 2 - Validate encryption alg
                // 检查 token 是否有有效的安全算法
                if (validatedToken is JwtSecurityToken jwtSecurityToken)
                {
                    var result = jwtSecurityToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256, StringComparison.InvariantCultureIgnoreCase);

                    if (result == false)
                    {
                        return null;
                    }
                }

                // Validation 3 - validate expiry date
                // 验证原 token 的过期时间,得到 unix 时间戳
                var utcExpiryDate = long.Parse(tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Exp).Value);

                var expiryDate = DateTimeHelper.UnixTimeStampToDateTime(utcExpiryDate);

                if (expiryDate > DateTime.Now)
                {
                    return new AuthResult()
                    {
                        Success = false,
                        Errors = new List<string>() { "Token has not yet expired" }
                    };
                }
                // validation 4 - validate existence of the token
                // 验证 refresh token 是否存在,是否是保存在数据库的 refresh token
                var storedRefreshToken = await db.Queryable<RefreshToken>().FirstAsync(x => x.Token == tokenRequest.RefreshToken);
                if (storedRefreshToken == null)
                {
                    return new AuthResult()
                    {
                        Success = false,
                        Errors = new List<string>() { "Refresh Token does not exist" }
                    };
                }

                // Validation 5 - 检查存储的 RefreshToken 是否已过期
                // Check the date of the saved refresh token if it has expired
                if (DateTime.Now > storedRefreshToken.ExpiryDate)
                {
                    return new AuthResult()
                    {
                        Errors = new List<string>() { "Refresh Token has expired, user needs to re-login" },
                        Success = false
                    };
                }

                // Validation 8 - validate the id
                // 这里获得原 JWT token Id
                var jti = tokenInVerification.Claims.FirstOrDefault(x => x.Type == JwtRegisteredClaimNames.Jti).Value;

                // 根据数据库中保存的 Id 验证收到的 token 的 Id
                if (storedRefreshToken.JwtId != jti)
                {
                    return new AuthResult()
                    {
                        Success = false,
                        Errors = new List<string>() { "The token doesn't mateched the saved token" }
                    };
                }
                // 生成一个新的 token
                var dbUser = await db.Queryable<SysUser>().FirstAsync(c => c.UserGuid == storedRefreshToken.UserId);
                return await GenerateJwtToken(jwtSetting, dbUser);
            }
            catch (Exception ex)
            {
                if (ex.Message.Contains("Lifetime validation failed. The token is expired."))
                {
                    return new AuthResult()
                    {
                        Success = false,
                        Errors = new List<string>() { "Token has expired please re-login" }
                    };
                }
                else
                {
                    return new AuthResult()
                    {
                        Success = false,
                        Errors = new List<string>() { "Something went wrong." }
                    };
                }
            }
 public class TokenRequest
    {
        
        public string Token { get; set; }

        /// <summary>
        /// Refresh Token
        /// </summary>
       
        public string RefreshToken { get; set; }
    }
 public class AuthResult
    {
        public string Token { get; set; }
        public string RefreshToken { get; set; }
        public bool Success { get; set; }
        public List<string> Errors { get; set; }
    }

 


SysUser也是一个实体,登录验证的时候,返回sysuser的信息和token,refreshtoken的值,用来调用刷新token的参数,也可以存缓存里面比如redis中

 

授权不通过自定义返回数据类型,方便前端识别

 public class ApiResponseHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        public ApiResponseHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) : base(options, logger, encoder, clock)
        {
        }

        protected override Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            throw new NotImplementedException();
        }

        protected override async Task HandleChallengeAsync(AuthenticationProperties properties)
        {
            Response.ContentType = "application/json";  
            Response.StatusCode = StatusCodes.Status401Unauthorized;
            await Response.WriteAsync(JsonConvert.SerializeObject(new AuthResult(StatusCode.CODE401)));  AuthResult返回的结果列
        }

        protected override async Task HandleForbiddenAsync(AuthenticationProperties properties)
        {
            Response.ContentType = "application/json";
            Response.StatusCode = StatusCodes.Status403Forbidden;
            await Response.WriteAsync(JsonConvert.SerializeObject(new AuthResult(StatusCode.CODE403)));
        }
    }

在startup中

 services.AddAuthentication(o =>
            {
                o.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
                o.DefaultChallengeScheme = nameof(ApiResponseHandler);
                o.DefaultForbidScheme = nameof(ApiResponseHandler);

            })
              // 添加JwtBearer服务
              .AddJwtBearer(o =>
              {
                  o.TokenValidationParameters = new TokenValidationParameters
                  {
                      NameClaimType = JwtClaimTypes.Name,
                      RoleClaimType = JwtClaimTypes.Role,
                      ValidIssuer = jwtSetting.Issuer,
                      ValidAudience = jwtSetting.Audience,
                      IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtSetting.SecurityKey)),
                      //是否验证token有效期,使用当前时间与token的claims中的notbefore和expires对比
                      ValidateLifetime = true,
                      //注意这里是缓冲过期时间,总的有效时间等于这个时间加上jwt的过期时间,如果不配置,默认是5分钟
                      ClockSkew = System.TimeSpan.FromSeconds(5)
                  };
              })
              .AddScheme<AuthenticationSchemeOptions, ApiResponseHandler>(nameof(ApiResponseHandler), o => { });

 后续还有一个问题,当验证token失败或者过期时,返回的都是401,我怎么识别时过期还是没有其他?

posted @ 2020-07-04 18:07  青兰柳  阅读(355)  评论(0编辑  收藏  举报