编辑于 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,来实现认证并返回所请求的数据。
</br> - 认证:因JWT签名使用公钥私钥对,只要私钥不丢失,就可以确认发放token的人,而且使用了计算签名,可以有效的判断验证内容是否被修改。
JWT Token结构
HEADER.PAYLOAD.SIGNATURE
-
HEADER
头 -
PAYLOAD
载荷 -
SIGNATURE
签名
-
HEADER 头
HEADER
包含token的元数据,使用的加密算法,和签名的类型,如下所示,加密类型是JWT
,算法是HMAC SHA-256
{"alg":"HS256","typ":"JWT"}
再通过base64算法编码,形成JWT头
</br> -
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的人都可以读取到里面的内容
</br> -
SIGNATURE 签名
SIGNATURE
部分是对前两部分的签名,防止数据篡改。
首先,需要指定一个密钥(secret)。这个密钥只有服务器才知道,不能泄露给用户。然后,使用 Header 里面指定的签名算法(默认是 HMAC SHA256),按照下面的公式产生签名。HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
下面显示了一个JWT,它具有先前的头和有效负载编码,并使用机密签名。
JWT官网提供了解码器,大家可以自行尝试
JWT运行详解
一般在身份验证中用户请求登陆成功后,后端将返回JWT Token给前端,其中包含了用户的部分可见信息。
当前端请求受到请求限制的资源时,前端在请求头中带着JWT Token进行请求,如Authorization: Bearer <token>
</br>
在请求受保护的路由将检查Authorization标头中的有效JWT,如果JWT包含必要的数据,则可减能可以少查询数据库以进行某些操作的需要。
</br>
使用Jwt身份验证,那么跨域资源共享将不会成为问题,因为它不使用cookie。
</br>
Asp .Net WebAPI中使用Jwt
一. 创建. Net Core WebAPI 3.1项目并安装NuGet包+前置工作
创建项目
这里就不演示了
</br>-
安装的NuGet包
也可以通过进行双击项目在包还原下添加,不明白的可以博主最后面提供的源码
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.2" />
</br> -
创建Utils工具类库
- 创建. net core 类库并命名为Utils
- 创建Helper文件夹并添加Appsettings.cs文件
- 添加Appsettings代码
Appsettings.cs
using Microsoft.Extensions.Configuration; using Microsoft.Extensions. Configuration.Json; namespace NetCore.Blogs.Demo.Utils { /// <summary> /// appsettings.json操作类 /// </summary> 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(); } /// <summary> /// 封装要操作的字符 /// </summary> /// <param name="sections">节点配置</param> /// <returns></returns> public static string app(params string[] sections) { try { if (sections.Any()) { return Configuration[string.Join(":", sections)]; } } catch (Exception) { } return ""; } } }
-
创建完后Utils类库结构如下
-
最后在Startup.cs中注入Appsettings类
完成后,就可以通过Appsettings.app(string[]) 传入一个数组进行访问appsettings.json配置文件了
二. 创建Jwt授权认证
重头戏来了!
-
在
appsettings.json
中添加Jwt字段- Issuer(发行人)
- Audience(受众)
- SecurityKey(秘钥)
"JwtSettings": { "Issuer": "zyknow", "Audience": "audience", "SecurityKey": "zyknowzyknowzyknowzyknowzyknowzyknow" }
</br>
-
在webApi项目中创建Extensions文件夹并创建
AuthorizationSetup.cs
类,这样把在Startup.cs做的操作提出来,这样Startup.cs看起来更简洁些,如图所示,并且粘贴代码
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, }; }); } }
</br>
-
注册Jwt到Startup.cs中
</br>
-
创建Login控制器并创建GetToken方法
这个控制器是用来发放Jwt Token的,通过把请求中携带的userRole添加到Jwt授权中并返回(实际应该是查询数据库,然后返回对应的权限等级之类的,比如admin,SuperAdmin,User等)
注意一下代码中自定义的Jwt字段,后面会提到
[Route("api/[controller]")] [ApiController] [AllowAnonymous] public class LoginController : ControllerBase { /// <summary> /// 颁发令牌接口 /// </summary> /// <returns></returns> [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)}); } }
</br>
-
开启调试,看看是否能正确请求到Token
这里给大家推荐一下国人开发的谷歌浏览器调试插件,功能跟Postman一样,非常便携还是中文apizza进入正题,使用Http请求工具进行请求Login/GetJwtToken方法
可以看到成功的获取到了Token
我们可以复制token在Jwt官网中解析看看
接下来输入我们在appsettings.json中的SecurityKey字段
效验成功了!有效令牌!
通过在PAYLOAD定义的公共属性(若没有私钥伪造令牌),可以很好的实现跨域信息共享
</br> -
在控制器中使用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<WeatherForecastController> _logger; public WeatherForecastController(ILogger<WeatherForecastController> logger) { _logger = logger; } [HttpGet] [Route(nameof(AllGet))] [AllowAnonymous] //没有token也能访问 public IEnumerable<WeatherForecast> 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<WeatherForecast> 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<WeatherForecast> 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<WeatherForecast> 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<WeatherForecast> 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(); } }
</br>
-
开启调试
这里就不一个个方法给大家演示了,有兴趣的同学可以自己下载demo进行演示
这里只给大家演示一下成功示例-
不携带Token请求,无法请求
-
在请求头中携带token时请注意格式如下!
携带没有权限的token请求
先请求login获取token,注意看,这里使用的User
复制token,再请求AdminPolicy接口,可以发现状态码为403,请求成功,但是权限错误,所以请求不到数据
-
最后,使用Admin生成的token来请求
-
自定义授权处理
这部分主要是讲解Jwt授权认证的自定义授权处理环节
-
创建PolicyRequirement文件夹并创建
AdminRequirement.cs
类和MustRoleAdminHandler.cs
类</br>
AdminRequirement.cs
public class AdminRequirement : IAuthorizationRequirement { //这里定义的字段是在Login GetToken方法授权时确定的 public string Name { get; set; } }
</br>
MustRoleAdminHandler.cs
//继承AuthorizationHandler<AdminRequirement> public class MustRoleAdminHandler : AuthorizationHandler<AdminRequirement> { //重写授权认证方法,每次jwt认证都会进入这个方法,可以开启断点调试查看 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminRequirement requirement) { //这里获取的是请求时AdminRequirement类中在jwt包含的字段 Console.WriteLine(requirement); context.Succeed(requirement); return Task.CompletedTask; } }
</br>
-
在
AuthorizationSetup.cs
中添加如下代码
// 基于自定义处理策略 o.AddPolicy("AdminRequireMent", o => { o.Requirements.Add(new AdminRequirement() { Name = "zyknow" });//完全自定义 });
//依赖注入自定义处理策略 services.AddSingleton<IAuthorizationHandler, MustRoleAdminHandler>();
</br>
-
在
WeatherForecastController.cs
中添加使用自定义授权的方法
[HttpGet(nameof(AdminRequireMent))] [Authorize(Policy = "AdminRequireMent")] //自定义授权 public IEnumerable<WeatherForecast> 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(); }
</br>
-
接着启动程序,断点调试,带Jwt Token请求方法
进入断点,可以获取到自定义授权策略的类,这里直接返回成功了,主要是只是为了演示,在这里可以做自己的逻辑处理
源码
下载Jwt分支即可