• JWT权限验证,兼容多方式验证


    前言

      许久没写博文了,整合下这段时间所学吧,前进路上总要停下来回顾下学习成果。

      本篇记录下项目的权限验证,WebApi项目中用权限验证来保证接口安全总是需要的,然而权限验证的方式多种多样,博主在项目中使用的多的也就是JWT了,一般都是写完之后万年不动~~

      所以,本篇算是对鉴权授权的回顾与总结

    JWT

      至于什么是JWT(https://jwt.io/),只要不是小白都知道吧,不知道的去看下JWT的结构原理这些,偷偷补下课,JWT(JSON Web Token)名字可看出来这是Json格式的web凭证,也就是一个令牌,只有拿到这个Token才能访问到接口,否则请求接口之后会返回401HTTP状态码,401状态码表示未授权,而想要拿到服务器的Token,必须通过服务器验证,一般这个验证来自登录之后返回出来,如果是开发平台一般是通过AppId和Secrect来获取到Token,获取到Token后将Token添加到请求头中,服务器收到请求后,获取到请求头的Token后一验证,“诶!~是我发布的Token,通过!”,随后才能进入控制器。

    引入

      先把JWT引入到项目中来,目前最新版本为稳定版5.0.2 

      nuget : Microsoft.AspNetCore.Authentication.JwtBearer

      

    鉴权授权

      首先要知道沃恩需要什么样的鉴权策略,在生成Token时的策略就必须保持一致。在WebApi中我们不知道是谁在访问服务器,当然想要知道还是可以的,这时可以通过Token将用户信息传到服务器,我们知道JWT的负载信息除了已经准备好的"sub"、"name"、"iat"这些信息,我们还能自定义我们需要的字段,比如登录人的UserId,UserName,AppId等……

      首先需要一个接口  IAuthService 

     public interface IAuthService
        {
            /// <summary>
            /// 判断权限
            /// </summary>
            /// <param name="token"></param>
            /// <param name="path"></param>
            /// <returns></returns>
            Task<bool> PermissionAsync(string token, string path);
    
            /// <summary>
            /// 获取用户
            /// </summary>
            /// <param name="token"></param>
            /// <returns></returns>
            Task SetUserAsync(string token);
        }
    

      以后所有的权限类型都可以使用这个接口,先将JWT需要的类包装下,这样就能直接使用了

    public static class JwtUtils
        {
            /// <summary>
            /// 生成token
            /// </summary>
            /// <param name="claims"></param>
            /// <returns></returns>
            public static string CreateToken(IEnumerable<Claim> claims, string securityKey)
            {
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha512);
                var securityToken = new JwtSecurityToken(
                    issuer: null,
                    audience: null,
                    claims: claims,
                    //expires: DateTime.Now.AddMinutes(settings.ExpMinutes),
                    signingCredentials: creds);
                var token = new JwtSecurityTokenHandler().WriteToken(securityToken);
                return token;
            }
    
    
            /// <summary>
            /// 生成Jwt
            /// </summary>
            /// <param name="userName"></param>
            /// <param name="roleName"></param>
            /// <param name="userId"></param>
            /// <returns></returns>
            public static string GenerateToken(string userId, string securityKey)
            {
                //声明claim
                var claims = new Claim[] {
                    new Claim(JwtRegisteredClaimNames.Typ,"JWT"),
                    new Claim(JwtRegisteredClaimNames.Sub, userId),
                    new Claim(JwtRegisteredClaimNames.Iat,DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(),ClaimValueTypes.Integer64),
                    new Claim(JwtRegisteredClaimNames.Exp, DateTimeOffset.UtcNow.AddMonths(2).ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64), //过期时间
                };
                return CreateToken(claims, securityKey);
            }
    
    
            ///// <summary>
            ///// 刷新token
            ///// </summary>
            ///// <returns></returns>
            //public static string RefreshToken(string oldToken)
            //{
            //    var pl = GetPayload(oldToken);
            //    //声明claim
            //    var claims = new Claim[] {
            //        new Claim(JwtRegisteredClaimNames.Sub, pl?.UserName),
            //        new Claim(JwtRegisteredClaimNames.Jti, pl?.UserId),
            //        new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToUnixDate().ToString(), ClaimValueTypes.Integer64),//签发时间
            //        new Claim(JwtRegisteredClaimNames.Nbf, DateTime.UtcNow.ToUnixDate().ToString(), ClaimValueTypes.Integer64),//生效时间
            //        new Claim(JwtRegisteredClaimNames.Exp, DateTime.Now.AddMinutes(settings.ExpMinutes).ToUnixDate().ToString(), ClaimValueTypes.Integer64), //过期时间
            //        new Claim(JwtRegisteredClaimNames.Iss, settings.Issuer),
            //        new Claim(JwtRegisteredClaimNames.Aud, settings.Audience),
            //        new Claim(ClaimTypes.Name, pl?.UserName),
            //        new Claim(ClaimTypes.Role, pl?.RoleId),
            //        new Claim(ClaimTypes.Sid, pl?.UserId)
            //    };
    
            //    return IsExp(oldToken) ? CreateToken(claims) : null;
            //}
    
    
            /// <summary>
            /// 从token中获取用户身份
            /// </summary>
            /// <param name="token"></param>
            /// <returns></returns>
            public static IEnumerable<Claim> GetClaims(string token)
            {
                var handler = new JwtSecurityTokenHandler();
                var securityToken = handler.ReadJwtToken(token);
                return securityToken?.Claims;
            }
    
    
            /// <summary>
            /// 从Token中获取用户身份
            /// </summary>
            /// <param name="token"></param>
            /// <param name="securityKey">securityKey明文,Java加密使用的是Base64</param>
            /// <returns></returns>
            public static ClaimsPrincipal GetPrincipal(string token, string securityKey)
            {
                try
                {
                    var handler = new JwtSecurityTokenHandler();
                    TokenValidationParameters tokenValidationParameters = new TokenValidationParameters
                    {
                        ValidateAudience = false,
                        ValidateIssuer = false,
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey)),
                        ValidateLifetime = false
                    };
                    return handler.ValidateToken(token, tokenValidationParameters, out SecurityToken validatedToken);
                }
                catch (Exception ex)
                {
    
                    return null;
                }
            }
    
    
            /// <summary>
            /// 校验Token
            /// </summary>
            /// <param name="token">token</param>
            /// <returns></returns>
            public static bool CheckToken(string token, string securityKey)
            {
                var principal = GetPrincipal(token, securityKey);
                if (principal is null)
                {
                    return false;
                }
                return true;
            }
    
    
            /// <summary>
            /// 获取Token中的载荷数据
            /// </summary>
            /// <param name="token">token</param>
            /// <returns></returns>
            public static JwtPayload GetPayload(string token)
            {
                var jwtHandler = new JwtSecurityTokenHandler();
                JwtSecurityToken securityToken = jwtHandler.ReadJwtToken(token);
                return new JwtPayload
                {
                    sub = securityToken.Payload[JwtRegisteredClaimNames.Sub]?.ToString(),
                    exp = DateTimeOffset.FromUnixTimeSeconds(long.Parse(securityToken.Payload[JwtRegisteredClaimNames.Exp].ToString())).ToLocalTime().DateTime,
                    iat = securityToken.Payload[JwtRegisteredClaimNames.Iat]?.ToString()
                };
            }
    
    
            /// <summary>
            /// 获取Token中的载荷数据
            /// </summary>
            /// <typeparam name="T">泛型</typeparam>
            /// <param name="token">token</param>
            /// <returns></returns>
            public static T GetPayload<T>(string token)
            {
                var jwtHandler = new JwtSecurityTokenHandler();
                JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(token);
                return JsonConvert.DeserializeObject<T>(jwtToken.Payload.SerializeToJson());
            }
    
    
            /// <summary>
            /// 判断token是否过期
            /// </summary>
            /// <param name="token"></param>
            /// <returns></returns>
            public static bool IsExp(string token)
            {
                return false;
                //return  GetPrincipal(token)?.Claims.First(c => c.Type == JwtRegisteredClaimNames.Exp)?.Value?.TimeStampToDate() < DateTime.Now;
                //return GetPayload(token).ExpTime < DateTime.Now;
            }
        }
    
        /// <summary>
        /// Jwt载荷信息
        /// </summary>
        public class JwtPayload
        {
            public string sub { get; set; }
    
            public string iat { get; set; }
    
            public DateTime exp { get; set; }
        }
    

      JWT服务实现

      

    public class AuthSettings
        {
            public string Secret { get; set; }
            public string Issuer { get; set; }
            public double Expire { get; set; }
        }
    public class AuthServiceImpl : IAuthService
        {
            private readonly AuthSettings _authSettings;
            private readonly LoginUser _currentUser;
    
            public AuthServiceImpl(IOptions<AuthSettings> authSettings, LoginUser currentUser)
            {
                _authSettings = authSettings.Value;
                _currentUser = currentUser;
            }
            public Task<bool> PermissionAsync(string token)
            {
                return Task.FromResult(JwtUntil.CheckToken(token, _authSettings.Secret));
            }
    
            public Task SetUserAsync(string token)
            {
                var payload = JwtUntil.GetPayload(token);
                _currentUser.UserId = payload.UserId;
                _currentUser.RoleType = payload.Role;
                return Task.CompletedTask;
            }
        }
    

      

      说到这还没有注册JWT,我们先注册到项目中,验证策略自己定,记得要先注入下服务

    services.AddScoped<IAuthService, AuthServiceImpl>();
    
                services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                    .AddJwtBearer(x =>
                    {
                        x.RequireHttpsMetadata = false;//元数据地址或权限是否需要https
                        x.SaveToken = true;//是否将存储信息保存在token中
                        x.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
                        {
                            ValidateLifetime = true,//是否验证过期时间
                            LifetimeValidator = (notBefore, expire, securityToken, validationparameters) =>
                            {
                                bool t = DateTime.UtcNow < expire;
                                return t;
                            },
                            ValidateAudience = false,//是否验证被发布者
    
                            ValidateIssuer = true,//是否验证发布者
                            ValidIssuer = configuration["AuthSettings:Issuer"],
    
                            ValidateIssuerSigningKey = true,//是否验证签名
                            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["AuthSettings:Secret"]))
                        };
                    });
    #region 授权鉴权
    /授权
    app.UseAuthentication();
    //鉴权
    app.UseAuthorization();
    #endregion
    

      接下来就是设置当前登录用户,获取当前登录用户:发布时在 claims中加入自定义的UserId等信息,授权时获取当前Token解析claims中用户信息属性即可获取到当前请求用户。

      本项目中使用的是一个中间件

    public sealed class LoginMiddlerware
        {
            private readonly RequestDelegate _next;
    
            public LoginMiddlerware(RequestDelegate next)
            {
                _next = next;
            }
    
            /// <summary>
            /// 设置登录用户
            /// </summary>
            /// <param name="context"></param>
            /// <param name="_authService"></param>
            /// <returns></returns>
            public async Task InvokeAsync(HttpContext context, IEnumerable<IAuthService> _authService)
            {
                string token = GetToken();
                if (!string.IsNullOrEmpty(token))
                {
                    if (token.StartsWith("Bearer", StringComparison.OrdinalIgnoreCase) || token.Contains('.'))
                    {
                        await _authService.First(a => a.ServiceName == nameof(JwtAuthServiceImpl)).SetUserAsync(token);
                    }
                    else if (token.StartsWith("App", StringComparison.OrdinalIgnoreCase))
                    {
                        await _authService.First(a => a.ServiceName == nameof(AppAuthServiceImpl)).SetUserAsync(token);
                    }
                    else
                    {
                        await _authService.First(a => a.ServiceName == nameof(OauthAuthServiceImpl)).SetUserAsync(token);
                    }
                }
                await _next.Invoke(context);
                string GetToken()
                {
                    string token = context.Request.Headers["token"];
                    if (string.IsNullOrEmpty(token))
                    {
                        token = context.Request.Query["token"];
                    }
                    return token;
                }
            }
        }
    

      

      到这里JWt就差不多讲完了,接下来是使用,使用时无非就是在控制器或方法上打上标记,如要同时兼容多种授权方式,可以自己写一个Attribute

    /// <summary>
        /// 自定义授权验证特性
        /// </summary>
        public class RequiresPermissionsAttribute : TypeFilterAttribute
        {
            public RequiresPermissionsAttribute(ClaimType claimType, string claimValue = "") : base(typeof(ClaimRequirementFilter))
            {
                Arguments = new object[] { new Claim(claimType.ToString(), claimValue) };
            }
        }
    
        public class ClaimRequirementFilter : IAuthorizationFilter
        {
            readonly Claim _claim;
            readonly IEnumerable<IAuthService> _authService;
            private readonly WinkSignSettings _winkSignSettings;
    
            public ClaimRequirementFilter(Claim claim, IEnumerable<IAuthService> authService, IOptions<WinkSignSettings> winkSignSettings)
            {
                _claim = claim;
                _authService = authService;
                _winkSignSettings = winkSignSettings.Value;
            }
    
            public void OnAuthorization(AuthorizationFilterContext context)
            {
                ControllerActionDescriptor controllerActionDescriptor = context.ActionDescriptor as ControllerActionDescriptor;
                if (controllerActionDescriptor != null)
                {
                    var skipAuthorization = controllerActionDescriptor.MethodInfo.GetCustomAttributes(inherit: true)
                        .Any(a => a.GetType().Equals(typeof(AllowAnonymousAttribute)));
                    if (skipAuthorization)
                    {
                        return;
                    }
                }
    
                ClaimType claimType = Enum.Parse<ClaimType>(_claim.Type);
                bool permission = false;
    
                string token = GetToken();
                if (string.IsNullOrEmpty(token))
                {
                    context.Result = new UnauthorizedResult();
                    return;
                }
                if (claimType == ClaimType.JwtOrOauth2)
                {
                    //根据Token类型选择认证方式
                    if (token.Any(t => t == '.'))
                    {
                        claimType = ClaimType.JWT;
                    }
                    else
                    {
                        claimType = ClaimType.Oauth2;
                    }
                }
    
                permission = claimType switch
                {
                    ClaimType.Oauth2 or ClaimType.Cookie =>
                        _authService.First(a => a.ServiceName == nameof(OauthAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
                    ClaimType.JWT =>
                        _authService.First(a => a.ServiceName == nameof(JwtAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
                    ClaimType.Key =>
                        _authService.First(a => a.ServiceName == nameof(KeyAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
                    ClaimType.App =>
                        _authService.First(a => a.ServiceName == nameof(AppAuthServiceImpl)).PermissionAsync(token, _claim.Value).Result,
                };
    
                if (!permission)
                {
                    context.Result = new UnauthorizedResult();
                    return;
                }
    
                string GetToken()
                {
                    string token = context.HttpContext.Request.Headers["token"];
                    if (string.IsNullOrEmpty(token))
                    {
                        token = context.HttpContext.Request.Query["token"];
                    }
                    if (string.IsNullOrEmpty(token))
                    {
                        context.HttpContext.Request.Cookies.TryGetValue("token", out token);
                    }
                    return token;
                }
            }
        }
    
        public enum ClaimType
        {
            Oauth2,
            JWT,
            Cookie,
            Key,
            App,
            JwtOrOauth2
        }
    

      

      这样就能实现多个方式同时存在,想用哪个就用哪个了,只要实现 IAuthService 接口就行

  • 相关阅读:
    sublime使用技巧
    周末时间学习Linux
    中小企业网络安全提升
    NoSQL是什么?
    IBM的淘汰之路
    Linux 中断处理浅析
    深入理解 C 语言的函数调用过程
    LAMP简易安装
    安装Fedora 24后必要的设置
    wget命令详解
  • 原文地址:https://www.cnblogs.com/zousc/p/14276445.html
Copyright © 2020-2023  润新知