JWT學習文章:
第一篇:JWT原理
第二篇:JWT原理實作代碼
第三篇:asp.net core中使用JWT
上一篇學習了JWT的基本理論,這一篇将根據原理進行代碼實作。
要想實作jwt的加密解密,要先生成一個SecurityKey,大家可以在網上工具生成一個随機的密鑰。我是在這裡生成的。
下面篇幅大量都是代碼,因為注釋寫得很清楚,是以就不再有過多文字說明。
代碼實作
建立常量類:Const
public class Const
{
public const string SecurityKey= "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDSfLGu+kcFDcJUCV46J+SbgR0lNc2NqgCGzojQTWW9xqjuzPF3mpisvTggYZSGfBzN+88YLZYbBLrDTUMJ4nTieElbP6SHkBFu8F+7fFBi7w3UPsaAXDr2E2srQYU5ZlKAcFBoNajNWj3sfSVRoYRPdqDTj4WdJlUPSNGz0wgRrQIDAQAB";
public const string Domain = "http://localhost:5000";
}
建立控制器:AuthController
[ApiController]
[Route("[controller]")]
public class AuthController : ControllerBase
{
[HttpGet]
public IActionResult Get(string userName, string pwd)
{
//此處隻簡單的驗證使用者名和密碼的不為空,實際中使用時不要這樣
if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(pwd))
{
//Header
var header = "{\"alg\": \"HS256\",\"typ\": \"JWT\"}";
var headerBase = Base64UrlTextEncoder.Encode(Encoding.UTF8.GetBytes(header));
//Payload
var payloadDic = new Dictionary<string, object>();
payloadDic["iss"]= Const.Domain;
//添加jwt可用時間
var now = DateTime.UtcNow;
payloadDic["nbf"] = now.ToUniversalTime();//可用時間起始
payloadDic["exp"] = now.AddMinutes(30).ToUniversalTime();//可用時間結束
var payload = JsonConvert.SerializeObject(payloadDic);
var payloadBase = Base64UrlTextEncoder.Encode(Encoding.UTF8.GetBytes(payload));
//Signature
//聲明hs256對象
var hs256 = new HMACSHA256(Encoding.UTF8.GetBytes(Const.SecurityKey));
//生成signature
var signature = hs256.ComputeHash(Encoding.UTF8.GetBytes(headerBase + "." + payloadBase));
var signatureBase = Base64UrlTextEncoder.Encode(signature);
return Ok(new
{
token = headerBase + "." + payloadBase + "." + signatureBase
}) ;
}
else
{
return BadRequest(new { message = "username or password is incorrect." });
}
}
}
為了過濾哪些接口需要驗證,此處建立一個特性:AuthAttribute
public class AuthAttribute : Attribute
{
public AuthAttribute()
{
}
}
修改原有的Home控制器:
[ApiController]
[Route("[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
[Route("api/value1")]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value1" };
}
[HttpGet]
[Route("api/value2")]
[Auth]
public ActionResult<IEnumerable<string>> Get2()
{
return new string[] { "value2", "value2" };
}
}
Value2接口标記了Auth特性,在下面驗證時有Auth特性标記的接口才會被要求token。
建立靜态類:AuthExtension,并且增加一個IApplicationBuilder的擴充方法:
public static class AuthExtension
{
public static void AddAuthorize(this IApplicationBuilder applicationBuilder)
{
applicationBuilder.Use(async (currentContext, nextContext) =>
{
//擷取是否具有自定義的auth特性
var authAttribute = currentContext.GetEndpoint()?.Metadata.GetMetadata<AuthAttribute>();
if (authAttribute != null)
{
if (currentContext.Request.Headers.ContainsKey("Authorization"))
{
var authorize = currentContext.Request.Headers["Authorization"].ToString();
if (authorize.Contains("Bearer"))
{
var info = authorize.Replace("Bearer ", string.Empty);
var jwtStr = info.Split('.').ToArray();
//聲明hs256對象
var hs256 = new HMACSHA256(Encoding.UTF8.GetBytes(Const.SecurityKey));
//生成signature
var signature = Base64UrlTextEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(jwtStr[0] + "." + jwtStr[1])));
//驗證加密後是否相等
if (jwtStr[2] == signature)
{
//驗證是否在有效時間内
var now = DateTime.UtcNow.ToUniversalTime();
var payload = JsonConvert.DeserializeObject<Dictionary<string, object>>(Encoding.UTF8.GetString(Base64UrlTextEncoder.Decode(jwtStr[1])));
if (now >= Convert.ToDateTime(payload["nbf"]) && now <= Convert.ToDateTime(payload["exp"]))
{
//await currentContext.Response.WriteAsync("驗證通過").ConfigureAwait(true);
await nextContext?.Invoke();
return;
}
currentContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await currentContext.Response.WriteAsync("Authorization time has passed, please log in again!").ConfigureAwait(true);
}
}
}
currentContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
await currentContext.Response.WriteAsync("Verification failed, no permission to access!").ConfigureAwait(true);
}
await nextContext?.Invoke();
});
}
}
在啟動類Startup的請求管道中(Configure)添加上面的擴充方法:
//添加身份驗證
app.AddAuthorize();
注意一定要把這句話添加在UseRouting()之後,因為在擴充方法中擷取Auth特性隻有在注冊了Routing規則後才能擷取到值。
測試
通路無需權限的Value1接口:

通路成功!!!
擷取token:
我啟用了swagger,如果沒有啟用在postman中請求https://localhost:5001/Auth?userName=admin&pwd=admin也是一樣的。
代碼中使用者名和密碼我隻是簡單的驗證了下是否為空,是以這裡填寫什麼都能通過。
擷取token成功說明擷取token的代碼沒有問題,邏輯有沒有問題還不能确定,是以要經過後面接口的确認看是否成功。
通路要求權限驗證的Value2接口:
不帶token:
通路失敗!!!
帶上token:
成功!!!
靜待三十分鐘(代碼中設定token過期時間為三十分鐘),調用Value2接口:
失敗了!!!錯誤提示是token過期。
如果覺得不保險,還可以逐漸調試看一下是否所有邏輯都正确執行,這裡就不再進行贅述了。
至此證明我們依照jwt原理寫的權限驗證成功!!!
源碼位址:https://gitee.com/jingboweilanGO/Demo_jwt_core.git
說明:Demo-jwt-core2是本篇文章涉及到的源碼,是使用asp.net core 自帶的jwt方法;
Demo-jwt-core是第三篇jwt文章的源碼,根據jwt原理實作的代碼。