.NET CORE Jwt授权及失效重新获取

目录

 

前言

StartUp中ConfigureServices注册

Appsetting配置,参数需自定义

完整登陆接口如下

用到一个自用封装类

客户端请求

结语


前言

本文主要讲述了在.net core下实现JWT授权以及Token失效之后如何刷新Token,关于JWT请参考如下文章JWT,TODO:萌新劝退,本文需要具有一定的基础知识。照抄可能你会什么都不懂。

 

以下正文:

StartUp中ConfigureServices注册

#region jwt
            //读取Jwt 配置文件
            services.Configure(Configuration.GetSection("JwtSettings"));
            //绑定到实例中
            var jwt = new JwtSetting();
            Configuration.Bind("JwtSettings", jwt);
            //添加身份验证
            services.AddAuthentication(options =>
            {
                //认证middleware配置
                options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
            }).AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new TokenValidationParameters
                {
                    NameClaimType = JwtClaimTypes.Name,
                    RoleClaimType = JwtClaimTypes.Role,
                    //Token颁发机构
                    ValidIssuer = jwt.Issuer,
                    //颁发给谁
                    ValidAudience = jwt.Audience,
                    //这里的key要进行加密
                    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwt.SecretKey)),
                    ValidateIssuer = true,//是否验证Issuer
                    ValidateAudience = true,//是否验证Audience
                    ValidateLifetime = true,//是否验证失效时间
                    ValidateIssuerSigningKey = true,//是否验证SecurityKey
                    ClockSkew = TimeSpan.Zero,//校验时间是否过期时,设置的时钟偏移量
                };
                //捕捉令牌过期
                o.Events = new JwtBearerEvents
                {
                    OnAuthenticationFailed = context =>
                      {
                          if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                          {
                              context.Response.Headers.Add("act", "expired");
                          }
                          return Task.CompletedTask;
                      }
                };
            });
            #endregion

Appsetting配置,参数需自定义

"JwtSettings": {
    "Issuer": "",
    "Audience": "",
    "SecretKey": "",
    "AccessTokenExpiresMinutes": "35",
    "RefreshTokenAudience": "RefreshTokenAudience",
    "RefreshTokenExpiresMinutes": "240" //4Сʱ
  }

JwtSetting类

/// 
    /// Jwt
    /// 
    public class JwtSetting
    {
        /// 
        /// token是谁颁发的
        /// 
        public string Issuer { get; set; }

        /// 
        /// token可以给那些客户端使用
        /// 
        public string Audience { get; set; }

        /// 
        /// 加密的key(SecretKey必须大于16个,是大于,不是大于等于)
        /// 
        public string SecretKey { get; set; }
        /// 
        /// token过期时间
        /// 
        public string AccessTokenExpiresMinutes { get; set; }
        /// 
        /// refresh Token
        /// 
        public string RefreshTokenAudience { get; set; }
        /// 
        /// refresh Token  exprise
        /// 
        public string RefreshTokenExpiresMinutes { get; set; }
    }

StartUp中Configure注入身份授权,需在UseMvc之前

//身份授权认证
app.UseAuthentication();

登入方法

/// 
        /// 获取Token
        /// 
        /// 
        /// 
        private async Task GetTokenAsync(Users users)
        {
            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.UTF8.GetBytes(jwt.SecretKey);
            var authTime = DateTime.Now;
            var expiresAt = authTime.AddMinutes(double.Parse(jwt.AccessTokenExpiresMinutes));
            var tokenDescripor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[] {
                    new Claim(JwtClaimTypes.Audience,jwt.Audience),
                    new Claim(JwtClaimTypes.Issuer,jwt.Issuer),
                    new Claim(JwtClaimTypes.NickName, users.NickName),
                    new Claim(JwtClaimTypes.Id, users.Id.ToString()),
                    new Claim(JwtClaimTypes.Name, users.UserName),
                    new Claim(JwtClaimTypes.Role,users.Roles.Id.ToString())
                }),
                Expires = expiresAt,
                //对称秘钥SymmetricSecurityKey
                //签名证书(秘钥,加密算法)SecurityAlgorithms
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescripor);
            var tokenString = tokenHandler.WriteToken(token);
            #region refreshToken
            //获取刷新token
            var refresh = GenerateRefreshToken();
            //获取缓存的token keys
            var reTokens = await _cache.GetRefreshTokens();
            await _cache.RemoveAsync(CacheKey.RefreshTokens);
            if (!reTokens.Any(t => t.Issuer.Id == users.Id))
            {
                reTokens.Add(new RefreshToken()
                {
                    Token = refresh,
                    Exprise = DateTime.Now.AddMinutes(double.Parse(jwt.RefreshTokenExpiresMinutes)),
                    Issuer = users
                });
                await _cache._cache.SetObjectAsync("RefreshTokens", reTokens);
            }
            else
            {
                reTokens.ForEach(delegate (RefreshToken refreshToken)
                {
                    if (refreshToken.Issuer.Id == users.Id)
                    {
                        refreshToken.Token = refresh;
                        refreshToken.Exprise = DateTime.Now.AddMinutes(double.Parse(jwt.RefreshTokenExpiresMinutes));
                    }
                });
                await _cache._cache.SetObjectAsync("RefreshTokens", reTokens);
            }
            #endregion
            return new AccountRes
            {
                AccessToken = tokenString,
                RefreshToken = refresh,
                TokenType = "Bearer",
                Profile = new Profiles
                {
                    Name = users.UserName,
                    NickName = users.NickName,
                    Auth_Time = authTime,
                    Expires_At = expiresAt
                }
            };
        }

        /// 
        /// 生成refresh Token
        /// 
        /// 
        private string GenerateRefreshToken()
        {
            var randomNumber = new byte[32];
            using (var rng = RandomNumberGenerator.Create())
            {
                rng.GetBytes(randomNumber);
                return Convert.ToBase64String(randomNumber);
            }
        }

注意:登入方法同时生成了一个RefreshToken,它将在Token失效时,进行调用生产新的Token并返回结果集

对外接口:Token失效获取新的Token

        /// 
        /// 刷新Token
        /// 
        /// 
        [HttpPost]
        [Route("RefreshToken")]
        [AllowAnonymous]
        [Filter.SkipAuthorization]
        public async Task RefreshToken()
        {
            try
            {
                var token = GetParams("token");
                if (string.IsNullOrEmpty(token))
                    return Result.Fail(HttpStatusCode.NoContent, "invalid refreshToken !");
                var reTokens = await _cache.GetRefreshTokens();
                var tokenUser = reTokens?.SingleOrDefault(t => t.Token.ToLower().Equals(token.ToLower()));
                var obj = new AccountRes();
                if (tokenUser != null)
                {
                    //Token存在并且有效
                    if (tokenUser.Token.ToLower().Equals(token.ToLower()) && DateTime.Now < tokenUser.Exprise)
                    {
                        obj = await GetTokenAsync(tokenUser.Issuer);
                    }
                }
                else
                    return Result.Fail(HttpStatusCode.NotModified, "refreshtoken error !");
                if (obj != null && !string.IsNullOrEmpty(obj.AccessToken))
                    return Result.Success(obj);
                else
                    return Result.Fail(HttpStatusCode.NotModified, "refreshtoken error !");
            }
            catch (Exception c)
            {
                _logger.LogError(c, $"【{GetCurrentUser(_context).NickName}】刷新Token失败。");
                return Result.Fail("refresh token error !");
            }
        }

注:[AllowAnonymous] 允许此接口不经授权即可访问。[Filter.SkipAuthorization]我重写的对外接口拦截器,需要自用

完整登陆接口如下

/// 
        /// 登录
        /// 
        /// 
        [Route("Login")]
        [AllowAnonymous]
        [HttpPost]
        public async Task Login()
        {
            try
            {
                string username = GetParams("username");
                string pwd = GetParams("pwd");
                if (string.IsNullOrEmpty(username) || string.IsNullOrEmpty(pwd))
                    return Result.Fail(HttpStatusCode.NoContent, "invalid username or pwd !");
                if (username.IndexOfAny(System.IO.Path.GetInvalidFileNameChars()) >= 0 || username.Reg())
                    return Result.Fail(HttpStatusCode.NonAuthoritativeInformation, "invalid username !");
                var user = await _context.Users.Where(t => t.UserName.Equals(username) && t.PassWord.ToUpper().Equals(pwd.Md5().ToUpper()))?.Include(a => a.Roles).FirstOrDefaultAsync();
                if (user != null)
                {
                    //Jwt登录
                    var obj = await GetTokenAsync(user);

                    #region 写入登录日志
                    //写入登录日志
                    var ip = HttpContext.Connection.RemoteIpAddress.ToString();
                    _context.Loginlogs.Add(new Loginlog()
                    {
                        Users = user,
                        LoginIp = ip,
                        LoginAddress = Ip.GetAddress(ip)
                    });
                    await _context.SaveChangesAsync();
                    #endregion
                    return Result.Success(obj);
                }
                else
                    return Result.Fail(HttpStatusCode.NotFound, "incorrect user name or password !");
            }
            catch (Exception c)
            {
                _logger.LogError(c, $"【{GetParams("username")}】登入系统出错。");
                return Result.Fail("login system error !");
            }
        }

用到一个自用封装类

// 
    /// 返回结果集
    /// 
    public class Result
    {
        internal const string DefaultExceptionMessage = "未知错误!";

        /// 
        /// 构造方法
        /// 
        public Result()
        {
            this.Code = HttpStatusCode.NotFound;
            this.Msg = "操作失败";
            this.Error = "";
            this.Url = "";
        }

        /// 
        /// 返回成功的对象
        /// 
        /// 
        /// 
        public Result Success(string msg)
        {
            if (string.IsNullOrEmpty(msg)) msg = "操作成功";
            return new Result
            {
                Code = HttpStatusCode.OK,
                Msg = msg,
                Error = "",
                Url = ""
            };
        }

        /// 
        /// 返回成功的对象
        /// 
        /// 
        public static Result Success()
        {
            return Success(string.Empty);
        }

        /// 
        /// 返回成功的带结果集的对象
        /// 
        /// 
        /// 
        /// 
        /// 
        public static Result Success(string msg, T data)
        {
            if (string.IsNullOrWhiteSpace(msg)) msg = "操作成功!";
            return new Result
            {
                Code = HttpStatusCode.OK,
                Msg = msg,
                Error = "",
                Url = "",
                Data = data
            };
        }

        /// 
        /// 返回成功的带结果集的对象
        /// 
        /// 
        /// 
        /// 
        public static Result Success(T data)
        {
            return Success(string.Empty, data);
        }

        /// 
        /// 返回失败的带结果集的对象
        /// 
        /// 
        /// 
        /// 
        /// 
        public static Result Fail(string msg, T data)
        {
            if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
            return new Result
            {
                Code = HttpStatusCode.BadRequest,
                Msg = msg,
                Error = "",
                Url = "",
                Data = data
            };
        }

        /// 
        /// 返回失败的对象
        /// 
        /// 
        /// 
        /// 
        public static Result Fail(HttpStatusCode code, string msg)
        {
            if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
            return new Result
            {
                Code = code,
                Msg = msg,
                Error = "",
                Url = ""
            };
        }

        /// 
        /// 返回失败的带结果集的对象
        /// 
        /// 
        /// 
        /// 
        /// 
        /// 
        public static Result Fail(HttpStatusCode code, string msg, T data)
        {
            if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
            return new Result
            {
                Code = code,
                Msg = msg,
                Error = "",
                Url = "",
                Data = data
            };
        }

        /// 
        /// 返回失败的带结果集的对象
        /// 
        /// 
        /// 
        /// 
        public static Result Fail(string msg)
        {
            return Fail(msg, default);
        }

        /// 
        /// 返回失败的对象
        /// 
        /// 
        /// 
        public static Result Fail(string msg)
        {
            if (string.IsNullOrWhiteSpace(msg)) msg = "操作失败!";
            return new Result
            {
                Code = HttpStatusCode.BadRequest,
                Msg = msg,
                Error = "",
                Url = ""
            };
        }

        #region 扩展方法
        /// 
        /// 异步返回结果集
        /// 
        /// 
        /// 
        /// 
        public static async Task> GetAsync(T data)
        {
            return await Task>.Factory.StartNew(() =>
            {
                try
                {
                    return Success(data);
                }
                catch (Exception c)
                {
                    return Fail(c.ToString());
                }
            });
        }

        /// 
        /// 异步返回结果集
        /// 
        /// 
        /// 
        /// 
        /// 
        public static async Task> GetAsync(string msg, T data)
        {
            return await Task>.Factory.StartNew(() =>
            {
                try
                {
                    return Success(msg, data);
                }
                catch (Exception c)
                {
                    return Fail(c.ToString());
                }
            });
        }

        /// 
        /// 异步返回结果集
        /// 
        /// 
        public static async Task GetAsync()
        {
            return await Task.Factory.StartNew(() =>
            {
                try
                {
                    return Success();
                }
                catch (Exception c)
                {
                    return Fail(c.ToString());
                }
            });
        }

        #endregion

        /// 
        /// 状态码
        /// 
        public HttpStatusCode Code { get; set; }
        /// 
        /// 消息
        /// 
        public string Msg { get; set; }
        /// 
        /// 错误
        /// 
        public string Error { get; set; }
        /// 
        /// 错误Url
        /// 
        public string Url { get; set; }

    }


    public class Result : Result
    {
        private T _data = default;

        /// 
        /// 构造方法
        /// 
        public Result()
            : base()
        {
            _data = default;
        }

        /// 
        /// 操作结果业务数据
        /// 
        public T Data
        {
            get
            {
                if (typeof(T).BaseType == typeof(IEnumerator))
                    _data = Activator.CreateInstance();
                return _data;
            }
            set
            {
                _data = value;
            }
        }

        /// 
        /// 返回成功的带消息提示对象
        /// 
        /// 
        /// 
        /// 
        public static Result Success(string msg, T data)
        {
            return Success(msg, data);
        }

        /// 
        /// 返回成功的带消息提示对象
        /// 
        /// 
        /// 
        public static Result Success(T data)
        {
            return Success(string.Empty, data);
        }

        /// 
        /// 返回失败的带消息提示对象
        /// 
        /// 
        /// 
        /// 
        public static Result Fail(string msg, T data)
        {
            return Fail(msg, data);
        }

        /// 
        /// 返回失败的带消息提示对象
        /// 
        /// 
        /// 
        public static new Result Fail(string msg)
        {
            return Fail(msg, default);
        }

        public static Result Fail(HttpStatusCode code, string msg,T data)
        {
            return Fail(code, msg, data);
        }

    }

客户端请求

建议将服务端的登陆返回结果存放到客户端cookie中,使用cookie拉取登陆之后返回的Token

.NET CORE Jwt授权及失效重新获取_第1张图片

客户端逻辑如上图,请自行实现。

Todo:客户端同步缓存你的登陆信息,Token过期捕捉到然后发起RefreshToken请求。登陆方法中部分我自用逻辑,建议参考实现。

结语

写在最后,文中有错误之处请在下方留言。转载需注明出处

个人博客:欢迎关注我的博客

 

你可能感兴趣的:(.Net,Core,C#)