• 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

  • 相关阅读:
    [POI2007]山峰和山谷Grz
    [POI2007]驾驶考试egz
    [POI2007]立方体大作战tet
    BZOJ1085 [SCOI2005]骑士精神
    BZOJ1975 [Sdoi2010]魔法猪学院
    codeforces754D Fedor and coupons
    UOJ79 一般图最大匹配
    BZOJ3944 Sum
    BZOJ3434 [Wc2014]时空穿梭
    UOJ58 【WC2013】糖果公园
  • 原文地址:https://www.cnblogs.com/xiewenyu/p/13377591.html
Copyright © 2020-2023  润新知