从0到1详解Jwt以及在Asp.NetCore 3.1中使用JWT Bearer授权认证

编辑于 2020/03/15
修改于 2020/04/02

环境 Asp. Net Core WebAPI 3.1

JWT介绍


jwt全称JSON Web Token,是目前最流行的跨域授权认证解决方案。
jwt是一个记录着用户身份信息的令牌,访问api时持有这个令牌就可以访问api,这就是jwt。
JWT官网

  • 授权:当用户登陆成功时,返回jwt的Token,后续的每次请求都将携带Jwt发放的token,来实现认证并返回所请求的数据。

  • 认证:因JWT签名使用公钥私钥对,只要私钥不丢失,就可以确认发放token的人,而且使用了计算签名,可以有效的判断验证内容是否被修改。

JWT Token结构


HEADER.PAYLOAD.SIGNATURE
  • HEADER
  • PAYLOAD 载荷
  • SIGNATURE 签名
  1. HEADER 头
    HEADER包含token的元数据,使用的加密算法,和签名的类型,如下所示,加密类型是JWT,算法是HMAC SHA-256

    {"alg":"HS256","typ":"JWT"}
    

    再通过base64算法编码,形成JWT头

  2. PAYLOAD 载荷
    PAYLOAD也是一个Json,用来存放需要传递的数据,JWT官方解释了7个字段可以使用。

    • iss (issuer):签发人
    • exp (expiration time):过期时间
    • sub (subject):主题
    • aud (audience):受众
    • nbf (Not Before):生效时间
    • iat (Issued At):签发时间
    • jti (JWT ID):编号

    同时也可以自定义添加字段,如

    {
        "Account": "Tuser",
        "Name": "我叫Tuser",
        "Age": 18
    }
    

    需要注意的是,PAYLOAD包含的字段是通过Base64编码的,所以任何获取到token的人都可以读取到里面的内容

  3. SIGNATURE 签名
    SIGNATURE 部分是对前两部分的签名,防止数据篡改。
    首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。

    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)
    

    下面显示了一个JWT,它具有先前的头和​​有效负载编码,并使用机密签名。
    JWT官网提供了解码器,大家可以自行尝试

    JWT内容

JWT运行详解


一般在身份验证中用户请求登陆成功后,后端将返回JWT Token给前端,其中包含了用户的部分可见信息。
当前端请求受到请求限制的资源时,前端在请求头中带着JWT Token进行请求,如Authorization: Bearer


在请求受保护的路由将检查Authorization标头中的有效JWT,如果JWT包含必要的数据,则可减能可以少查询数据库以进行某些操作的需要。


使用Jwt身份验证,那么跨域资源共享将不会成为问题,因为它不使用cookie。

Asp .Net WebAPI中使用Jwt


一. 创建. Net Core WebAPI 3.1项目并安装NuGet包+前置工作

  1. 创建项目
    这里就不演示了

  2. 安装的NuGet包

    Jwt 包

    也可以通过进行双击项目在包还原下添加,不明白的可以博主最后面提供的源码


  3. 创建Utils工具类库

    • 创建. net core 类库并命名为Utils
    • 创建Helper文件夹并添加Appsettings.cs文件
    • 添加Appsettings代码
      Appsettings.cs
        using Microsoft.Extensions.Configuration;
        using Microsoft.Extensions. Configuration.Json;
    
        namespace NetCore.Blogs.Demo.Utils
        {
            /// 
            /// appsettings.json操作类
            /// 
            public class Appsettings
            {
                static IConfiguration Configuration { get; set; }
                static string contentPath { get; set; }
    
                public Appsettings(string contentPath)
                {
                    string Path = "appsettings.json";
    
                    //如果你把配置文件 是 根据环境变量来分开了,可以这样写
                    //Path = $"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT")}.json";
    
                    Configuration = new ConfigurationBuilder()
                    .SetBasePath(contentPath)
                    .Add(new JsonConfigurationSource { Path = Path, Optional = false, ReloadOnChange = true })//这样的话,可以直接读目录里的json文件,而不是 bin 文件夹下的,所以不用修改复制属性
                    .Build();
    
    
                }
    
                /// 
                /// 封装要操作的字符
                /// 
                /// 节点配置
                /// 
                public static string app(params string[] sections)
                {
                    try
                    {
    
                        if (sections.Any())
                        {
                            return Configuration[string.Join(":", sections)];
                        }
                    }
                    catch (Exception) { }
    
                    return "";
                }
            }
        }
    
    • 创建完后Utils类库结构如下


      Utils结构
    • 最后在Startup.cs中注入Appsettings类


      注入cs中注入Appsettings类

      完成后,就可以通过Appsettings.app(string[]) 传入一个数组进行访问appsettings.json配置文件了

二. 创建Jwt授权认证

重头戏来了!

  1. appsettings.json中添加Jwt字段

    • Issuer(发行人)
    • Audience(受众)
    • SecurityKey(秘钥)
    "JwtSettings": {
        "Issuer": "zyknow",
        "Audience": "audience",
        "SecurityKey": "zyknowzyknowzyknowzyknowzyknowzyknow"
    }
    


  2. 在webApi项目中创建Extensions文件夹并创建AuthorizationSetup.cs类,这样把在Startup.cs做的操作提出来,这样Startup.cs看起来更简洁些,如图所示,并且粘贴代码

    Extensions结构

    AuthorizationSetup.cs

    public static class AuthorizationSetup
    {
        public static void AddAuthorizationSetup(this IServiceCollection services)
        {
            if (services == null) throw new ArgumentNullException(nameof(services));
    
            // 配置策略授权
            services.AddAuthorization(o =>
            {
                // 添加策略,使用时在方法上标注[Authorize(Policy ="AdminPolicy")],就会验证请求token中的ClaimTypes.Role是否包含了Admin
                o.AddPolicy("AdminPolicy", o =>
                {
                    //ClaimTypes.Role == Admin
                    o.RequireRole("Admin").Build();
    
                    //ClaimTypes.Role == Admin 或者 == User
                    //o.RequireRole("Admin","User").Build(); 
    
                    //ClaimTypes.Role == Admin 并且 == User ,关于添加多个角色策略,在Login控制器中
                    //o.RequireRole("Admin").RequireRole("User").Build(); 
                });
    
                //只有User的策略
                o.AddPolicy("onlyUserPolicy", o =>
                {
                    o.RequireRole("User").Build();
                });
    
                //User和Admin都可以访问的策略
                o.AddPolicy("UserOrAdminPolicy", o =>
                {
                    o.RequireRole("User", "Admin").Build();
                });
    
                //User并且是Admin才能请求的策略
                o.AddPolicy("UserAndAdminPolicy", o =>
                {
                    o.RequireRole("User").RequireRole("Admin").Build();
                });
            });
    
            string key = Appsettings.app(new string[] { "JwtSettings", "SecurityKey" });
            string issuer = Appsettings.app(new string[] { "JwtSettings", "Issuer" });
            string audience = Appsettings.app(new string[] { "JwtSettings", "Audience" });
    
            SecurityKey securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)); //秘钥的长度有要求,必须>=16位
    
            services.AddAuthentication("Bearer").AddJwtBearer(o =>
            {
                o.TokenValidationParameters = new TokenValidationParameters()
                {
                    //是否秘钥认证
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKey = securityKey, //秘钥
    
                    //是否验证发行人
                    ValidateIssuer = true,
                    ValidIssuer = issuer, //这个字符串可以随便写,就是发行人
    
                    //是否验证订阅
                    ValidateAudience = true,
                    ValidAudience = audience,
    
                    //是否验证过期时间
                    RequireExpirationTime = true,
                    ValidateLifetime = true,
                };
            });
        }
    }
    
    


  3. 注册Jwt到Startup.cs中


    ConfigureServices
    Configure


  4. 创建Login控制器并创建GetToken方法


    创建Login控制器

    这个控制器是用来发放Jwt Token的,通过把请求中携带的userRole添加到Jwt授权中并返回(实际应该是查询数据库,然后返回对应的权限等级之类的,比如admin,SuperAdmin,User等)

    注意一下代码中自定义的Jwt字段,后面会提到

    [Route("api/[controller]")]
    [ApiController]
    [AllowAnonymous]
    public class LoginController : ControllerBase
    {
        /// 
        /// 颁发令牌接口
        /// 
        /// 
        [HttpGet(nameof(GetJwtToken))]
        public string GetJwtToken(string userRole)
        {
            //获取在配置文件中获取Jwt属性设置
            string key = Appsettings.app(new string[] { "JwtSettings", "securityKey" });
            string issuer = Appsettings.app(new string[] { "JwtSettings", "Issuer" });
            string audience = Appsettings.app(new string[] { "JwtSettings", "Audience" });
    
            //创建授权的token类
            SecurityToken securityToken = new JwtSecurityToken(
                issuer: issuer, //签发人
                audience: audience, //受众
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(key)), SecurityAlgorithms.HmacSha256), //秘钥
    
                //创建过期时间
                expires: DateTime.Now.AddHours(1), //过期时间 一小时之后过期
    
                //自定义JWT字段
                claims: new Claim[] {
                    new Claim(ClaimTypes.Role,userRole), //把模拟请求的角色权限添加到Role中
                    //下面的都是自定义字段,可以任意添加到Claim作为信息共享
                    new Claim("Name","我叫Tuser"),
                    new Claim("Age","18"),
                }
                );
    
            //返回请求token
            return JsonConvert.SerializeObject(new {token = new JwtSecurityTokenHandler().WriteToken(securityToken)});
        }
    }
    


  5. 开启调试,看看是否能正确请求到Token
    这里给大家推荐一下国人开发的谷歌浏览器调试插件,功能跟Postman一样,非常便携还是中文apizza

    进入正题,使用Http请求工具进行请求Login/GetJwtToken方法


    请求GetJwtToken成功

    可以看到成功的获取到了Token

    我们可以复制token在Jwt官网中解析看看

    Jwt官网解析Token

    接下来输入我们在appsettings.json中的SecurityKey字段


    SecurityKey字段内容
    有效令牌

    效验成功了!有效令牌!
    通过在PAYLOAD定义的公共属性(若没有私钥伪造令牌),可以很好的实现跨域信息共享

  6. 在控制器中使用Jwt授权认证
    在创建项目时自动生成的WeatherForecastController.cs控制器中添加如下代码

    [ApiController]
    [Route("[controller]")]
    [Authorize]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };
    
        private readonly ILogger _logger;
    
        public WeatherForecastController(ILogger logger)
        {
            _logger = logger;
        }
    
        [HttpGet]
        [Route(nameof(AllGet))]
        [AllowAnonymous] //没有token也能访问
        public IEnumerable AllGet()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    
        [HttpGet]
        [Route(nameof(AllTokenGet))]
        [Authorize]   //只需要有效token
        public IEnumerable AllTokenGet()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    
        [HttpGet]
        [Route(nameof(AdminPolicy))]
        [Authorize(Policy = "AdminPolicy")] //需要请求携带token的策略为Admin才能访问
        public IEnumerable AdminPolicy()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
        [HttpGet]
        [Route(nameof(onlyUserPolicy))]
        [Authorize(Policy = "onlyUserPolicy")] //需要请求携带token的策略为Admin才能访问
        public IEnumerable onlyUserPolicy()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    
        [HttpGet]
        [Route(nameof(AdminRequireMent))]
        [Authorize(Policy = "AdminRequireMent")] //自定义授权
        public IEnumerable AdminRequireMent()
        {
            var rng = new Random();
    
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateTime.Now.AddDays(index),
                TemperatureC = rng.Next(-20, 55),
                Summary = Summaries[rng.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
    


  7. 开启调试
    这里就不一个个方法给大家演示了,有兴趣的同学可以自己下载demo进行演示
    这里只给大家演示一下成功示例

    • 不携带Token请求,无法请求


      不携带token的请求
    • 在请求头中携带token时请注意格式如下!


      Bearer认证格式

      携带没有权限的token请求
      先请求login获取token,注意看,这里使用的User


      注意看,这里使用的User

      复制token,再请求AdminPolicy接口,可以发现状态码为403,请求成功,但是权限错误,所以请求不到数据
      请求成功,但是权限错误
    • 最后,使用Admin生成的token来请求


      请求成功

自定义授权处理

这部分主要是讲解Jwt授权认证的自定义授权处理环节

  1. 创建PolicyRequirement文件夹并创建AdminRequirement.cs类和MustRoleAdminHandler.cs


    AdminRequirement.cs

    public class AdminRequirement : IAuthorizationRequirement
    {
        //这里定义的字段是在Login GetToken方法授权时确定的
        public string Name { get; set; }
    }  
    


    MustRoleAdminHandler.cs

    //继承AuthorizationHandler
    public class MustRoleAdminHandler : AuthorizationHandler
    {
        //重写授权认证方法,每次jwt认证都会进入这个方法,可以开启断点调试查看
        protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement)
        {
            //这里获取的是请求时AdminRequirement类中在jwt包含的字段
            Console.WriteLine(requirement);
    
            context.Succeed(requirement);
            return Task.CompletedTask;
        }
    }
    


  2. AuthorizationSetup.cs中添加如下代码

    添加代码位置

     // 基于自定义处理策略
    o.AddPolicy("AdminRequireMent", o => {
        o.Requirements.Add(new AdminRequirement() { Name = "zyknow" });//完全自定义
    });  
    
    //依赖注入自定义处理策略
    services.AddSingleton();
    
    


  3. WeatherForecastController.cs中添加使用自定义授权的方法

    自定义授权方法

    [HttpGet(nameof(AdminRequireMent))]
    [Authorize(Policy = "AdminRequireMent")] //自定义授权
    public IEnumerable AdminRequireMent()
    {
        var rng = new Random();
    
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
    


  4. 接着启动程序,断点调试,带Jwt Token请求方法


    发送请求

    进入断点,可以获取到自定义授权策略的类,这里直接返回成功了,主要是只是为了演示,在这里可以做自己的逻辑处理


    进入断点

源码

下载Jwt分支即可

码云

你可能感兴趣的:(从0到1详解Jwt以及在Asp.NetCore 3.1中使用JWT Bearer授权认证)