简介
JWT(JSON Web Token)是一种开放标准(RFC 7519),它提供了一种在网络应用间安全传输信息的简洁、自包含的方式。JWT主要用于身份验证和授权机制,是一种轻量级的、可扩展的、自包含的身份验证和授权解决方案。以下是JWT的详细介绍:
JWT的组成
JWT由三部分组成:头部(Header)、载荷(Payload)和签名(Signature),这三部分之间使用点(.)分隔。
- 头部(Header) :
- 头部是一个JSON对象,通常包含两个字段:
alg
(算法)和typ
(类型)。 alg
字段指定了用于签名的哈希算法(如HMAC SHA256或RSA)。typ
字段通常设置为JWT。- 头部信息会被Base64编码后用于JWT的字符串表示中。
- 头部是一个JSON对象,通常包含两个字段:
- 载荷(Payload) :
- 载荷也是一个JSON对象,包含声明(Claims)。声明是关于实体(如用户)和其他数据的声明。
- JWT标准中定义了七个预定义的声明字段,如
iss
(发行人)、exp
(到期时间)、sub
(主题)、aud
(用户)、nbf
(在此之前不可用)、iat
(发布时间)和jti
(JWT ID)。 - 除了这些预定义字段外,还可以添加自定义的声明字段。
- 载荷信息同样会被Base64编码后用于JWT的字符串表示中。
- 签名(Signature) :
- 签名用于验证JWT的完整性和真实性。
- 签名是通过将头部和载荷的Base64编码字符串用指定的算法(如HMAC SHA256)和密钥进行签名得到的。
- 签名确保了JWT在传输过程中不会被篡改,并且验证了JWT的发送者身份。
案例演示
安装Nuget包
在接下来的操作步骤中,我们需要如下的Nuget包
Install-Package System.IdentityModel.Tokens.Jwt
Install-Package Microsoft.IdentityModel.Tokens
Install-Package Microsoft.AspNetCore.Authentication.JwtBearer
配置密钥
在配置文件appsettings.json中加入如下配置
在JWT(JSON Web Tokens)中,Issuer
(发行者)、Audience
(受众)和SecurityKey
(密钥)是生成和验证JWT时非常重要的三个参数。下面解释这三个值通常是什么样子的:
Issuer
(发行者) :- 这个值标识了JWT的签发者。它通常是一个唯一的字符串,用于表明是哪个实体签发了这个JWT。在实际应用中,这可以是一个服务的URL、服务名称、组织名称或任何其他能够唯一标识签发者的字符串。
- 示例:
"https://auth.example.com"
或"ExampleService"
Audience
(受众) :- 这个值标识了JWT的接收者(或受众)。它同样是一个字符串,用于表明这个JWT是打算给哪个实体使用的。这有助于JWT的接收方验证JWT是否真的是为他们而签发的。
- 示例:
"https://api.example.com"
或"ExampleClientApp"
- 在某些情况下,JWT可以有多个受众,这时可以使用一个包含多个受众字符串的数组。但是,在大多数JWT库中,
aud
(受众)字段通常只接受单个字符串或字符串数组的一个元素。
SecurityKey
(密钥) :- 这个值用于签名JWT,以确保JWT的完整性和来源的可靠性。它应该是一个保密的字符串,只有JWT的签发者和验证者知道。这个密钥可以是任何足够复杂和难以猜测的字符串,但出于安全考虑,通常建议使用随机生成的密钥或密钥对(在使用RSA等算法时)。
- 示例:一个随机生成的字符串,如
"8f7c09b7-03a5-4d36-843c-e82f13e56365"
,或者使用专门的密钥管理工具生成的密钥。 - 注意:在生产环境中,应确保密钥的安全存储,避免硬编码在代码中或容易泄露的地方。
当生成JWT时,发行者(iss
)和受众(aud
)会被包含在JWT的header
和payload
部分中,并使用密钥(SecurityKey
)进行签名。接收JWT的实体可以通过验证签名和检查发行者和受众来确认JWT的合法性和有效性。
Token生成服务
首先我们新建一个User实体类,用于声明用户的名称和密码
cs
public class UserRes
{
/// <summary>
/// 用户名
/// </summary>
[Required]
public string Name { get; set; }
/// <summary>
/// 密码
/// </summary>
[Required]
public string Password { get; set; }
}
然后新建CustomJWTService
的类,它用于生成和返回一个基于 JSON Web Tokens (JWT) 的访问令牌。
cs
public class CustomJWTService
{
private readonly JwtTokenOptions _jwtTokenOptions;
public CustomJWTService(IOptionsMonitor<JwtTokenOptions> jwtTokenOptions)
{
_jwtTokenOptions = jwtTokenOptions.CurrentValue;
}
public string GetToken(UserRes user)
{
var claims = new List<Claim>()
{
new Claim("Name", user.Name),
new Claim("Password", user.Password),
};
//需要加密:需要加密key:
//Nuget引入:Microsoft.IdentityModel.Tokens
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_jwtTokenOptions.SecurityKey));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//token过期时间,10分钟有效期
var expires = DateTime.Now.AddMinutes(10);
//Nuget引入:System.IdentityModel.Tokens.Jwt
JwtSecurityToken token = new JwtSecurityToken(
issuer: _jwtTokenOptions.Issuer,
audience: _jwtTokenOptions.Audience,
claims: claims,
expires: expires,
signingCredentials: creds);
string returnToken = new JwtSecurityTokenHandler().WriteToken(token);
return returnToken;
}
}
代码解读:
GetToken
方法接收一个UserRes
类型的参数user
,这个参数预期包含用户的名称和密码(在实践中,通常不建议在 JWT 令牌中包含密码,因为它增加了安全风险)。- 创建一个
List<Claim>
来存储要包含在 JWT 中的声明(claims)。在这个例子中,声明包括用户的名称和密码。 - 使用
SymmetricSecurityKey
和从配置中获取的密钥(_jwtTokenOptions.SecurityKey
)创建一个SymmetricSecurityKey
实例。这个密钥用于签名 JWT,以确保其完整性和真实性。 - 创建一个
SigningCredentials
实例,它指定了用于签名的算法(HmacSha256
)和密钥。 - 设置令牌的过期时间为当前时间加上 10 分钟。
- 使用
JwtSecurityToken
构造函数创建一个新的 JWT 令牌。这个构造函数需要发行者、受众、声明、过期时间和签名凭据作为参数。 - 使用
JwtSecurityTokenHandler
的WriteToken
方法将JwtSecurityToken
对象序列化为一个字符串,这个字符串就是 JWT 令牌,可以发送给客户端。
依赖注入
cs
//读取appsettings的JWTTokenOptions,注册JWT
builder.Services.Configure<JwtTokenOptions>(builder.Configuration.GetSection("JWTTokenOptions"));
//将CustomJWTService类注册为ASP.NET Core DI容器中的单例服务。
builder.Services.AddSingleton<CustomJWTService>();
控制器增加获取token接口
新建controller,测试一下我们的获取token服务
cs
[Route("api/[controller]/[action]")]
[ApiController]
public class TestController : ControllerBase
{
private CustomJWTService _jwtService;
public TestController(CustomJWTService jwtService)
{
_jwtService = jwtService;
}
[HttpPost]
public string GetToken(UserRes userRes)
{
return _jwtService.GetToken(userRes);
}
}
启动项目,在swagger中设置user信息,发送请求!
将得到的token复制到在线jwt解密网站 jwt解密/加密 - bejson在线工具 解码一下,可以看到payload中的信息就是我们刚刚输入的user信息,证明token有效。
JWT校验中间件
得到了token,接下来我们就要使用token来进行请求的校验鉴权操作了
因为代码比较多,为了代码的整洁性,我们新建一个WebApplicationBuilder的扩展类,并将刚刚的依赖注入代码也搬过来
cs
public static class HostBuiderExtend
{
public static void Register(this WebApplicationBuilder builder)
{
//读取appsettings的JWTTokenOptions,注册JWT
builder.Services.Configure<JwtTokenOptions>(builder.Configuration.GetSection("JWTTokenOptions"));
//将CustomJWTService类注册为ASP.NET Core DI容器中的单例服务。
builder.Services.AddSingleton<CustomJWTService>();
#region JWT校验
//增加鉴权逻辑
JwtTokenOptions tokenOptions = new JwtTokenOptions();
builder.Configuration.Bind("JWTTokenOptions", tokenOptions);
//需要引入nuget包,Microsoft.AspNetCore.Authentication.JwtBearer
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) //Scheme
.AddJwtBearer(options => //这里是配置的鉴权的逻辑
{
options.TokenValidationParameters = new TokenValidationParameters
{
//JWT有一些默认的属性,就是给鉴权时就可以筛选了
ValidateIssuer = true, //是否验证Issuer
ValidateAudience = true, //是否验证Audience
ValidateLifetime = true, //是否验证失效时间
ValidateIssuerSigningKey = true, //是否验证SecurityKey
ValidAudience = tokenOptions.Audience, //
ValidIssuer = tokenOptions.Issuer, //Issuer,这两项和前面签发jwt的设置一致
IssuerSigningKey =
new SymmetricSecurityKey(Encoding.UTF8.GetBytes(tokenOptions.SecurityKey)) //拿到SecurityKey
};
});
#endregion
}
}
然后在Program.cs
文件中调用扩展方法,并且启动权限验证
app.UseAuthentication();
app.UseAuthentication();
调用将认证中间件添加到应用程序的请求处理管道中。这个中间件负责处理与用户身份验证相关的所有事情,比如读取请求中的认证令牌(如JWT令牌、Cookie等),验证这些令牌的有效性,以及将验证结果(如用户身份)附加到当前请求上,以便后续中间件或控制器可以访问。
简而言之,UseAuthentication
是处理"你是谁?"这个问题的,它确保了应用程序能够识别出当前请求是由哪个用户发出的。
app.UseAuthorization();
app.UseAuthorization();
调用将授权中间件添加到应用程序的请求处理管道中,并且这个中间件应该总是在UseAuthentication
之后调用。授权中间件使用认证中间件提供的信息(比如当前用户的身份)来决定当前用户是否有权访问请求的资源。这包括检查用户是否拥有足够的权限或角色来访问特定的控制器、操作或资源。
简而言之,UseAuthorization
是处理"你能做什么?"这个问题的,它确保了只有被授权的用户才能访问特定的应用程序功能或资源。
控制器增加获取用户信息接口
接下来我们在控制器中添加一个get请求,并使用[Authorize]
属性标记,在请求进来时,JWT认证中间件会验证token的有效性。然后我们将token中我们写进去的用户信息拿出来,返回给调用请求者。
cs
[HttpGet]
[Authorize]
public UserRes GetUser()
{
//解析token
string token = Request.Headers["Authorization"];
if (token.StartsWith("Bearer "))
{
token = token.Substring("Bearer ".Length);
}
var handler = new JwtSecurityTokenHandler();
var jwtToken = handler.ReadJwtToken(token);
var nameClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "Name").Value;
var passwordClaim = jwtToken.Claims.FirstOrDefault(claim => claim.Type == "Password").Value;
var userRes = new UserRes()
{
Name = nameClaim,
Password = passwordClaim
};
return userRes;
}
因为swagger中我没有开启携带tokne,因此这里使用postman进行演示,先直接对我们的接口发送一个请求,会得到一个401的响应,这是因为我们没有设置token。
状态码 401 Unauthorized
代表客户端错误,指的是由于缺乏目标资源要求的身份验证凭证,发送的请求未得到满足。
在请求头(headers)中添加一个Authorization,值为 Bearer + 空格 + 我们上一个请求获得的token,这时发送请求,权限校验可以通过了,并且刚刚设置的用户信息就被解析出来并返回了。