0、引言
若不清楚什么是JWT的請先了解下什么是JWT。
1、關於Authentication與Authorization
我相信在aspnet core中剛接觸甚至用了段時間這兩個概念的時候都是一頭霧水的,傻傻分不清。
認證(Authentication)和授權(Authorization)在概念上比較的相似,且又有一定的聯系,因此很容易混淆。
認證(Authentication)是指驗證用戶身份的過程,即當用戶要訪問受保護的資源時,將其信息(如用戶名和密碼)發送給服務器並由服務器驗證的過程。
授權(Authorization)是驗證一個已通過身份認證的用戶是否有權限做某件事情的過程。
有過RBAC的開發經驗者來說這里可以這么通俗的來理解:認證是驗證一個用戶是否“合法”(一般就是檢查數據庫中是否有這么個用戶),授權是驗證這個用戶是否有做事情的權限(簡單理解成RBAC中的用戶權限)。
2、整個認證流程是怎樣的?
從圖中可以看到整個認證、授權的流程,先進行身份驗證 ,驗證通過后將Token放回給客戶端,客戶端訪問資源的時候請求頭中添加Token信息,服務器進行驗證並於授權是否能夠訪問該資源。
3、開始JWT身份認證
3.1 引入nuget包:Microsoft.AspNetCore.Authentication.JwtBearer

3.2 在Startup.cs文件中進行配置
#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
配置中間件
//啟用身份驗證功能。必須要在app.UseAuthorization();之前
app.UseAuthentication();//鑒權,
JWTHelper
public class JWTHelper
{
public class JwtHelper
{
/// <summary>
/// 頒發JWT字符串
/// </summary>
/// <param name="tokenModel"></param>
/// <returns></returns>
public static string IssueJwt(TokenModelJwt tokenModel)
{
// 自己封裝的 appsettign.json 操作類,看下文
string iss = Appsettings.app(new string[] { "Audience", "Issuer" });
string aud = Appsettings.app(new string[] { "Audience", "Audience" });
string secret = Appsettings.app(new string[] { "Audience", "Secret" });
var claims = new List<Claim>
{
/*
* 特別重要:
1、這里將用戶的部分信息,比如 uid 存到了Claim 中,如果你想知道如何在其他地方將這個 uid從 Token 中取出來,請看下邊的SerializeJwt() 方法,或者在整個解決方案,搜索這個方法,看哪里使用了!
2、你也可以研究下 HttpContext.User.Claims ,具體的你可以看看 Policys/PermissionHandler.cs 類中是如何使用的。
*/
new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ToString()),
new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
//這個就是過期時間,目前是過期7200秒,可自定義,注意JWT有自己的緩沖過期時間
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddSeconds(7200)).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Iss,iss),
new Claim(JwtRegisteredClaimNames.Aud,aud),
//new Claim(ClaimTypes.Role,tokenModel.Role),//為了解決一個用戶多個角色(比如:Admin,System),用下邊的方法
};
// 可以將一個用戶的多個角色全部賦予;
// 作者:DX 提供技術支持;
claims.AddRange(tokenModel.Role.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
//秘鑰 (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
//,expires:DateTime.Now.AddMinutes(1)
);
var jwtHandler = new JwtSecurityTokenHandler();
var encodedJwt = jwtHandler.WriteToken(jwt);
return encodedJwt;
}
/// <summary>
/// 解析
/// </summary>
/// <param name="jwtStr"></param>
/// <returns></returns>
public static TokenModelJwt SerializeJwt(string jwtStr)
{
var jwtHandler = new JwtSecurityTokenHandler();
JwtSecurityToken jwtToken = jwtHandler.ReadJwtToken(jwtStr);
object role;
try
{
jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var tm = new TokenModelJwt
{
Uid = (jwtToken.Id).ObjToInt(),
Role = role != null ? role.ObjToString() : "",
};
return tm;
}
}
/// <summary>
/// 令牌
/// </summary>
public class TokenModelJwt
{
/// <summary>
/// Id
/// </summary>
public long Uid { get; set; }
/// <summary>
/// 角色
/// </summary>
public string Role { get; set; }
/// <summary>
/// 職能
/// </summary>
public string Work { get; set; }
}
}
然后呢 如果你使用的Swagger
加上自動在Header帶上Token
Bearer就是在ASP.NET Core 在 Microsoft.AspNetCore.Authentication 下實現了一系列認證, 包含 Cookie, JwtBearer, OAuth, OpenIdConnect 等
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme()
{
Description = "JWT授權(數據將在請求頭中進行傳輸) 直接在下框中輸入Bearer {token}(注意兩者之間有一個空格)",
Name = "Authorization",//jwt默認的參數名稱,
In = ParameterLocation.Header,//jwt默認存放Autorization信息的位置(header中)
Type = SecuritySchemeType.ApiKey, //指定ApiKey
BearerFormat = "JWT",//標識承載令牌的格式 該信息主要是出於文檔目的
Scheme = "bearer"//授權中要使用的HTTP授權方案的名稱
});
//在Heder中添加Token 傳遞到后台
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme{
Reference = new OpenApiReference {
Type = ReferenceType.SecurityScheme,
Id = "Bearer"}
},new string[] { }
}
});
在對應Controller上或Action上加上Authorize標記

驗證TOken
options.Events = new JwtBearerEvents
{
//此處為權限驗證失敗后觸發的事件
OnChallenge = context =>
{
//此處代碼為終止.Net Core默認的返回類型和數據結果,這個很重要哦,必須
context.HandleResponse();
//自定義自己想要返回的數據結果,我這里要返回的是Json對象,通過引用Newtonsoft.Json庫進行轉換
var payload = JsonConvert.SerializeObject(new ApiResult<bool>("Token無效"));
//自定義返回的數據類型
context.Response.ContentType = "application/json";
//自定義返回狀態碼,默認為401 我這里改成 200
context.Response.StatusCode = StatusCodes.Status200OK;
//context.Response.StatusCode = StatusCodes.Status401Unauthorized;
//輸出Json數據結果
context.Response.WriteAsync(payload);
return Task.FromResult(0);
}
};
/// <summary>
/// 獲取Token
/// </summary>
/// <param name="req">請求流</param>
/// <returns></returns>
public static string GetToken(HttpRequest req)
{
string tokenHeader = req.Headers["Authorization"].ToString();
if (string.IsNullOrEmpty(tokenHeader))
throw new Exception("缺少token!");
string pattern = "^Bearer (.*?)$";
if (!Regex.IsMatch(tokenHeader, pattern))
throw new Exception("token格式不對!格式為:Bearer {token}");
string token = Regex.Match(tokenHeader, pattern).Groups[1]?.ToString();
if (string.IsNullOrEmpty(token))
throw new Exception("token不能為空!");
return token;
}
好了 基本沒問題 最后在如果請求401可以自定義


整的代碼如下:
[AllowAnonymous]
[HttpGet]
[Route("api/auth")]
public IActionResult Get(string userName, string pwd)
{
if (CheckAccount(userName, pwd, out string role))
{
//每次登陸動態刷新
Const.ValidAudience = userName + pwd + DateTime.Now.ToString();
// push the user’s name into a claim, so we can identify the user later on.
//這里可以隨意加入自定義的參數,key可以自己隨便起
var claims = new[]
{
new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}") ,
new Claim (JwtRegisteredClaimNames.Exp,$"{new DateTimeOffset(DateTime.Now.AddMinutes(30)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.NameIdentifier, userName),
new Claim("Role", role)
};
//sign the token using a secret key.This secret will be shared between your API and anything that needs to check that the token is legit.
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
//.NET Core’s JwtSecurityToken class takes on the heavy lifting and actually creates the token.
var token = new JwtSecurityToken(
//頒發者
issuer: Const.Domain,
//接收者
audience: Const.ValidAudience,
//過期時間
expires: DateTime.Now.AddMinutes(30),
//簽名證書
signingCredentials: creds,
//自定義參數
claims: claims
);
return Ok(new
{
token = new JwtSecurityTokenHandler().WriteToken(token)
});
}
else
{
return BadRequest(new { message = "username or password is incorrect." });
}
}
3.3 然后改造一下StartUp.cs
我們僅僅需要關心改動的地方,也就是AddJwtBearer這個驗證token的方法,我們不用原先的固定值的校驗方式,而提供一個代理方法進行運行時執行校驗
.AddJwtBearer(options =>
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateLifetime = true,//是否驗證失效時間
ClockSkew = TimeSpan.FromSeconds(30),
ValidateAudience = true,//是否驗證Audience
//ValidAudience = Const.GetValidudience(),//Audience
//這里采用動態驗證的方式,在重新登陸時,刷新token,舊token就強制失效了
AudienceValidator = (m, n, z) =>
{
return m != null && m.FirstOrDefault().Equals(Const.ValidAudience);
},
ValidateIssuer = true,//是否驗證Issuer
ValidIssuer = Const.Domain,//Issuer,這兩項和前面簽發jwt的設置一致
ValidateIssuerSigningKey = true,//是否驗證SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Const.SecurityKey))//拿到SecurityKey
};
這里邏輯是這樣的:因為重新登陸將原來的變量更改了,所以這里校驗的時候也一並修改成了新的變量值,那么舊的token當然就不匹配了,也就是舊的token被強制失效了。




參考地址https://www.cnblogs.com/7tiny/p/11012035.html https://www.cnblogs.com/cokeking/p/10969579.html

