如果不了解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
