.Net Core 基于JWT签发Token
如果不了解JWT可以先了解这篇文章 。 这里主要是来记录一下怎样使用Jwt 自己来签发和刷新Token,很多地方不符合实际使用,只是为了在这里测试达到效果,正式使用根据实际情况修改代码
1. 添加Nuget引用
Microsoft.AspNetCore.Authentication.JwtBeare
System.IdentityModel.Tokens.Jwt
2. 添加简单封装的工具类
public class JwtHelper
{
public IConfiguration _configuration { get; set; }
public JwtHelper(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// 生成AccessToken
/// </summary>
/// <param name="username">这里测试用的是用户信息,可以传入其他信息</param>
/// <returns></returns>
public string GenerateAccessToken(string username)
{
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
string issuer = _configuration.GetSection("JwtConfig:Issuer").Value;
// 获取SecurityKey
string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value;
//------------生成AccessToken----------------------------------
// token中的claims用于储存自定义信息,如登录之后的用户id等
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Sub,username),
new Claim(ClaimTypes.Role,"admin")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
//生成Token两种方式
//方式一
//var tokenDescriptor = new SecurityTokenDescriptor
//{
// Issuer = issuer,
// Audience = "testClient",
// NotBefore = DateTime.Now, // 预设值就是 DateTime.Now
// IssuedAt = DateTime.Now, // 预设值就是 DateTime.Now
// Subject = new ClaimsIdentity(claims),
// Expires = DateTime.Now.AddMinutes(30),
// SigningCredentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256)
//};
//var securityToken = jwtSecurityTokenHandler.CreateToken(tokenDescriptor);
//var serializeToken = jwtSecurityTokenHandler.WriteToken(securityToken);
//方式二
var token = new JwtSecurityToken(
issuer: issuer, // 发布者
audience: "testClient", // 接收者
notBefore: DateTime.Now, // token签发时间
expires: DateTime.Now.AddMinutes(30), // token过期时间
claims: claims, // 该token内存储的自定义字段信息
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法
);
return jwtSecurityTokenHandler.WriteToken(token);
}
/// <summary>
/// 生成RefreshToken
/// </summary>
/// <returns></returns>
public string GenerateRefreshToken()
{
string issuer = _configuration.GetSection("JwtConfig:Issuer").Value;
// 获取SecurityKey
string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value;
var refClaims = new[]
{
new Claim("role","refresh")
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
var refreshToken = new JwtSecurityToken(
issuer: issuer, // 发布者
audience: "testClient", // 接收者
notBefore: DateTime.Now, // token签发时间
expires: DateTime.Now.AddDays(7), // token过期时间
claims: refClaims, // 该token内存储的自定义字段信息
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法
);
// 返回成功信息,写出token
return new JwtSecurityTokenHandler().WriteToken(refreshToken);
}
/// <summary>
/// 刷新accessToken
/// </summary>
/// <param name="accessToken">过期的accessToken</param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public string RefreshToken(string accessToken)
{
string issuer = _configuration.GetSection("JwtConfig:Issuer").Value;
// 获取SecurityKey
string securityKey = _configuration.GetSection("JwtConfig:SecurityKey").Value;
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
bool isCan = jwtSecurityTokenHandler.CanReadToken(accessToken);//验证Token格式
if (!isCan)
throw new Exception("传入访问令牌格式错误");
//var jwtToken = jwtSecurityTokenHandler.ReadJwtToken(refreshtoken);//转换类型为token,不用这一行
var validateParameter = new TokenValidationParameters()//验证参数
{
ValidateAudience = true,
// 验证发布者
ValidateIssuer = true,
// 验证过期时间
ValidateLifetime = false,
// 验证秘钥
ValidateIssuerSigningKey = true,
// 读配置Issure
ValidIssuer = issuer,
// 读配置Audience
ValidAudience = "testClient",
// 设置生成token的秘钥
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey))
};
//验证传入的过期的AccessToken
SecurityToken validatedToken = null;
try
{
jwtSecurityTokenHandler.ValidateToken(accessToken, validateParameter, out validatedToken);//微软提供的验证方法。那个out传出的参数,类型是是个抽象类,记得转换
}
catch (SecurityTokenException)
{
throw new Exception("传入AccessToken被修改");
}
// 获取SecurityKey
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(securityKey));
var jwtToken = validatedToken as JwtSecurityToken;//转换一下
var accClaims = jwtToken.Claims;
var access_Token = new JwtSecurityToken(
issuer: "fcb", // 发布者
//audience: "myClient", // 接收者
notBefore: DateTime.Now, // token签发时间
expires: DateTime.Now.AddMinutes(30), // token过期时间
claims: accClaims, // 该token内存储的自定义字段信息
signingCredentials: new SigningCredentials(key, SecurityAlgorithms.HmacSha256) // 用于签发token的秘钥算法
);
// 返回成功信息,写出token
return new JwtSecurityTokenHandler().WriteToken(access_Token);
}
}
3. 修改Program.cs
(这里多设置了swagger,方便测试,如果实际情况不需要swagger进行测试可以去掉 AddSwaggerGen 中参数配置)
builder.Services.AddSwaggerGen(c => {
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Description = "在下框中输入请求头中需要添加Jwt授权Token:Bearer Token",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
BearerFormat = "JWT",
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
new string[] { }
}
});
});
builder.Services.AddScoped<JwtHelper>();
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
// 当验证失败时,表头WWW-Authenticate会返回失败原因
options.IncludeErrorDetails = true;
//配置Token的验证
options.TokenValidationParameters = new TokenValidationParameters
{
// 可以从 "sub" 取值并设定給 User.Identity.Name
NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier",
// 可以从 "roles" 取值,并可以从 [Authorize] 设置角色
RoleClaimType = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role",
// 一般我们都要验证 Issuer
ValidateIssuer = true,
ValidIssuer = builder.Configuration.GetValue<string>("JwtConfig:Issuer"),
// 通常不太需要验证 Audience
ValidateAudience = false,
//ValidAudience = "JwtAuthDemo", // 不验证就不需要
// 一般我们都会验证 Token 的有效期
ValidateLifetime = true,
// 如果 Token 中包含 key 才需要验证,一般都只有前面而已
ValidateIssuerSigningKey = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetValue<string>("JwtConfig:SecurityKey")))
};
});
添加认证管道,系统里面已经存在 UseAuthorization ,这里 在 UseAuthorization 之前添加 UseAuthentication
app.UseAuthentication(); app.UseAuthorization();
4. 添加控制器
这里刷新Token的接口限制了 [Authorize(Roles = "refresh")] ,只有 refreshToken 才有相应的角色,所以 需要换成 refreshToken ,并且传参之前过期的accessToken,目的主要是拿取token中的claim信息,方便生成新的accessToken重新写入进去, 当前也可以特别处理refreashToken,而取消传入失效的accessToken,我这里没有试过,理论上是可以的。这里还存在一个问题就是可以通过refreshToken 去请求 其他 需要的 accessToken 验证的接口 ,所以可以给相应的接口新增一些限制,只能通过 accessToken 去请求,比如下面的接口 Test2 限制了 [Authorize(Roles = "admin")] ,accessToken 才有admin的角色权限,只能通过accessToken 去请求
[Route("api/[controller]")]
[ApiController]
public class AccessController : ControllerBase
{
private JwtHelper _jwtHelper;
public AccessController(JwtHelper jwtHelper)
{
_jwtHelper = jwtHelper;
}
[Authorize]
[HttpGet("test")]
public ActionResult Test()
{
return Ok(HttpContext.User.Claims.Count());
}
[Authorize(Roles = "admin")]
[HttpGet("test2")]
public ActionResult Test2()
{
return Ok(HttpContext.User.Claims.Count());
}
[HttpGet("login")]
public ActionResult Login(string username, string password)
{
//校验账号密码,这里省略
if (!string.IsNullOrEmpty(username) && !string.IsNullOrEmpty(password))
{
var access_token= _jwtHelper.GenerateAccessToken(username);
var refresh_token = _jwtHelper.GenerateRefreshToken();
// 返回成功信息,写出token
return Ok(new { code = 200, message = "登录成功", accessToken = access_token, refreshToken = refresh_token });
}
// 返回错误请求信息
return BadRequest(new { code = 400, message = "登录失败,用户名或密码为空" });
}
//此方法用来刷新令牌,逻辑是验证refToken才能进入方法,进入后验证accessToken除了过期时间项的其他所有项,目的是防止用户修改权限等
[HttpGet("refresh")]
[Authorize(Roles = "refresh")]//验证权限
public ActionResult Refresh(string accessToken)
{
var newAccessToken = _jwtHelper.RefreshToken(accessToken);
//重新生成refeashToken
var refresh_token = _jwtHelper.GenerateRefreshToken();
// 返回成功信息,写出token
return Ok(new
{
code = 200,
message = "令牌刷新成功",
refreshToken = refresh_token,
accessToken = newAccessToken
});
}
}
5. 运行一下

获取到accessToken之后授权swagger (Bearer+" " + accessToken )


再请求一下刷新Token的接口

文章参考文档:
https://docs.microsoft.com/en-us/dotnet/api/system.identitymodel.tokens.jwt.jwtsecuritytokenhandler?view=azure-dotnet(官网)
https://www.cnblogs.com/zxy001126/p/15530864.html
https://www.cnblogs.com/hot-tofu-curd/p/15115844.html

浙公网安备 33010602011771号