Jwt的自定义使用 中间件+身份验证鉴权方式

 

本文实例环境及版本 NetCore 3.1

一、JWT简介

JWT其全称是JSON Web Token,官网地址:https://jwt.io/

通俗地说,JWT的本质就是一个字符串,它是将用户信息保存到一个Json字符串中,然后进行编码后得到一个JWT token,并且这个JWT token带有签名信息,接收后可以校验是否被篡改,所以可以用于在各方之间安全地将信息作为Json对象传输。

 

JWT认证的优势
对比传统的session认证方式,JWT的优势是:

1、JWT Token数据量小,传输速度也很快。因为JWT Token是以JSON加密形式保存在客户端的,所以JWT是跨语言的,原则上任何web形式都支持

2、不需要在服务端保存会话信息,也就是说不依赖于cookie和session,所以没有了传统session认证的弊端,特别适用于分布式微服务

3、单点登录友好 使用Session进行身份认证的话,由于cookie无法跨域,难以实现单点登录。但是,使用token进行认证的话, token可以被保存在客户端的任意位置的内存中,不一定是cookie,所以不依赖cookie,不会存在这些问题
适合移动端应用 使用Session进行身份认证的话,需要保存一份信息在服务器端,而且这种方式会依赖到Cookie(需要 Cookie 保存 SessionId),所以不适合移动端


二、具体使用

1、NuGet导入Microsoft.AspNetCore.Authentication、Microsoft.AspNetCore.Authentication.JwtBearer、Microsoft.IdentityModel.Tokens

2、定义一个认证用户信息实体类如下

    /// <summary>
    /// 用户信息model
    /// </summary>
    public class UserInfo
    {
        /// <summary>
        /// 用户编号
        /// </summary>
        public string userId { get; set; }
        /// <summary>
        /// 用户名称
        /// </summary>
        public string userName { get; set; }
        /// <summary>
        /// 角色、标识
        /// </summary>
        public string roleType { get; set; }/// <summary>
        /// Token的过期时间
        /// </summary>
        public DateTime expiresDate { get; set; }
    }

3、Jwt配置对象

    /// <summary>
    /// jwt配置对象
    /// </summary>
    public class JWTTokenOptions
    {
        /// <summary>
        /// Jwt认证Key
        /// </summary>
        public string SecurityKey { get; set; }
        /// <summary>
        /// 过期时间 单位为小时
        /// </summary>
        public int Expire { get; set; }
        /// <summary>
        /// 观众 相当于接受者受众者
        /// </summary>
        public string Audience { get; set; }
        /// <summary>
        /// 发行者
        /// </summary>
        public string Issuer { get; set; }
    }

4、配置文件appsettings.json 添加如下

 "JWTTokenOptions": {
    "Audience": "http://localhost:5200",
    "Issuer": "http://localhost:5200",
    "SecurityKey": "**************************", //用于生成token的秘钥 至少16位此处24位
    "Expire": "7" //过期时间 单位为天
  }

5、Jwt管理接口

    /// <summary>
    /// 用于生成Token的接口
    /// </summary>
    public interface IAuthManage
    {
        /// <summary>
        /// 生成JwtToken
        /// </summary>
        /// <param name="user">用户信息</param>
        /// <returns></returns>
        string GenerateJwtToken(UserInfo user);
    }

6、接口实现类

    /// <summary>
    /// 用于生成token的实现类
    /// </summary>
    public class MicrosoftJwtAuthManage : IAuthManage
    {
        private readonly JWTTokenOptions _authOptions;
        public MicrosoftJwtAuthManage(JWTTokenOptions authOptions)
        {
            _authOptions = authOptions;
        }

        public string GenerateJwtToken(UserInfo user)
        {
            var days = _authOptions.Expire.ToString();

            var tokenHandler = new JwtSecurityTokenHandler();
            var key = Encoding.ASCII.GetBytes(_authOptions.SecurityKey); //获取用于生成Token的key
            var tokenDescriptor = new SecurityTokenDescriptor
            {
                Subject = new ClaimsIdentity(new Claim[]
                {
                    new Claim("userInfo",user.ToJson())  //将实体类转json存入Token中
                }),
                Expires = DateTime.UtcNow.AddDays(_authOptions.Expire),//过期时间
                SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
            };
            var token = tokenHandler.CreateToken(tokenDescriptor);
            return tokenHandler.WriteToken(token);
        }
    }

7、验证Token的中间件

    /// <summary>
    /// JWT中间件 用于验证Token信息的
    /// </summary>
    public class JwtMiddleware
    {
        private readonly RequestDelegate _next;
        private readonly JWTTokenOptions _authOptions;

        public JwtMiddleware(RequestDelegate next, JWTTokenOptions authOptions)
        {
            _next = next;
            _authOptions = authOptions;
        }

        public async Task Invoke(HttpContext context)
        {
            //获取传递过来的Token,可自定义扩展
            var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last()
                        ?? context.Request.Headers["Token"].FirstOrDefault()
                        ?? context.Request.Query["Token"].FirstOrDefault()
                        ?? context.Request.Cookies["Token"];

            if (token != null)
                AttachUserToContext(context, token);

            await _next(context);
        }

        private void AttachUserToContext(HttpContext context, string token)
        {
            try
            {
                var tokenHandler = new JwtSecurityTokenHandler();
                var key = Encoding.UTF8.GetBytes(_authOptions.SecurityKey); //获取到用于生成token的加密key
                tokenHandler.ValidateToken(token, new TokenValidationParameters
                {
                    ValidateIssuerSigningKey = true, //是否验证Key SecurityKey
                    IssuerSigningKey = new SymmetricSecurityKey(key), //拿到SecurityKey
                    ValidateIssuer = false, //是否验证Issuer
                    ValidateAudience = false, //是否验证Audience
                    ValidateLifetime = true, //是否验证Token的失效时间
                }, out SecurityToken validatedToken);

                var jwtToken = (JwtSecurityToken)validatedToken;
                var user = jwtToken.Claims.First(x => x.Type == "userInfo").Value; //获取Token中存入的用户信息
                var userModel = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(user);


                //写入认证信息,方便业务类使用
                var claimsIdentity = new ClaimsIdentity(new Claim[] { new Claim("userInfo", jwtToken.Claims.First(x => x.Type == "userInfo").Value) });
                Thread.CurrentPrincipal = new ClaimsPrincipal(claimsIdentity);

                //将Token中解析出来的用户信息存入当前请求中
                context.Items["UserModel"] = userModel;
            }
            catch(Exception ex)
            {
                context.Items["UserModel"] = null;
            }
        }

    }

8、全局权限认证过滤器

    /// <summary>
    /// 鉴权,身份验证授权的过滤器
    /// </summary>
    public class ApiAuthorizeAttribute : IAuthorizationFilter 
    {
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            var user = context.HttpContext.Items["UserModel"];

            //context.HttpContext.Response.WriteAsync("我是身份验证过滤器");

            //验证是否需要授权和授权信息
            if (HasAllowAnonymous(context) == false && user == null)
            {
                context.Result = new JsonResult(new { code = StatusCodes.Status401Unauthorized, message = "Token验证失败或已过期请重新登录并获取!" });
                //也可自定义返回的HTTP code 
                //{ StatusCode = StatusCodes.Status401Unauthorized };
            }
        }

        private static bool HasAllowAnonymous(AuthorizationFilterContext context)
        {
            var filters = context.Filters;
            if (filters.OfType<IAllowAnonymousFilter>().Any())
            {
                return true;
            }
            var endpoint = context.HttpContext.GetEndpoint();
            return endpoint?.Metadata?.GetMetadata<IAllowAnonymous>() != null;
        }
    }

9、Startup里 ConfigureServices 添加

    #region JWT配置
    //读取配置文件中的JWTTokenOptions字段
    services.Configure<JWTTokenOptions>(this.Configuration.GetSection("JWTTokenOptions"));

    //将字段中的值赋值给 JWTTokenOptions 实体类
    JWTTokenOptions tokenOptions = new JWTTokenOptions();
    Configuration.Bind("JWTTokenOptions", tokenOptions);
    //依赖注入 将配置注入
    services.AddSingleton(tokenOptions);
    services.AddSingleton<IAuthManage>(new MicrosoftJwtAuthManage(tokenOptions));
    #endregion

10、Startup里 Configure 添加

 //启用jwt认证中间件
 app.UseMiddleware<JwtMiddleware>();

11、生成Token

public class LoginController : Controller
    {
        private IAuthManage _AuthManage = null;
        /// <summary>
        /// 用于获取jwt配置
        /// </summary>
        private readonly JWTTokenOptions _JWTTokenOptions;

        public LoginController(IAuthManage AuthManage, IOptionsMonitor<JWTTokenOptions> jwtTokenOptions)
        {
            _AuthManage = AuthManage;
            _JWTTokenOptions = jwtTokenOptions.CurrentValue;
        }
        /// <summary>
        /// 登录操作
        /// </summary>
        /// <returns></returns>
        [HttpGet("Login")]
        public IActionResult Index()
        {
            UserInfo user = new UserInfo();
            user.userId = "55114d44-a2d6-4f1a-a749-0618ca542591";
            user.userName = "张三";
            user.roleType = "sys_p_user";//读取配置的过期时间 单位为天
            double expire = Convert.ToDouble(_JWTTokenOptions.Expire);
            var expireTime = DateTime.Now.AddDays(expire); //Token的过期日期
            user.expiresDate = expireTime;//生成Token 
            string token = this._AuthManage.GenerateJwtToken(user);
            
            var result = new
            {
                Result = true,
                Token= token 
};

return Ok(result);
}

 获取存入请求中的UserInfo信息

  var userInfo= (UserInfo)this.HttpContext.Items["UserModel"];

 

如果个别接口不需要认证,可以使用 [AllowAnonymous] 特性,可以放在接口控制器或方法上都可以。

总结

1、JwtMiddleware 类文件为验证Token的中间件,如果验证通过把token中的信息存入上下文中
2、ApiAuthorizeAttribute 类为鉴权的过滤器,查看当前上下文中是否有存入的token信息,如果没有则返回自定义的消息
3、在Startup中无需配置太多东西,只需要注入JwtMiddleware中间件、注入JWTTokenOptions(jwt的配置)、添加一个全局的鉴权认证过滤器(ApiAuthorizeAttribute)即可
使用方便可且可以自由定义和扩展!

 

才疏学浅,相关文档等仅供自我总结,如有相关问题可留言交流谢谢。

 

posted @ 2022-02-08 15:28  独角马  阅读(1195)  评论(0编辑  收藏  举报