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<WeatherForecast>();
}
其中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<Claim>
{
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<WeatherForecastController> _logger;
private readonly WeatherForecast _weatherForecast;
public WeatherForecastController(ILogger<WeatherForecastController> logger,WeatherForecast weatherForecast)
{
_logger = logger;
_weatherForecast = weatherForecast;
}
[HttpGet]
public IEnumerable<WeatherForecast> 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 未授权响应。如下图:
欲知如何进行基于角色、基于声明、基于策略的授权,请听下回分解。