ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

权限验证

2021-01-14 12:04:35  阅读:262  来源: 互联网

标签:Claim return string 验证 token new 权限 public


前言

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

  本篇记录下项目的权限验证,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 接口就行

 

标签:Claim,return,string,验证,token,new,权限,public
来源: https://www.cnblogs.com/zousc/p/14276445.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有