ASP.NET Core 3.1使用JWT认证Token授权

0、引言
若不清楚什么是JWT的请先了解下什么是JWT。

1、关于Authentication与Authorization
我相信在aspnet core中刚接触甚至用了段时间这两个概念的时候都是一头雾水的,傻傻分不清。
认证(Authentication)和授权(Authorization)在概念上比较的相似,且又有一定的联系,因此很容易混淆。
认证(Authentication)是指验证用户身份的过程,即当用户要访问受保护的资源时,将其信息(如用户名和密码)发送给服务器并由服务器验证的过程。
授权(Authorization)是验证一个已通过身份认证的用户是否有权限做某件事情的过程。
有过RBAC的开发经验者来说这里可以这么通俗的来理解:认证是验证一个用户是否“合法”(一般就是检查数据库中是否有这么个用户),授权是验证这个用户是否有做事情的权限(简单理解成RBAC中的用户权限)。

2、整个认证流程是怎样的?

 从图中可以看到整个认证、授权的流程,先进行身份验证 ,验证通过后将Token放回给客户端,客户端访问资源的时候请求头中添加Token信息,服务器进行验证并于授权是否能够访问该资源。

3、开始JWT身份认证

3.1 引入nuget包:Microsoft.AspNetCore.Authentication.JwtBearer

 3.2 在Startup.cs文件中进行配置

复制代码
#region jwt验证
            var jwtConfig = new JwtConfig();
            Configuration.Bind("JwtConfig", jwtConfig);
            services
                .AddAuthentication(option =>
                {
                    //认证middleware配置
                    option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(options =>
                {
                    options.TokenValidationParameters = new TokenValidationParameters
                    {
                        //Token颁发机构
                        ValidIssuer = jwtConfig.Issuer,
                        //颁发给谁
                        ValidAudience = jwtConfig.Audience,
                        //这里的key要进行加密
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecretKey)),
                        //是否验证Token有效期,使用当前时间与Token的Claims中的NotBefore和Expires对比
                        ValidateLifetime = true,
                    };
                });
            #endregion
复制代码

配置中间件

//启用身份验证功能。必须要在app.UseAuthorization();之前
            app.UseAuthentication();//鉴权,

JWTHelper

复制代码
public class JWTHelper
    {
        public class JwtHelper
        {

            /// <summary>
            /// 颁发JWT字符串
            /// </summary>
            /// <param name="tokenModel"></param>
            /// <returns></returns>
            public static string IssueJwt(TokenModelJwt tokenModel)
            {
                // 自己封装的 appsettign.json 操作类,看下文
                string iss = Appsettings.app(new string[] { "Audience", "Issuer" });
                string aud = Appsettings.app(new string[] { "Audience", "Audience" });
                string secret = Appsettings.app(new string[] { "Audience", "Secret" });

                var claims = new List<Claim>
              {
                 /*
                 * 特别重要:
                   1、这里将用户的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方将这个 uid从 Token 中取出来,请看下边的SerializeJwt() 方法,或者在整个解决方案,搜索这个方法,看哪里使用了!
                   2、你也可以研究下 HttpContext.User.Claims ,具体的你可以看看 Policys/PermissionHandler.cs 类中是如何使用的。
                 */                

                new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ToString()),
                new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                //这个就是过期时间,目前是过期7200秒,可自定义,注意JWT有自己的缓冲过期时间
                new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(7200)).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Iss,iss),
                new Claim(JwtRegisteredClaimNames.Aud,aud),
                
                //new Claim(ClaimTypes.Role,tokenModel.Role),//为了解决一个用户多个角色(比如:Admin,System),用下边的方法
               };

                // 可以将一个用户的多个角色全部赋予;
                // 作者:DX 提供技术支持;
                claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));


                //秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                
                var jwt = new JwtSecurityToken(
                    issuer: iss,
                    claims: claims,
                    signingCredentials: creds
                    //,expires:DateTime.Now.AddMinutes(1)
                    );

                var jwtHandler = new JwtSecurityTokenHandler();
                var encodedJwt = jwtHandler.WriteToken(jwt);

                return encodedJwt;
            }

            /// <summary>
            /// 解析
            /// </summary>
            /// <param name="jwtStr"></param>
            /// <returns></returns>
            public static TokenModelJwt SerializeJwt(string jwtStr)
            {
                var jwtHandler = new JwtSecurityTokenHandler();
                JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
                object role;
                try
                {
                    jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }
                var tm = new TokenModelJwt
                {
                    Uid = (jwtToken.Id).ObjToInt(),
                    Role = role != null ? role.ObjToString() : "",
                };
                return tm;
            }
        }

        /// <summary>
        /// 令牌
        /// </summary>
        public class TokenModelJwt
        {
            /// <summary>
            /// Id
            /// </summary>
            public long Uid { get; set; }
            /// <summary>
            /// 角色
            /// </summary>
            public string Role { get; set; }
            /// <summary>
            /// 职能
            /// </summary>
            public string Work { get; set; }

        }
    }
复制代码

然后呢 如果你使用的Swagger

加上自动在Header带上Token

Bearer就是在ASP.NET Core 在 Microsoft.AspNetCore.Authentication 下实现了一系列认证, 包含 Cookie, JwtBearer, OAuth, OpenIdConnect 等
复制代码
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
                {
                    Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间有一个空格)",
                    Name = "Authorization",//jwt默认的参数名称,
                    In = ParameterLocation.Header,//jwt默认存放Autorization信息的位置(header中)
                    Type = SecuritySchemeType.ApiKey, //指定ApiKey
                    BearerFormat = "JWT",//标识承载令牌的格式 该信息主要是出于文档目的
                    Scheme = "bearer"//授权中要使用的HTTP授权方案的名称
                });
                //在Heder中添加Token 传递到后台
                options.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme{
                            Reference = new OpenApiReference {
                                        Type = ReferenceType.SecurityScheme,
                                        Id = "Bearer"}
                        },new string[] { }
                    }
                });
复制代码

 在对应Controller上或Action上加上Authorize标记

 验证TOken

复制代码
复制代码
options.Events = new JwtBearerEvents
                {
                    //此处为权限验证失败后触发的事件
                    OnChallenge = context =>
                    {
                        //此处代码为终止.Net Core默认的返回类型和数据结果,这个很重要哦,必须
                        context.HandleResponse();
                        //自定义自己想要返回的数据结果,我这里要返回的是Json对象,通过引用Newtonsoft.Json库进行转换
                        var payload = JsonConvert.SerializeObject(new ApiResult<bool>("Token无效"));
                        //自定义返回的数据类型
                        context.Response.ContentType = "application/json";
                        //自定义返回状态码,默认为401 我这里改成 200
                        context.Response.StatusCode = StatusCodes.Status200OK;
                        //context.Response.StatusCode = StatusCodes.Status401Unauthorized;
                        //输出Json数据结果
                        context.Response.WriteAsync(payload);
                        return Task.FromResult(0);
                    }
                };
复制代码
 /// <summary>
        /// 获取Token
        /// </summary>
        /// <param name="req">请求流</param>
        /// <returns></returns>
        public static string GetToken(HttpRequest req)
        {
            string tokenHeader = req.Headers["Authorization"].ToString();
            if (string.IsNullOrEmpty(tokenHeader))
                throw new Exception("缺少token!");

            string pattern = "^Bearer (.*?)$";
            if (!Regex.IsMatch(tokenHeader, pattern))
                throw new Exception("token格式不对!格式为:Bearer {token}");

            string token = Regex.Match(tokenHeader, pattern).Groups[1]?.ToString();
            if (string.IsNullOrEmpty(token))
                throw new Exception("token不能为空!");

            return token;
        }
复制代码

好了 基本没问题 最后在如果请求401可以自定义

 

 整的代码如下:

复制代码
[AllowAnonymous]
        [HttpGet]
        [Route("api/auth")]
        public IActionResult Get(string userName, string pwd)
        {
            if (CheckAccount(userName, pwd, out string role))
            {
                //每次登陆动态刷新
                Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
                // push the user’s name into a claim, so we can identify the user later on.
                //这里可以随意加入自定义的参数,key可以自己随便起
                var claims = new[]
                {
                    new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
                    new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
                    new Claim(ClaimTypes.NameIdentifier, userName),
                    new Claim("Role", role)
                };
                //sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
                var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
                var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
                //.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
                var token = new JwtSecurityToken(
                    //颁发者
                    issuer: Const.Domain,
                    //接收者
                    audience: Const.ValidAudience,
                    //过期时间
                    expires: DateTime.Now.AddMinutes(30),
                    //签名证书
                    signingCredentials: creds,
                    //自定义参数
                    claims: claims
                    );

                return Ok(new
                {
                    token = new JwtSecurityTokenHandler().WriteToken(token)
                });
            }
            else
            {
                return BadRequest(new { message = "username or password is incorrect." });
            }
        }
复制代码

3.3 然后改造一下StartUp.cs

  我们仅仅需要关心改动的地方,也就是AddJwtBearer这个验证token的方法,我们不用原先的固定值的校验方式,而提供一个代理方法进行运行时执行校验

复制代码
.AddJwtBearer(options =>

options.TokenValidationParameters = new TokenValidationParameters
{
    ValidateLifetime = true,//是否验证失效时间
    ClockSkew = TimeSpan.FromSeconds(30),
    ValidateAudience = true,//是否验证Audience
    //ValidAudience = Const.GetValidudience(),//Audience
    //这里采用动态验证的方式,在重新登陆时,刷新token,旧token就强制失效了
    AudienceValidator = (m, n, z) =>
    {
        return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
    },
    ValidateIssuer = true,//是否验证Issuer
    ValidIssuer = Const.Domain,//Issuer,这两项和前面签发jwt的设置一致
    ValidateIssuerSigningKey = true,//是否验证SecurityKey
    IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey
};
复制代码

这里逻辑是这样的:因为重新登陆将原来的变量更改了,所以这里校验的时候也一并修改成了新的变量值,那么旧的token当然就不匹配了,也就是旧的token被强制失效了。

 

 

 参考地址https://www.cnblogs.com/7tiny/p/11012035.html   https://www.cnblogs.com/cokeking/p/10969579.html

猜你喜欢

转载自www.cnblogs.com/xiewenyu/p/13377591.html