爲什麼使用JWT?
JWT實際上就是一個字符串,它由三部分組成,頭部、載荷與簽名。JWT不僅可用於認證,還可用於信息交換。善用JWT有助於減少服務器請求數據庫的次數。適用於多客戶端的前後端解決方案,JWT 是無狀態化的,更適用於 RESTful 風格的接口驗證。本文主要介紹使用JWT進行接口身份認證。
一.準備工作
(1).添加NuGet程序包
Microsoft.AspNetCore.Authentication.JwtBearer
(2).appsettings.json
"JwtConfig": {
"SecretKey": "d0ecd23c-dfdb-4005-a2ea-0fea210c858a", // 密鑰
"Issuer": "liucheng", // 頒發者
"Audience": "liucheng", // 接收者
"Expired": 30 // 過期時間(30min)
},
二.創建JWT配置類
/// <summary>
/// jwt配置
/// </summary>
public class JwtConfig : IOptions<JwtConfig>
{
public JwtConfig Value => this;
public string SecretKey { get; set; }
public string Issuer { get; set; }
public string Audience { get; set; }
public int Expired { get; set; }
public DateTime NotBefore => DateTime.UtcNow;
public DateTime IssuedAt => DateTime.UtcNow;
public DateTime Expiration => IssuedAt.AddMinutes(Expired);
private SecurityKey SigningKey => new SymmetricSecurityKey(Encoding.UTF8.GetBytes(SecretKey));
public SigningCredentials SigningCredentials =>
new SigningCredentials(SigningKey, SecurityAlgorithms.HmacSha256);
}
三.創建JWT工具類
public class GenerateJwt
{
private readonly JwtConfig _jwtConfig;
public GenerateJwt(IOptions<JwtConfig> jwtConfig)
{
_jwtConfig = jwtConfig.Value;
}
/// <summary>
/// 生成token
/// </summary>
/// <param name="sub"></param>
/// <param name="customClaims">攜帶的用戶信息</param>
/// <returns></returns>
public JwtTokenResult GenerateEncodedTokenAsync(string sub, LoginUserModel customClaims)
{
//創建用戶身份標識,可按需要添加更多信息
var claims = new List<Claim>
{
new Claim("userid", customClaims.userid),
new Claim("username", customClaims.username),
new Claim("realname",customClaims.realname),
new Claim("roles", string.Join(";",customClaims.roles)),
new Claim("permissions", string.Join(";",customClaims.permissions)),
new Claim("normalPermissions", string.Join(";",customClaims.normalPermissions)),
new Claim(JwtRegisteredClaimNames.Sub, sub),
};
//創建令牌
var jwt = new JwtSecurityToken(
issuer: _jwtConfig.Issuer,
audience: _jwtConfig.Audience,
claims: claims,
notBefore: _jwtConfig.NotBefore,
expires: _jwtConfig.Expiration,
signingCredentials: _jwtConfig.SigningCredentials);
string access_token = new JwtSecurityTokenHandler().WriteToken(jwt);
return new JwtTokenResult()
{
access_token = access_token,
expires_in = _jwtConfig.Expired * 60,
token_type = JwtBearerDefaults.AuthenticationScheme,
user = customClaims
};
}
}
四.JwtTokenResult
登錄成功生成jwt返回給前端的model,LoginUserModel是用戶信息model,我這裏攜帶是爲了避免前端解析token。
/// <summary>
/// 登錄成功返回model
/// </summary>
public class JwtTokenResult
{
public string access_token { get; set; }
public string refresh_token { get; set; }
/// <summary>
/// 過期時間(單位秒)
/// </summary>
public int expires_in { get; set; }
public string token_type { get; set; }
public LoginUserModel user { get; set; }
}
public class LoginUserModel
{
public string userid { get; set; }
public string username { get; set; }
public string realname { get; set; }
public string roles { get; set; }
public string permissions { get; set; }
public string normalPermissions { get; set; }
}
五.JwtUser,用戶解析jwt 獲取當前用戶信息
public static class JwtUser
{
/// <summary>
/// 解析jwt 獲取當前用戶信息
/// </summary>
/// <param name="request"></param>
/// <returns>用戶信息</returns>
public static LoginUserModel GetRequestUser(this HttpRequest request)
{
var authorization = request.Headers["Authorization"].ToString();
var auth = authorization.Split(" ")[1];
var jwtArr = auth.Split('.');
var dic = JsonConvert.DeserializeObject<Dictionary<string, string>>(Base64UrlEncoder.Decode(jwtArr[1]));
//解析Claims
var reqUser = new LoginUserModel
{
userid = dic["userid"],
username = dic["username"],
realname = dic["realname"],
roles = dic["roles"],
permissions = dic["permissions"],
normalPermissions = dic["normalPermissions"],
};
return reqUser;
}
六.Startup配置
(1) .ConfigureServices注入jwt
//注入jwt
services.AddScoped<GenerateJwt>();
services.Configure<JwtConfig>(Configuration.GetSection("JwtConfig"));
#region jwt驗證
var jwtConfig = new JwtConfig();
Configuration.Bind("JwtConfig", jwtConfig);
services
.AddAuthentication(option =>
{
//認證middleware配置
option.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
option.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
//Token頒發機構
ValidIssuer = jwtConfig.Issuer,
//頒發給誰
ValidAudience = jwtConfig.Audience,
//這裏的key要進行加密
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecretKey)),
//是否驗證Token有效期,使用當前時間與Token的Claims中的NotBefore和Expires對比
ValidateLifetime = true,
};
});
#endregion
(2).Configure
app.UseAuthentication();//jwt
七.登錄接口生成jwt(部分代碼)
這裏需要注意,將refreshToken存入redis 設置15天過期
var claims = new LoginUserModel()
{
userid = user.Id.ToString(),
username = user.UserName,
realname = user.RealName,
roles = string.Join(";", rolesReturn),
permissions = string.Join(";", permissionReturn),
normalPermissions = string.Join(";", PermissionHelper.GetPermission(2)),
};
var refreshToken = Guid.NewGuid().ToString();
//refreshToken設置15天過期
await RedisHelper.SetAsync(refreshToken, claims, 60 * 60 * 24 * 15);
var jwtTokenResult = _generateJwt.GenerateEncodedTokenAsync(user.Id.ToString(), claims);
jwtTokenResult.refresh_token = refreshToken;
八.刷新token
設置的token有效期爲30min,前端拿到token後需要定時刷新token(在token失效臨界點刷新)。
根據refreshToken獲取新的token,首先驗證在redis中能否找到refreshToken,找不到則證明登錄已過期,找到後則生成新的token。
/// <summary>
/// 刷新token
/// </summary>
/// <param name="refreshToken"></param>
/// <returns></returns>
[HttpGet]
[Route("refreshToken")]
public async Task<JsonResult> RefreshToken(string refreshToken)
{
if (string.IsNullOrEmpty(refreshToken))
return AjaxHelper.JsonResult(HttpStatusCode.BadRequest, "refreshToken不能爲空");
var claims = await RedisHelper.GetAsync<LoginUserModel>(refreshToken);
if (claims == null)
return AjaxHelper.JsonResult(HttpStatusCode.Unauthorized, "登錄已過期");
var jwtTokenResult = _generateJwt.GenerateEncodedTokenAsync(claims.userid, claims);
jwtTokenResult.refresh_token = refreshToken;
return AjaxHelper.JsonResult(HttpStatusCode.OK, "成功", jwtTokenResult);//200
}
九.使用
在接口上標記身份驗證