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 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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需要的类包装下,这样就能直接使用了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
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服务实现

  

1
2
3
4
5
6
public class AuthSettings
    {
        public string Secret { get; set; }
        public string Issuer { get; set; }
        public double Expire { get; set; }
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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,我们先注册到项目中,验证策略自己定,记得要先注入下服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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"]))
                    };
                });
1
2
3
4
5
6
#region 授权鉴权
/授权
app.UseAuthentication();
//鉴权
app.UseAuthorization();
#endregion

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
/// <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 接口就行

 

posted @   贰拾~  阅读(1126)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示