WebAPI学习(五):JWT权限验证

  1. 在appsettings.json中配置JWT参数
    "JWTSetting": {
      "Issuer": "", //颁发者
      "audience": "jwtAudience", //可以给那些客户端使用
      "SecretKey": "8kh2luzmp0oq9wfbdeasygj647vr531n678fs" //加密Key 必须大于16个字符
    }
  2. 在TEST.Entity中创建令牌实体
    /// <summary>
    /// 令牌
    /// </summary>
    public class TokenData
    {
        /// <summary>
        /// ID
        /// </summary>
        public string Uid {  get; set; }    
    
        /// <summary>
        /// 角色
        /// </summary>
        public string Role {  get; set; }
    }
  3. 新增JWTHelper.cs帮助类
    /// <summary>
    /// JWT帮助类
    /// </summary>
    public class JWTHelper
    {
        /// <summary>
        /// 颁发JWT字符串
        /// </summary>
        /// <param name="tokenData"></param>
        /// <returns></returns>
        public static string IssueJwt(TokenData tokenData)
        {
            //获取appsetting配置
            var iss = AppSettings.App(new string[] { "JWTSetting", "Issuer" });
            var aud = AppSettings.App(new string[] { "JWTSetting", "audience" });
            var secret = AppSettings.App(new string[] { "JWTSetting", "SecretKey" });
    
            var claims = new List<Claim>
            { 
                /*
                 * 特别重要:
                 * 1.这里将用户的部分信息,比如UID 存到了Claim中,如果你想知道如何在其他地方将这个UID从Token中取出来,
                 * 请看下面的SerializeJwt()方法,或者在整个解决方案,搜索这个方法,看看哪里使用了!
                 * 2.你也可以研究下 HttpContext.User.Claims,具体的你可以看看 Policys/PermissionHanlder.cs类中是如何使用的。
                 */
                new Claim(JwtRegisteredClaimNames.Jti,tokenData.Uid.ToString()),
                new Claim(JwtRegisteredClaimNames.Iat,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
                //这个就是过期时间,目前是过期1000秒,可自定义,注意JWT有自己的缓冲过期时间
                new Claim(JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"),
                new Claim(ClaimTypes.Expiration,DateTime.Now.AddSeconds(1000).ToString()),
                new Claim(JwtRegisteredClaimNames.Iss,iss),
                new Claim(JwtRegisteredClaimNames.Aud,aud),
            };
    
            //可以将一个用户的多个角色全部赋予
            claims.AddRange(tokenData.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
                );
    
            var jwtHandler = new JwtSecurityTokenHandler();
            var encodedJwt = jwtHandler.WriteToken(jwt);
    
            return encodedJwt;
        }
    
        /// <summary>
        /// 解析
        /// </summary>
        /// <param name="jwtStr"></param>
        /// <returns></returns>
        public static TokenData SerializeJwt(string jwtStr)
        {
            var jwtHandler = new JwtSecurityTokenHandler();
            JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
    
            object role = "";
    
            try
            {
                jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
            }
            catch (Exception ex) 
            {
                Console.WriteLine(ex);
            }
    
            var td = new TokenData
            {
                Uid = jwtToken.Id.ToString(),
                Role = role != null ? role.ToString() : "",
            };
    
            return td;
        }
    }
  4. 在TestController中新增Login接口来获取Token
    /// <summary>
    /// 获取Token
    /// </summary>
    /// <param name="name"></param>
    /// <param name="password"></param>
    /// <returns></returns>
    [HttpGet]
    [AllowAnonymous]
    public async Task<IActionResult> Login(string name, string password)
    {
        var jwtStr = string.Empty;
        bool suc = false;
    
        if (name != null && password != null)
        {
            if (name != "admin" && password != "123456")
            {
                jwtStr = "输入账号密码错误!";
            }
            else
            {
                //将用户和角色作为单独的自定义变量封装进token字符串中。
                var tokenData = new TokenData { Uid = "1", Role = name };
    
                jwtStr = JWTHelper.IssueJwt(tokenData);//登录,获取到一定规则的Token令牌
                suc = true;
            }
    
        }
        else
        {
            jwtStr = "输入不能为空!";
        }
    
        return Ok(new
        {
            success = suc,
            token = jwtStr
        });
        
    }
  5. 运行项目,输入账号密码获取Token

    4

  6. JWT权限验证,就需要开启验证,然后输入Token令牌,在SwaggerHelper.cs的AddSwaggerSetup方法中添加代码:
    /// <summary>
    /// Swagger
    /// </summary>
    /// <param name="services"></param>
    public static void AddSwaggerSetup(this IServiceCollection services)
    {
        if(services == null)
            throw new ArgumentNullException(nameof(services));
    
        var ApiName = "TEST API";
    
        services.AddSwaggerGen(options =>
        {
            options.SwaggerDoc("v1", new OpenApiInfo
            {
                Version = "v1",
                Title = $"{ApiName} 接口文档— TEST API",
                Description = $"{ApiName} HTTPS TEST API v1"
            });
    
            options.OrderActionsBy(o => o.RelativePath);
    
            
            var xmlPath = Path.Combine(AppContext.BaseDirectory, "TEST.API.xml"); //这个就是刚刚设置的xml文件名
            options.IncludeXmlComments(xmlPath,false);
    
            var xmlEntityPath = Path.Combine(AppContext.BaseDirectory, "TEST.Entity.xml");//这个就是Model的xml文件名
            options.IncludeXmlComments(xmlEntityPath);
    
            //在Header中添加token,传递到后台
            options.OperationFilter<SecurityRequirementsOperationFilter>();
    
            #region Token绑定到ConfigureServices
    
            options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
            {
                Description = "JT授权(数据将在请求头中进行传输)直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
                Name = "Authorization",//jwt默认的参数名称
                In = ParameterLocation.Header, //jwt默认存放Authorization信息的位置(请求头)
                Type = SecuritySchemeType.ApiKey
            });
    
            #endregion
    
        });
    }
  7. 运行项目就可以看到JWT验证入口

    5

  8. 6

    在Business文件夹中新建注册方法AuthorizationSetup

    public static class AuthorizationSetup
    {
        public static void AddAuthorizationSetup(this IServiceCollection services)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
    
            //1.授权 这个和上面的异曲同工,好处就是不用再Controller中,写多个 roles.
            // [Authorize(Policy = "Admin")]
            services.AddAuthorization(option =>
            {
                option.AddPolicy("User", policy => policy.RequireRole("User").Build());
                option.AddPolicy("System", policy => policy.RequireRole("System").Build());
                option.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("admin").Build());
            });
    
            //读取配置文件
            var symmetricKeyAsBase64 = AppSettings.App(new string[] { "JWTSetting", "SecretKey" });
            var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
            var signingKey = new SymmetricSecurityKey(keyByteArray);
            var Issuer = AppSettings.App(new string[] { "JWTSetting", "Issuer" });
            var Audience = AppSettings.App(new string[] { "JWTSetting", "Audience" });
    
            //令牌验证参数
            var tokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = signingKey,
                ValidateIssuer = true,
                ValidIssuer = Issuer, //发行人
                ValidateAudience = true,
                ValidAudience = Audience, //订阅人
                ValidateLifetime = true,
                ClockSkew = TimeSpan.FromSeconds(30),
                RequireExpirationTime = true,
            };
    
            //2.认证 core自带官方JWT认证
            //开启Bearer认证
            services.AddAuthentication("Bearer")
                //添加JwtBearer服务
                .AddJwtBearer(o => 
                {
                    o.TokenValidationParameters = tokenValidationParameters;
                    o.Events = new Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerEvents
                    {
                        OnAuthenticationFailed = context =>
                        {
                            //如果过期,则把<是否过期>添加到返回头信息中
                            if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
                            {
                                context.Response.Headers.Add("Token-Expired", "true");
                            }
    
                            return Task.CompletedTask;
                        }
                    };
    
                });
    
    
        }
    }
  9. 在Program.cs注册服务,开启服务
    //jwt授权认证
    builder.Services.AddAuthorizationSetup();
    app.UseAuthentication();
  10. 在BaseController中设置全局权限验证:
    [Route("api/[controller]/[action]")]
    [ApiController]
    [Authorize]
    public class BaseController : ControllerBase
    {
    }
  11. 没有权限验证就会提示401

    7

  12. 设置获取Token 时[AllowAnonymous] 时无需验证

    14

    8

    9

  13. 设置测试方法需要System权限才能访问

    10

  14. 上面我们使用admin访问就会提示403错误

    11

  15. 新增解析Token方法
    [HttpGet]
    [Authorize]
    public IActionResult ParseToken()
    {
        //需要截取Bearer
        var tokenHeader = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
        var user = JWTHelper.SerializeJwt(tokenHeader);
        return Ok(user);
    }
  16. 解析出来的role 就是admin

    13

     

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容