.NET 和 .NET Core 使用 JWT 授權驗證
阿新 • • 發佈:2021-12-22
JWT介紹
參考文章 https://www.cnblogs.com/cjsblog/p/9277677.html
.NET 中使用
1. NuGet包
搜尋JWT,下載安裝(本人用的是8.2.3版本)
2. 自定義幫助類
2.1 新建 IHttpResponseResult
interface介面
public interface IHttpResponseResult { } /// <summary> /// 響應資料輸出泛型介面 /// </summary> /// <typeparam name="T"></typeparam> // ReSharper disable once UnusedTypeParameter public interface IHttpResponseResult<T> : IHttpResponseResult { }
2.2 新建 HttpResponseResult
資料響應類
public class HttpResponseResult<T> : IHttpResponseResult<T> { /// <summary> /// 狀態碼 /// </summary> public int Code { get; set; } /// <summary> /// 訊息 /// </summary> public string Message { get; set; } /// <summary> /// 資料 /// </summary> public T Data { get; set; } /// <summary> /// 成功 /// </summary> /// <param name="data">資料</param> /// <param name="msg">訊息</param> public HttpResponseResult<T> Success(T data = default, string msg = null) { Code = 0; Data = data; Message = msg; return this; } /// <summary> /// 失敗 /// </summary> /// <param name="code">狀態碼</param> /// <param name="msg">訊息</param> /// <param name="data">資料</param> /// <returns></returns> public HttpResponseResult<T> Fail(T data = default, int code = -1, string msg = null) { Code = code; Message = msg; Data = data; return this; } } /// <summary> /// 響應資料靜態輸出 /// </summary> public static class HttpResponseResult { /// <summary> /// 成功 /// </summary> /// <param name="data">資料</param> /// <param name="msg">訊息</param> /// <returns></returns> public static IHttpResponseResult Success<T>(T data, string msg = "message") { return new HttpResponseResult<T>().Success(data, msg); } /// <summary> /// 失敗 /// </summary> /// <param name="data">資料</param> /// <param name="msg">訊息</param> /// <param name="code">狀態碼</param> /// <returns></returns> public static IHttpResponseResult Fail<T>(T data, string msg = null, int code = -1) { return new HttpResponseResult<T>().Fail(data, code, msg); } }
3.生成JWT Token
3.1 新建Conteoller
新建 LoginController
並且新建一個生成 Token 的方法 Login
/// <summary> /// JWT授權 /// </summary> [RoutePrefix("api/login")] public class LoginController : ApiController { private readonly string secretKey = "JwtSecretKey";//Jwt金鑰,自定義 private readonly int tokenExpire = 2;//Token過期時間 /// <summary> /// 授權登入獲取Token /// </summary> /// <param name="loginModel"></param> /// <returns></returns> [HttpPost, Route("GetToken")] public IHttpResponseResult Login([FromBody] Login loginModel) { dynamic obj = new ExpandoObject(); try { var userName = loginModel.UserName; var userPwd = loginModel.UserPwd; //var loginRes = ChatUserDao.GetJwtUser(userName, userPwd).IsNull(); //自己的資料庫驗證 var loginRes = "Dennis".Equals(userName) && "123".Equals(userPwd); if (loginRes) { return HttpResponseResult.Fail(obj, "The user is not found or password is error."); } var expireTime = DateTime.Now.AddHours(tokenExpire); //身份驗證資訊 var jwtToken = new JwtToken { AuthUserName = userName, ExpireTime = expireTime }; var key = Encoding.UTF8.GetBytes(secretKey); IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //加密方式 IJsonSerializer serializer = new JsonNetSerializer(); //序列化Json IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder(); //base64加解密 IJwtEncoder encoder = new JwtEncoder(algorithm, serializer, urlEncoder); //JWT編碼 var token = encoder.Encode(jwtToken, key); //生成令牌 obj.Token = token; obj.ExpireTime = expireTime; return HttpResponseResult.Success(obj, "Get Token Success."); } catch (Exception e) { LogFileHelper.WriteLog("GetToken", e.ToExceptionString()); return HttpResponseResult.Fail(obj, e.ToExceptionString()); } } }
3.2 新建JwtToken 模型類
/// <summary>
/// Jwt Token
/// </summary>
public class JwtToken
{
/// <summary>
/// 授權者
/// </summary>
public string AuthUserName { get; set; }
/// <summary>
/// Token過期時間
/// </summary>
public DateTime ExpireTime { get; set; }
}
4. 新建Attribute類驗證Token
在App_Start
專案檔案加中新建一個ApiAuthAttribute
類,繼承自AuthorizeAttribute
/// <summary>
/// Jwt 授權驗證
/// </summary>
public class ApiAuthAttribute : AuthorizeAttribute
{
private readonly string secretKey = "JwtSecretKey";//加密祕鑰,與生成Token時的金鑰相同
private const string authHeader = "Authorization";//請求頭Header中存放JwtToken的Key名稱
/// <summary>
/// 判斷授權
/// </summary>
/// <param name="httpContext"></param>
/// <returns></returns>
protected override bool IsAuthorized(HttpActionContext httpContext)
{
try
{
var httpHeader = httpContext.Request.Headers;
var token = string.Empty;//獲取token
foreach (var keyHeader in httpHeader)
{
if (authHeader.Equals(keyHeader.Key))
{
token = keyHeader.Value.FirstOrDefault();
}
}
if (token.IsEmpty())
{
return false;
}
var key = Encoding.UTF8.GetBytes(secretKey);
IJwtAlgorithm algorithm = new HMACSHA256Algorithm(); //加密方式
IJsonSerializer serializer = new JsonNetSerializer();//序列化Json
IDateTimeProvider provider = new UtcDateTimeProvider();
IJwtValidator validator = new JwtValidator(serializer, provider);
IBase64UrlEncoder urlEncoder = new JwtBase64UrlEncoder();//base64加解密
IJwtDecoder decoder = new JwtDecoder(serializer, validator, urlEncoder, algorithm);
//解密
var json = decoder.DecodeToObject<JwtToken>(token, key, true);
if (json.IsNull()) return false;
return json.ExpireTime >= DateTime.Now;
}
catch (Exception e)
{
LogFileHelper.WriteLog(authHeader, e.ToExceptionString());
return false;
}
}
/// <summary>
/// 授權失敗時呼叫
/// </summary>
/// <param name="httpContext"></param>
protected override void HandleUnauthorizedRequest(HttpActionContext httpContext)
{
//Token過期時,響應頭新增過期標識
httpContext.Response = httpContext.ControllerContext.Request.CreateResponse(
HttpStatusCode.Unauthorized, HttpResponseResult.Fail(false, "Token is not found or expired, authorization failed."));
httpContext.Response.Headers.Add("Token-Expired", "true");
}
}
5. 測試Token驗證
/// <summary>
/// 測試介面
/// </summary>
[RoutePrefix("api/test")]
public class TestController : ApiController
{
/// <summary>
/// Token認證測試
/// </summary>
/// <returns></returns>
[HttpGet, Route("TokenAuth")]
[ApiAuth]
public IHttpResponseResult TokenAuth()
{
return HttpResponseResult.Success(true, "Auth Passed");
}
}
.NET Core 中使用
1. NuGet包
搜尋JwtBearer,下載安裝(本人安裝的是5.0.7)
2. 配置Startup
註冊JWT中介軟體,我是單獨寫了一個類然後引用,你也可以直接寫在Startup的ConfigureServices
方法中
/// <summary>
/// JWT授權中介軟體
/// </summary>
public static class AuthorizationMiddleware
{
/// <summary>
/// 註冊授權服務
/// </summary>
/// <param name="services"></param>
public static void AddAuthorizationService(this IServiceCollection services)
{
// 開啟Bearer認證
services.AddAuthentication(options =>
{
// 設定預設使用jwt驗證方式
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
// 新增JwtBearer服務
.AddJwtBearer(o =>
{
// 令牌驗證引數
o.TokenValidationParameters = new TokenValidationParameters
{
// 設定生成token的祕鑰
//IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(AppConfig.SecretKey)),
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppConfig.SecretKey)),
// 驗證祕鑰
ValidateIssuerSigningKey = true,
// 驗證釋出者
ValidateIssuer = true,
// 驗證Issure
ValidIssuer = AppConfig.Issuer,//發行人
// 驗證接收者
ValidateAudience = true,
// 讀配置Audience
ValidAudience = AppConfig.Audience,//訂閱人
// 驗證過期時間
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(30),
RequireExpirationTime = true
};
o.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
// 如果過期,則把<是否過期>新增到,返回頭資訊中
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
//如果沒有角色控制到Action則註釋下列程式碼
services.AddAuthorization(options =>
{
options.AddPolicy("User", policy => policy.RequireRole("User").Build());
options.AddPolicy("SystemOrAdmin", policy => policy.RequireRole("Admin", "System"));
});
}
}
然後在Startup的ConfigureServices
方法中新增引用
services.AddAuthorizationService();
在Startup的Configure
方法中使用授權
在app.UseRouting()
之後和app.UseEndpoints
之前新增程式碼
app.UseAuthentication();//身份驗證
app.UseAuthorization();//身份授權
3. JWT自定義幫助類
3.1 JWTHelper
/// <summary>
/// JWT 幫助類
/// </summary>
public static class JWTHelper
{
/// <summary>
/// 頒發JWT字串
/// </summary>
/// <param name="tokenModel"></param>
/// <returns></returns>
public static string IssueJwt(UserModel tokenModel)
{
//獲取Appsetting配置
var iss = AppConfig.Issuer;
var aud = AppConfig.Audience;
var secret = AppConfig.SecretKey;
var expire = AppConfig.Expire;
//var claims = new Claim[] //old
var claims = new List<Claim>
{
/*
* 特別重要:
1、這裡將使用者的部分資訊,比如 uid 存到了Claim 中,如果你想知道如何在其他地方將這個 uid從 Token 中取出來,請看下邊的SerializeJwt() 方法,或者在整個解決方案,搜尋這個方法,看哪裡使用了!
2、你也可以研究下 HttpContext.User.Claims ,具體的你可以看看 Policys/PermissionHandler.cs 類中是如何使用的。
*/
new Claim(JwtRegisteredClaimNames.Jti, tokenModel.UserId),
new Claim(JwtRegisteredClaimNames.Iat, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
new Claim(JwtRegisteredClaimNames.Nbf, $"{new DateTimeOffset(DateTime.Now).ToUnixTimeSeconds()}"),
//這個就是過期時間,目前是過期1000秒,可自定義,注意JWT有自己的緩衝過期時間
new Claim(JwtRegisteredClaimNames.Exp,
$"{new DateTimeOffset(DateTime.Now.AddSeconds(1000)).ToUnixTimeSeconds()}"),
new Claim(ClaimTypes.Expiration, DateTime.Now.AddMinutes(expire).ToFormatString()),
new Claim(JwtRegisteredClaimNames.Iss, iss),
new Claim(JwtRegisteredClaimNames.Aud, aud)
};
// 可以將一個使用者的多個角色全部賦予,比如引數System,Admin,那麼該token即擁有兩個角色;
claims.AddRange(tokenModel.UserName.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
//祕鑰 (SymmetricSecurityKey 對安全性的要求,金鑰的長度太短會報出異常)
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(secret));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var jwt = new JwtSecurityToken(iss, claims: claims, signingCredentials: credentials);
var jwtHandler = new JwtSecurityTokenHandler();
var encodedJwt = jwtHandler.WriteToken(jwt);
return encodedJwt;
}
/// <summary>
/// 解析
/// </summary>
/// <param name="jwtStr"></param>
/// <returns></returns>
public static UserModel SerializeJwt(string jwtStr)
{
var jwtHandler = new JwtSecurityTokenHandler();
var jwtToken = jwtHandler.ReadJwtToken(jwtStr);
object role;
try
{
jwtToken.Payload.TryGetValue(ClaimTypes.Role, out role);
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
var tm = new UserModel
{
UserId = jwtToken.Id,
UserRole = role != null ? role.ToString() : "",
};
return tm;
}
}
金鑰等相關配置動態配置在appsettings.json
檔案中
"JwtSetting": {
"Issuer": "DennisDong",
"Audience": "https://www.dennisdong.top",
"SecretKey": "D@1#n$n%i&s.D*0n!g",
"Expire": "2"
}
3.2 UserModel
/// <summary>
/// 使用者Model
/// </summary>
public class UserModel
{
/// <summary>
/// UserId
/// </summary>
public string UserId { get; set; }
/// <summary>
/// 角色
/// </summary>
public string UserRole { get; set; }
/// <summary>
/// 姓名
/// </summary>
public string UserName { get; set; }
/// <summary>
/// 生日
/// </summary>
public DateTime UserBirthDay { get; set; }
}
4. 生成和測試Token
/// <summary>
/// 登入
/// </summary>
[Route("api/login/[action]")]
[ApiController]
public class LoginController : ControllerBase
{
/// <summary>
/// 登入獲取Token
/// </summary>
/// <param name="role"></param>
/// <returns></returns>
[HttpGet]
public IActionResult Login(string role)
{
string token;
var result = false;
if (role.NotNull())
{
//這裡只是測試,具體資料驗證根據自己需要來即可
token = JWTHelper.IssueJwt(new UserModel
{
UserId = Guid.NewGuid().ToString(),
UserRole = role,
UserName = role,
UserBirthDay = DateTime.Now
});
result = true;
}
else
{
token = " Login Fail.";
}
return Ok(new { Status = result, Token = token });
}
/// <summary>
/// 解析Token
/// </summary>
/// <returns></returns>
[HttpGet]
//該介面限制只有System 或 Admin 角色(中介軟體中AddAuthorization方法的配置決定)的Token可以訪問
[Authorize("SystemOrAdmin")]
//如果中介軟體中沒有配置AddPolicy,直接使用Authorize即可
//[Authorize]
public IActionResult ParseToken()
{
//需要擷取Bearer
var tokenHeader = HttpContext.Request.Headers["Authorization"].ToString().Replace("Bearer ", "");
var user = JWTHelper.SerializeJwt(tokenHeader);
return Ok(user);
}
}
Swagger配合JWT使用
請參考文章 https://www.cnblogs.com/dennisdong/p/15719616.html