一、什么是JWT
根据维基百科定义,JWT(读作 [/dʒɒt/]),即JSON Web Tokens,是一种基于JSON的、用于在网络上声明某种主张的令牌(token)。JWT通常由三部分组成: 头信息(header), 消息体(payload)和签名(signature)。它是一种用于双方之间传递安全信息的表述性声明规范。JWT作为一个开放的标准(RFC 7519),定义了一种简洁的、自包含的方法,从而使通信双方实现以JSON对象的形式安全的传递信息。
二、在appsettings.json中配置jwt参数的值
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppSettings": {
//数据库连接字符串
"ConnectionStrings": "Server=127.0.0.1;User Id=id;Password=pwd;Database=dbname;",
//jwt参数配置
"JwtSetting": {
"Issuer": "jwtIssuer", //颁发者
"Audience": "jwtAudience", //可以给哪些客户端使用
"SecretKey": "jwtsecretkeysixteen" //加密的Key,大于16位的字符串
}
}
}
三、新建用户信息类TokenModel.cs
namespace Model
{
/// <summary>
/// 令牌
/// </summary>
public class TokenModel
{
/// <summary>
/// ID
/// </summary>
public string? Uid { get; set; }
/// <summary>
/// 角色
/// </summary>
public string? Role { get; set; }
}
}
四、新建JwtHelper.cs辅助类,并在Nuget添加包
IdentityModel,
Microsoft.AspNetCore.Authentication.JwtBearer,
Microsoft.AspNetCore.Authorization
using Microsoft.IdentityModel.Tokens;
using Model;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace Common
{
public class JwtHelper
{
/// <summary>
/// 颁发JWT字符串
/// </summary>
/// <param name="tokenModel"></param>
/// <returns></returns>
public static string IssueJwt(TokenModel tokenModel)
{
//获Appsetting
//文章地址:
string iss = AppSetting.app(new string[] { "AppSettings", "JwtSetting", "Issuer" });//颁发者
string aud = AppSetting.app(new string[] { "AppSettings", "JwtSetting", "Audience" });//可以给哪些客户端使用
string secret = AppSetting.app(new string[] { "AppSettings", "JwtSetting", "SecretKey" });//加密的Key
//定义需要使用的Claim
var claims = new List<Claim>
{
new Claim(JwtRegisteredClaimNames.Jti, $"{tokenModel.Uid}"),
new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
//这个就是过期时间,目前是过期180秒,可自定义,注意JWT有自己的缓冲过期时间
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(180)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(180).ToString()),
new Claim(JwtRegisteredClaimNames.Iss,iss),
new Claim(JwtRegisteredClaimNames.Aud,aud),
};
// 可以将一个用户的多个角色全部赋予;
#pragma warning disable CS8602 // 解引用可能出现空引用。
claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
#pragma warning restore CS8602 // 解引用可能出现空引用。
//秘钥 (SymmetricSecurityKey 对安全性的要求,密钥的长度太短会报出异常)
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(
issuer: iss,
claims: claims,
signingCredentials: creds);
var jwtHandler = new JwtSecurityTokenHandler();
var encodedJwt = jwtHandler.WriteToken(jwt);
return encodedJwt;
}
/// <summary>
/// 解析
/// </summary>
/// <param name="jwtStr"></param>
/// <returns></returns>
public static TokenModel SerializeJwt(string jwtStr)
{
var jwtHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
object role;
try
{
#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。
jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var tm = new TokenModel
{
Uid = jwtToken.Id.ToString(),
Role = role != null ? role.ToString() : "",
};
return tm;
}
}
}
五、UserController新建Login接口,用来获取token
using Common;
using Microsoft.AspNetCore.Mvc;
using Model;
namespace MainProject.Controllers
{
/// <summary>
/// 用户控制器
/// </summary>
[Route("[controller]/[action]")]
[ApiController]
public class UserController : ControllerBase
{
/// <summary>
/// 登录验证
/// </summary>
/// <param name="role">用户角色,通常是通过账号密码验证后获取,这里测试直接当做参数</param>
/// <returns></returns>
[HttpGet]
public IActionResult Login(string role)
{
string jwtStr = string.Empty;
bool suc = false;
if (role != null)
{
// 将用户id和角色名,作为单独的自定义变量封装进 token 字符串中。
TokenModel tokenModel = new TokenModel { Uid = "123", Role = role };
jwtStr = JwtHelper.IssueJwt(tokenModel);//获取到一定规则的 Token 令牌
suc = true;
}
else
{
jwtStr = "login fail!!!";
}
return Ok(new
{
success = suc,
token = jwtStr
});
}
}
}
运行项目。使用swagger调用Login接口成功得到token,如下图
六、得到token字符串后,我们在调用业务接口就必定要输入token令牌,平时我们只需要在Header中添加Authorization属性,但在Swagger作为接口文档时,我们需要在Program.cs添加以下代码。
builder.Services.AddSwaggerGen(options =>
{
#region 在接口中添加token
options.OperationFilter<SecurityRequirementsOperationFilter>();
//Token绑定到ConfigureServices
options.AddSecurityDefinition("oauth2", new OpenApiSecurityScheme
{
Description = "JWT授权(数据将在请求头中进行传输) 直接在下框中输入Bearer {token}(注意两者之间是一个空格)\"",
Name = "Authorization",//jwt默认的参数名称
In = ParameterLocation.Header,//jwt默认存放Authorization信息的位置(请求头中)
Type = SecuritySchemeType.ApiKey
});
}
//jwt授权验证
builder.Services.AddAuthorizationSetup();
//注意中间件的顺序,UseRouting放在最前边,UseAuthentication在UseAuthorization前边
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
运行,就可以在 swagger/index.html 页面里看到这个Token入口了:
七、JWT授权认证,新建AuthorizationSetup.cs,新建注册服务方法
using Common;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text;
namespace MainProject.Setup
{
/// <summary>
/// 注册JWT服务方法
/// </summary>
public static class AuthorizationSetup
{
/// <summary>
///
/// </summary>
/// <param name="services"></param>
/// <exception cref="ArgumentNullException"></exception>
public static void AddAuthorizationSetup(this IServiceCollection services)
{
if (services == null) throw new ArgumentNullException(nameof(services));
// 1【授权】、这个和上边的异曲同工,好处就是不用在controller中,写多个 roles 。
// 然后这么写 [Authorize(Policy = "Admin")]
//services.AddAuthorization(options =>
//{
// options.AddPolicy("User", policy => policy.RequireRole("User").Build());
// options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));
//});
//读取配置文件
var symmetricKeyAsBase64 = AppSetting.app(new string[] { "AppSettings", "JwtSetting", "SecretKey" });
var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);
var signingKey = new SymmetricSecurityKey(keyByteArray);
var Issuer = AppSetting.app(new string[] { "AppSettings", "JwtSetting", "Issuer" });
var Audience = AppSetting.app(new string[] { "AppSettings", "JwtSetting", "Audience" });
// 令牌验证参数
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = signingKey,
ValidateIssuer = true,
ValidIssuer = Issuer,//发行人
ValidateAudience = true,
ValidAudience = Audience,//订阅人
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30),
RequireExpirationTime = true,
};
//2.1【认证】、core自带官方JWT认证
// 开启Bearer认证
services.AddAuthentication("Bearer")
// 添加JwtBearer服务
.AddJwtBearer(o =>
{
o.TokenValidationParameters = tokenValidationParameters;
o.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// 如果过期,则把<是否过期>添加到,返回头信息中
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
}
}
}
在Program.cs添加以下代码,在第六点代码中已写好,取消注释即可,注意位置
//jwt授权验证
services.AddAuthorizationSetup();
app.UseRouting();
app.UseAuthentication();
八、测试,接口
/// <summary>
/// 需要Admin权限
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize(Roles = "Admin")]
public IActionResult Admin()
{
return Ok("hello admin");
}
/// <summary>
/// 需要System权限
/// </summary>
/// <returns></returns>
[HttpGet]
[Authorize(Roles = "System")]
public IActionResult System()
{
return Ok("hello System");
}
F5运行项目,获取一个Admin权限的token,并放到swagger 的权限验证按钮里面
测试可知可以成功调取Admin,而System则会失败。
加入多角色怎么处理?我们把第七点注释代码取消注释,详情看代码,自行理解。即多角色访问,的代码如下图