JWT (JSON Web Token) 是当前非常流行的无状态身份验证的一种解决方案,其原理为通过加密算法生成一个字符串,将字符串置于每次请求的
Header
中来进行身份验证。
JWT的构成
一个有效的JWT字符串示例如下:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1ODMyNDM0NzQsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIn0.nrVYSkcIKMFdd9HSjkZz_7Kkcx3sdjEdFI9nT42OKac
可以看出JWT 字符串由.
分隔开来的三部分构成:
- Header
- Payload
- Signature
Header
将JWT
的Header
部分eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
通过Base64
解码可以得到其内容为:
{
"alg": "HS256",
"typ": "JWT"
}
容易看出Header
包含了两个部分:
类型 | 全称 | 释义 |
---|---|---|
alg | algorithm | 签名算法名称 |
typ | type | token类型 |
Payload
Payload
即载荷,载荷中主要放一些声明(Claims)
性的信息,比如用户名,角色等信息,因为是未加密的,所以不要放置密码等敏感的信息。
在这里同样将JWT
的Payload
部分eyJleHAiOjE1ODMyNDM0NzQsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIn0
通过Base64
解码得到其内容为:
{
"exp":1583243474,
"iss":"Issuer",
"aud":"Audience"
}
容易看出这些键值对所代表的含义:
类型 | 全称 | 释义 |
---|---|---|
exp | expires | 过期时间 |
iss | issuer | 发行者 |
aud | audience | 受众 |
Signature
Signature
签名部分,由Header
和Payload
两部分编码用Header
里面注明的加密算法加密而成。
HMACSHA256(
Base64UrlEncoder.Encode(header) + "." +
Base64UrlEncoder.Encode((payload),
Secret)
这里的Secret
是自己确定的密钥。
ASP.NET Core 实现JWT
开发环境准备
- 操作系统:Windows 10 1909
- IDE:Visual Studio Enterprise 2019 16.4.5
- .NET Core Runtime:3.1.102
新建项目
在Visual Studio
中新建一个ASP.NET Core Web API
项目,如下所示:
通过Nuget
安装JWT
相关包
在Nuget
包管理器中安装Microsoft.AspNetCore.Authentication.JwtBearer
包,如下所示
在Startup
中配置JWT
信息
在ConfigureServices
方法中加入配置后如下:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = "Issuer",
ValidateAudience = true,
ValidAudience = "Audience",
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("{069BD1DF-72D8-474B-8950-2C3EB03B2D03}")),
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(30),
};
});
services.AddSingleton();
}
其中services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
是将Bearer
作为身份认证的默认方案,JwtBearerDefaults.AuthenticationScheme
是Bearer
字符串常量。
而services.AddJwtBearer
是进行JWT
的相关配置。
添加认证管道
在Configure
方法中加入:
app.UseAuthentication();
注意它需要添加在app.UseAuthorization();
之前,app.UseRouting();
之后,顺序很重要,否则身份认证不会生效。
新建LoginController
签发token
namespace JwtTokenDemo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
// POST: api/Login
[HttpPost]
public IActionResult Index([FromBody] LoginViewModel model)
{
if (_names.Contains(model.Username) && model.Password == "admin")
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("{069BD1DF-72D8-474B-8950-2C3EB03B2D03}"));
var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var token = new JwtSecurityToken(
"Issuer",
"Audience",
new List
{
new Claim(ClaimTypes.Role, "admin"),
new Claim(ClaimTypes.Gender,"Male"),
new Claim(ClaimTypes.Name,model.Username),
},
expires: DateTime.UtcNow.AddMinutes(30),
signingCredentials: credentials
);
return Ok(new
{
access_token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
return Unauthorized();
}
}
}
在上面,我们定义了token
的过期时间为令牌颁发后的30分钟后,同时定义了Role
、Gender
、Name
的Claim
声明,这些都可以通过解码Payload
部分查看到。
这里用了一个用户类接受账号密码:
public class LoginViewModel
{
[JsonProperty("username")]
public string Username { get; set; }
[JsonProperty("password")]
public string Password { get; set; }
}
设置受保护的资源
在需要验证的控制器或者HTTP请求方法上添加[Authorize]
修饰头,请求的时候就会服务器验证当前请求是否有权限访问此资源。如,我们给Weather
控制器加上需要授权才能访问:
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};
private readonly ILogger _logger;
private readonly WeatherForecast _weatherForecast;
public WeatherForecastController(ILogger logger,WeatherForecast weatherForecast)
{
_logger = logger;
_weatherForecast = weatherForecast;
}
[HttpGet]
public IEnumerable Get()
{
var user = HttpContext.User;
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => _weatherForecast)
.ToArray();
}
}
Postman 测试
以FromBody
提交用户名和密码来获取access_token
验证资源
将access_token
的值加入到HTTP请求的Header中的Authorization
去,正确格式为:
Authorization:Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL3JvbGUiOiJhZG1pbiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL2dlbmRlciI6Ik1hbGUiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoiYWRtaW4iLCJleHAiOjE1ODMzMzk4MTMsImlzcyI6Iklzc3VlciIsImF1ZCI6IkF1ZGllbmNlIn0.ln6ZZ2b_eNgcDxW7zrmI6xY4clxdu4f-zScJ43FjmwY
注意Bearer
与后面的access_token
之间有一个空格。
我们请求资源可以看到200 OK
,请求成功。如下图:
当我们修改
token
中某个字符再次请求,就会发现401 Unauthorized
未授权响应。如下图:
欲知如何进行基于角色、基于声明、基于策略的授权,请听下回分解。