在普通的MVC項目中 咱們廣泛的使用Cookie來做爲認證受權方式,使用簡單。登陸成功後將用戶信息寫入Cookie;但當咱們作WebApi的時候顯然Cookie這種方式就有點不適用了。ios
在dotnet core 中 WebApi中目前比較流行的認證受權方式是Jwt (Json Web Token) 技術。Jwt 是一種無狀態的分佈式身份驗證方式,Jwt 是將用戶登陸信息加密後存放到返回的Token中 ,至關於用戶信息是存儲在客戶端。Jwt的加密方式有兩種 :對稱加密與非對稱加密,非對稱加密即 RSA 加密的方式。
git
本身手寫認證受權代碼和Jwt的思路是同樣的;不一樣之處在於:github
一、加密方式僅僅是採用的對稱加密方式 簡單高效。哈哈!(弊端就是沒有非對稱加密更安全);redis
二、用戶登陸信息主要保存在Redis中,即服務端。安全
本身寫的好處:mvc
一、擴展性強,可根據本身的須要進行各類擴展,好比在驗證受權時可很方便的添加多設備登陸擠下線功能等。dom
二、可隨時調整用戶的Token失效時間。分佈式
認證及受權流程post
一、先請求登陸接口,登陸成功,爲用戶產生一個Token,ui
登陸獲取Token 圖片中ticket字段。
二、 客戶端拿到Token在其餘請求中將Token信息添加到請求頭中傳遞到服務端。
開發思路
一、添加一個過濾器。在Startup 中ConfigureServices方法裏添加一個Filters 即咱們本身受權代碼類。
public
void
ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddMvc(mvc =>
{
//添加本身的受權驗證
mvc.Filters.Add(
typeof
(AuthorizeFilter));
});
}
|
添加過濾器以後咱們的每次請求都會優先執行過濾器的代碼。在這裏咱們就能夠判斷用戶是否已經登陸,從而進行攔截沒有受權的的請求。
/// <summary> /// 安全認證過濾器 /// </summary> public class AuthorizeFilter : IActionFilter, IAuthorizationFilter { public void OnAuthorization(AuthorizationFilterContext context) { //容許匿名訪問 if (context.HttpContext.User.Identity.IsAuthenticated || context.Filters.Any(item => item is IAllowAnonymousFilter)) return; var httpContext = context.HttpContext; var claimsIdentity = httpContext.User.Identity as ClaimsIdentity; var request = context.HttpContext.Request; var authorization = request.Headers["Authorization"].ToString(); if (authorization != null && authorization.Contains("BasicAuth")) { //獲取請求頭中傳遞的ticket var current_ticket = authorization.Split(" ")[1]; //校驗ticket並獲取用戶信息 var userInfo = TicketEncryption.VerifyTicket(current_ticket, out string dec_client); if (userInfo != null) { //同一個終端屢次登陸擠下線功能 返回403 if (userInfo.ticket != current_ticket && userInfo.client.ToString() == dec_client) { #region 多設備擠下線代碼 var response = new HttpResponseMessage(); context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Forbidden; context.Result = new JsonResult("Forbidden:The current authorization has expired"); #endregion return; } else { return; } } } // 401 未受權 context.HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; context.Result = new JsonResult("Forbidden:Tiket Invalid"); } }
二、登陸並獲取Token
因爲添加了IAuthorizationFilter類型的過濾器,因此每一個請求都會被攔截。因此登陸接口咱們須要容許匿名訪問。
三、加解密Token
加密:登陸成功後就要產生個Token了,產生也簡單。將用戶的惟一信息好比uid或者guid進行對稱式加密。固然若是須要對登陸設備作區分或者多設備登陸擠下線功能時最好也將登陸設備一塊兒加密進去。
咱們都知道 在加密中通常狀況下只要加密的數據及加密key不變;那麼加密後的內容也會一直保持不變。若是咱們每次登陸產生的Token一直沒有任何變化只要這個Token被泄露了那將很危險的。居然咱們但願每次登陸產生的Token都有變化。那就要改變加密數據或者加密key了。加密數據是用戶惟一信息這個顯然不可能產生變化。因此咱們能改變的地方只能是加密key了;咱們採用固定key+隨機key的方式。
由於加密key在咱們解密時也是須要一一對應的。因此咱們得想辦法將咱們的隨機key告訴咱們解密的代碼中。辦法就是 咱們將加密後的內容(通常狀況進行base64編碼)再加上隨機key。(隨機key必定是固定長度 否則後面沒法解析拆分)
好比加密內容是guid=73e01eab-210d-4d19-a72a-d0d64e053ec0+client=ios 固定key=123654+隨機key=FEZaaWbyimaWiJHah
即加密過程:
加密(73e01eab-210d-4d19-a72a-d0d64e053ec0&ios,123654FEZaaWbyimaWiJHah)=M0EzM0ZGRjk2QzgwRDY2RDJDMTdFOEJGRUE0NDI3NEE1RDlFNkU4NDQ0MERFNEIyMkQ5QjM4MjAxODcwj加隨機keyFEZaaWbyimaWiJHah
因此咱們返回給用戶的Token其實是包含了隨機key的。固然這個隨機key只有咱們本身知道。由於隨機key的長度以及位置只有咱們本身知道。這種方式即便咱們固定key被泄露了 只要別人不知道咱們隨機key處理方法也無濟於事。
解密:知道加密過程後就好解密了。拿到用戶提交的Token後首先按照隨機key的固定位置進行截取。將加密內容和隨機key拆開。而後將固定key和隨機key組合一塊兒解密加密的內容,取得用戶guid和登陸的客戶端類型。
完整加解密代碼
代碼中的ticket表明本文中的Token。代碼中使用的是DES加解密
public class TicketEncryption { //加密key 實際中請用配置文件配置 private static readonly string key = "yvDlky7GXGtlPCGr"; /// <summary> /// 獲取一個新的ticket /// </summary> /// <param name="guid">用戶的guid</param> /// <param name="client">客戶端</param> /// <returns></returns> public static string GenerateTicket(string guid, string client) { //隨機key string randomKey = Randoms.GetRandomString(15); var keys = key + randomKey; var desStr = Encryption.DesEncrypt(guid + "&" + client, keys); var base64Str = Encryption.Base64Encrypt(desStr) + randomKey; return base64Str; } /// <summary> /// 校驗ticket /// </summary> /// <param name="encryptStr"></param> /// <returns></returns> public static UserInfo VerifyTicket(string encryptStr,out string client) { try { RedisHelper redisHelper = new RedisHelper("127.0.0.1:6379"); //加密原型:guid&client; 如:08e80f78-95ad-427c-b506-a5f1504e29ac&ios string randomKey = encryptStr.Substring(encryptStr.Length - 15); var base64 = encryptStr.Substring(0, encryptStr.Length - 15); var deBase64 = Encryption.Base64Decrypt(base64); var keys = key + randomKey; string ticketInfo = Encryption.DesDecrypt(deBase64, keys); var guid = ticketInfo.Split("&")[0]; client = ticketInfo.Split("&")[1]; string redisKey = "ticket_" + guid; var obj = redisHelper.Get<UserInfo>(redisKey); return obj; } catch (Exception ex) { throw ex; } } }
完整demo代碼請看github