轉自:https://yq.aliyun.com/articles/335021/
首先說下什么是 JWT -- JSON WEB TOKEN,網上關於它的介紹已經很多很多啦,在此,推薦給大家一篇寫的比較好的文章:什么是 JWT -- JSON WEB TOKEN
以及Token的組成部分:Token存放的信息
OK,今天我想介紹的不再是理論,而是如何在C#中應用,說白了就是怎么寫程序唄。
借用 什么是 JWT -- JSON WEB TOKEN 文章中一句話:
基於token的鑒權機制類似於http協議也是無狀態的,它不需要在服務端去保留用戶的認證信息或者會話信息。這就意味着基於token認證機制的應用不需要去考慮用戶在哪一台服務器登錄了,這就為應用的擴展提供了便利。
流程上是這樣的:
- 用戶使用用戶名密碼來請求服務器
- 服務器進行驗證用戶的信息
- 服務器通過驗證發送給用戶一個token
- 客戶端存儲token,並在每次請求時附送上這個token值
- 服務端驗證token值,並返回數據
這個token必須要在每次請求時傳遞給服務端,它應該保存在請求頭里。
OK,按照上述的流程,首先我們應當拿到登錄的賬戶,密碼等信息,驗證通過后,生成TOKEN並發送給客戶端,之后客戶端的每個請求只需帶上這個TOKEN,服務器端對這個TOKEN驗證,驗證通過后即可訪問服務器資源,。
具體在C#中如何模仿這個流程呢?
- 用戶使用用戶名密碼來請求服務器
- 服務器進行驗證用戶的信息
上述二個步驟其實是個登錄過程,在此不作說明!
- 服務器通過驗證發送給用戶一個token
發送給客戶端一個Token,這個就需要我們生成Token了,那么怎樣生成呢?理論模塊可參考:Token的組成部分:Token存放的信息
1、用C#生成Token:
首先引入JWT.dll
Token生成的具體代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; /*---------------------------------------------------------------- Copyright (C) 2017 陳卧龍 文件名:TestForToken.CommonCS 文件功能描述:Token相關操作 ----------------------------------------------------------------*/ namespace TestForToken.CommonCS { public class CommonToken { public static string SecretKey = "This is a private key for Server";//這個服務端加密秘鑰 屬於私鑰 public static string GenToken(TokenInfo M) { var jwtcreated = Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 5); var jwtcreatedOver = Math.Round((DateTime.UtcNow.AddHours(2) - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 5); var payload = new Dictionary<string, dynamic> { {"iss", M.iss},//非必須。issuer 請求實體,可以是發起請求的用戶的信息,也可是jwt的簽發者。 {"iat", jwtcreated},//非必須。issued at。 token創建時間,unix時間戳格式 {"exp", jwtcreatedOver},//非必須。expire 指定token的生命周期。unix時間戳格式 {"aud", M.aud},//非必須。接收該JWT的一方。 {"sub", M.sub},//非必須。該JWT所面向的用戶 {"jti", M.jti},//非必須。JWT ID。針對當前token的唯一標識 {"UserName", M.UserName},//自定義字段 用於存放當前登錄人賬戶信息 {"UserPwd", M.UserPwd},//自定義字段 用於存放當前登錄人登錄密碼信息 {"UserRole", M.UserRole},//自定義字段 用於存放當前登錄人登錄權限信息 }; return JWT.JsonWebToken.Encode(payload, SecretKey, JWT.JwtHashAlgorithm.HS256); } } public class TokenInfo { public TokenInfo() { iss = "簽發者信息"; aud = "http://example.com"; sub = "HomeCare.VIP"; jti = DateTime.Now.ToString("yyyyMMddhhmmss"); UserName = "jack.chen"; UserPwd = "jack123456"; UserRole = "HomeCare.Administrator"; } // public string iss { get; set; } public string aud { get; set; } public string sub { get; set; } public string jti { get; set; } public string UserName { get; set; } public string UserPwd { get; set; } public string UserRole { get; set; } } }
2、將生成的Token發送給客戶端后,隨后,客戶端的每次請求只需帶上這個Token即可
一般都是將Token存放在Http請求的Headers中,也就是:context.Request.Headers,那么如何接收請求頭中的Token呢?接收到Token后如何驗證呢?
驗證TOKEN時就需要構建 MVC Action 過濾器(AuthorizeAttribute)了,不過在構建 AuthorizeAttribute 之前,有必要對 AuthorizeAttribute 說明下,如下:
首先,AuthorizeAttribute 類位於System.Web.Http 命名空間下及System.Web.Mvc命名空間下,
一般情況下,如果你需要對C# MVC 控制器的訪問作認證與授權,你需要用System.Web.Mvc命名空間下的 AuthorizeAttribute ,如果你需要對C# API 控制器的訪問作認證與授權,你需要用System.Web.Http 命名空間下的 AuthorizeAttribute !
OK,知道了上述兩種不同命名空間下的 AuthorizeAttribute ,下面以范例作為說明:
2.1、自定義MVC ACTION 登錄授權驗證,(由於本篇博客主講 Token 的驗證與實現,因此,關於MVC 登錄驗證只做代碼說明:)
2.1.1、新建一個MVC控制器,命名為BaseController,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using System.Web.Security; namespace TestForToken.Controllers { public class BaseController : Controller { #region 退出登錄 /// <summary> /// 退出登錄 /// </summary> public void ClearLogin() { FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket( 1, "", DateTime.Now, DateTime.Now.AddMinutes(-30), false, "", "/" ); //.ASPXAUTH string encryptedTicket = FormsAuthentication.Encrypt(authTicket); System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket); System.Web.HttpContext.Current.Response.Cookies.Add(authCookie); } #endregion #region 自定義過濾器 /// <summary> /// 自定義過濾器 /// </summary> /// <param name="filterContext"></param> protected override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext) { string cookieName = FormsAuthentication.FormsCookieName; HttpCookie authCookie = System.Web.HttpContext.Current.Request.Cookies[cookieName]; FormsAuthenticationTicket authTicket = null; try { authTicket = FormsAuthentication.Decrypt(authCookie.Value); } catch (Exception ex) { return; } if (authTicket != null && filterContext.HttpContext.User.Identity.IsAuthenticated) { string UserName = authTicket.Name; base.OnActionExecuting(filterContext); } else { Content("<script >top.location.href='/Home/Login';</script >", "text/html"); //filterContext.HttpContext.Response.Redirect("/Home/Logins"); } } #endregion #region 讀取錯誤信息 /// <summary> /// 讀取錯誤信息 /// </summary> /// <returns></returns> public string GetError() { var errors = ModelState.Values; foreach (var item in errors) { foreach (var item2 in item.Errors) { if (!string.IsNullOrEmpty(item2.ErrorMessage)) { return item2.ErrorMessage; } } } return ""; } #endregion } }
2.2.2、新建一個MVC控制器,命名為HomeController,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.Mvc; using TestForToken.Models; namespace TestForToken.Controllers { public class HomeController : BaseController { public ActionResult Login() { ClearLogin(); string HX_userName = CommonMethod.getCookie("HX_userName"); string HX_userPwd = CommonMethod.getCookie("HX_userPwd"); ViewBag.HX_userName = HX_userName; ViewBag.HX_userPwd = HX_userPwd; return View(); } [HttpPost] public object UserLogin(LoginsModel LoginMol) { if (ModelState.IsValid)//是否通過Model驗證 { return LoginMol.LoginAction(); } else { return GetError(); } } } }
2.2.3、新建一個登錄實體類,命名為:LoginsModel,代碼如下:
using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Web; using System.Web.Security; namespace TestForToken.Models { public class LoginsModel { private readonly object LOCK = new object(); [Required(ErrorMessage = "請輸入賬戶號碼/手機號")] [RegularExpression(@"^1[34578][0-9]{9}$", ErrorMessage = "手機號格式不正確")] public string UserName { get; set; } [Required(ErrorMessage = "請輸入賬戶密碼")] [DataType(DataType.Password, ErrorMessage = "密碼格式不正確")] public string UserPwd { get; set; } public bool remember { get; set; } public string LoginAction() { lock (LOCK) { string userRole = string.Empty; //數據庫操作代碼 int UserId = 0; if (UserName == "18137070152" && UserPwd == "18137070152") { UserId = 1; userRole = "HomeCare.Administrator"; } else if (UserName == "18911695087" && UserPwd == "18911695087") { UserId = 2; userRole = "HomeCare.Vip"; } else { UserId = 3; userRole = "HomeCare.User"; } if (UserId != 0) { if (remember) { CommonMethod.setCookie("HX_userName", UserName, 7); CommonMethod.setCookie("HX_userPwd", UserPwd, 7); } FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket( 1, UserName + "_" + UserId, DateTime.Now, DateTime.Now.AddMinutes(30), false, userRole, "/" ); //.ASPXAUTH string encryptedTicket = FormsAuthentication.Encrypt(authTicket); System.Web.HttpCookie authCookie = new System.Web.HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket); System.Web.HttpContext.Current.Response.Cookies.Add(authCookie); return "HomeCare.Administrator"; } else { return "賬戶密碼不存在"; } } } } }
2.2.4、修改你的Global.asax文件,修改代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Web; using System.Web.Http; using System.Web.Mvc; using System.Web.Optimization; using System.Web.Routing; using System.Web.Security; namespace TestForToken { // 注意: 有關啟用 IIS6 或 IIS7 經典模式的說明, // 請訪問 http://go.microsoft.com/?LinkId=9394801 public class MvcApplication : System.Web.HttpApplication { protected void Application_Start() { AreaRegistration.RegisterAllAreas(); WebApiConfig.Register(GlobalConfiguration.Configuration); FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); RouteConfig.RegisterRoutes(RouteTable.Routes); BundleConfig.RegisterBundles(BundleTable.Bundles); } /// <summary> /// 登錄驗證、s授權 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> protected void Application_AuthenticateRequest(Object sender, EventArgs e) { string cookieName = FormsAuthentication.FormsCookieName; HttpCookie authCookie = Context.Request.Cookies[cookieName]; FormsAuthenticationTicket authTicket = null; try { authTicket = FormsAuthentication.Decrypt(authCookie.Value); } catch (Exception ex) { return; } string[] roles = authTicket.UserData.Split(','); FormsIdentity id = new FormsIdentity(authTicket); GenericPrincipal principal = new GenericPrincipal(id, roles); Context.User = principal;//存到HttpContext.User中 } } }
2.2.5、公共訪問類CommonCS部分代碼如下:
using System; using System.Collections.Generic; using System.Web; using System.Collections; using System.Text; using System.Text.RegularExpressions; using System.Data; using System.Drawing; namespace TestForToken { public class CommonMethod { #region cookie操作 /// <summary> /// Cookies賦值 /// </summary> /// <param name="strName">主鍵</param> /// <param name="strValue">鍵值</param> /// <param name="strDay">有效天數</param> /// <returns></returns> public static bool setCookieForMIn(string strName, string strValue, int Mintius) { try { HttpCookie Cookie = new HttpCookie(strName); //Cookie.Domain = ".xxx.com";//當要跨域名訪問的時候,給cookie指定域名即可,格式為.xxx.com Cookie.Expires = DateTime.Now.AddMinutes(Mintius); Cookie.Value = strValue; System.Web.HttpContext.Current.Response.Cookies.Add(Cookie); return true; } catch { return false; } } /// <summary> /// Cookies賦值 /// </summary> /// <param name="strName">主鍵</param> /// <param name="strValue">鍵值</param> /// <param name="strDay">有效天數</param> /// <returns></returns> public static bool setCookie(string strName, string strValue, int strDay) { try { HttpCookie Cookie = new HttpCookie(strName); //Cookie.Domain = ".xxx.com";//當要跨域名訪問的時候,給cookie指定域名即可,格式為.xxx.com Cookie.Expires = DateTime.Now.AddDays(strDay); Cookie.Value = strValue; System.Web.HttpContext.Current.Response.Cookies.Add(Cookie); return true; } catch { return false; } } /// <summary> /// 讀取Cookies /// </summary> /// <param name="strName">主鍵</param> /// <returns></returns> public static string getCookie(string strName) { HttpCookie Cookie = System.Web.HttpContext.Current.Request.Cookies[strName]; if (Cookie != null) { return Cookie.Value.ToString(); } else { return null; } } /// <summary> /// 刪除Cookies /// </summary> /// <param name="strName">主鍵</param> /// <returns></returns> public static bool delCookie(string strName) { try { HttpCookie Cookie = new HttpCookie(strName); //Cookie.Domain = ".xxx.com";//當要跨域名訪問的時候,給cookie指定域名即可,格式為.xxx.com Cookie.Expires = DateTime.Now.AddDays(-1); System.Web.HttpContext.Current.Response.Cookies.Add(Cookie); return true; } catch { return false; } } #endregion } }
2.2.6、公共Token生成類代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; /*---------------------------------------------------------------- Copyright (C) 2017 陳卧龍 文件名:TestForToken.CommonCS 文件功能描述:Token相關操作 ----------------------------------------------------------------*/ namespace TestForToken.CommonCS { public class CommonToken { public static string SecretKey = "This is a private key for Server";//這個服務端加密秘鑰 屬於私鑰 public static string GenToken(TokenInfo M) { var jwtcreated = Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 5); var jwtcreatedOver = Math.Round((DateTime.UtcNow.AddHours(2) - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 5); var payload = new Dictionary<string, dynamic> { {"iss", M.iss},//非必須。issuer 請求實體,可以是發起請求的用戶的信息,也可是jwt的簽發者。 {"iat", jwtcreated},//非必須。issued at。 token創建時間,unix時間戳格式 {"exp", jwtcreatedOver},//非必須。expire 指定token的生命周期。unix時間戳格式 {"aud", M.aud},//非必須。接收該JWT的一方。 {"sub", M.sub},//非必須。該JWT所面向的用戶 {"jti", M.jti},//非必須。JWT ID。針對當前token的唯一標識 {"UserName", M.UserName},//自定義字段 用於存放當前登錄人賬戶信息 {"UserPwd", M.UserPwd},//自定義字段 用於存放當前登錄人登錄密碼信息 {"UserRole", M.UserRole},//自定義字段 用於存放當前登錄人登錄權限信息 }; return JWT.JsonWebToken.Encode(payload, SecretKey, JWT.JwtHashAlgorithm.HS256); } } public class TokenInfo { public TokenInfo() { iss = "簽發者信息"; aud = "http://example.com"; sub = "HomeCare.VIP"; jti = DateTime.Now.ToString("yyyyMMddhhmmss"); UserName = "jack.chen"; UserPwd = "jack123456"; UserRole = "HomeCare.Administrator"; } // public string iss { get; set; } public string aud { get; set; } public string sub { get; set; } public string jti { get; set; } public string UserName { get; set; } public string UserPwd { get; set; } public string UserRole { get; set; } } }
2.2.7、新建一個登錄頁面,Login.cshtml代碼如下:
@{ Layout = null; } <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no"> <title>賬戶登錄</title> <link href="~/Content/css/materialize.min.css" rel="stylesheet" /> <style type="text/css"> html, body { height: 100%; } html { display: table; margin: auto; } body { display: table-cell; vertical-align: middle; color:#47c1a8; } .margin { margin: 0 !important; } .card-panel{ min-width:350px;} </style> <!--[if IE]> <script src="http://apps.bdimg.com/libs/html5shiv/3.7/html5shiv.min.js"></script> <![endif]--> </head> <body class="red"> <div id="login-page" class="row"> <div class="col s12 z-depth-6 card-panel"> <form class="login-form"> <div class="row"> <div class="input-field col s12 center"> <img src="/content/images/100.png" alt="" class="responsive-img valign profile-image-login"> <p class="center login-form-text">賬戶登錄</p> </div> </div> <div class="row margin"> <div class="input-field col s12"> <i class="mdi-social-person-outline prefix"></i> <input class="validate" id="UserName" name="UserName" type="tel" value="@ViewBag.HX_userName"> <label for="tel" data-error="wrong" data-success="right" class="center-align">手機號碼:</label> </div> </div> <div class="row margin"> <div class="input-field col s12"> <i class="mdi-action-lock-outline prefix"></i> <input id="UserPwd" name="UserPwd" type="password" value="@ViewBag.HX_userPwd"> <label for="password">密碼:</label> </div> </div> <div class="row"> <div class="input-field col s12 m12 l12 login-text"> <input type="checkbox" id="remember-me" name="remember-me" /> <label for="remember-me">記住我</label> </div> </div> <div class="row"> <div class="input-field col s12"> <a href="JavaScript:void(0)" class="btn waves-effect waves-light col s12" onclick="Login()">登 錄</a> </div> </div> <div class="row"> <div class="input-field col s6 m6 l6"> <p class="margin medium-small"></p> </div> <div class="input-field col s6 m6 l6"> <p class="margin right-align medium-small"><a href="/home/forgotpassword">忘記密碼?</a></p> </div> </div> </form> </div> </div> <script src="~/Scripts/js/jquery-2.1.0.js"></script> <script src="~/Scripts/js/materialize.min.js"></script> <script type="text/javascript"> function Login() { var UserName = $("#UserName").val(); var UserPwd = $("#UserPwd").val(); var remember = document.getElementById("remember-me").checked; $(document).ready(function (data) { $.ajax({ url: "/Home/UserLogin", type: "post", contentType: "application/json", dataType: "text", data: JSON.stringify({ UserName: UserName, UserPwd: UserPwd, remember: remember }), success: function (result, status) { if (result == "HomeCare.Administrator") { //登錄成功 跳轉 location.href = "/Manger/Index";//管理員登錄 } else { alert(result); } }, error: function (error) { alert(error); } }); }); } </script> <!--materialize js--> </body> </html>
2.2.8、新建一個登錄驗證屬性,繼承自:System.Web.Mvc.AuthorizeAttribute,代碼如下:(千呼萬喚始出來啊......~_~)
using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Web; using System.Web.Http.Controllers; using System.Web.Mvc; using System.Web.Security; namespace TestForToken.Auth2._0 { public class MvcActionAuth : AuthorizeAttribute { public string[] AuthorizeRoleAry = new string[] { "HomeCare.User", "HomeCare.Vip", "HomeCare.Administrator" };//本系統允許的角色 普通用戶 會員 超級管理員 /// <summary> /// 自定義 MVC 控制前訪問權限驗證 /// </summary> /// <param name="filterContext"></param> public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext) { string Role = string.Empty; string controllerName = filterContext.ActionDescriptor.ControllerDescriptor.ControllerName; string actionName = filterContext.ActionDescriptor.ActionName; //數據庫驗證當前Controller及Action允許訪問的權限 //簡單模擬 讀取數據庫 if (controllerName == "Manger" && actionName == "Index") { //得到允許訪問 Manger/Index 的權限值 Role = "HomeCare.Administrator,HomeCare.Vip"; } // string cookieName = FormsAuthentication.FormsCookieName; HttpCookie authCookie = System.Web.HttpContext.Current.Request.Cookies[cookieName]; FormsAuthenticationTicket authTicket = null; try { authTicket = FormsAuthentication.Decrypt(authCookie.Value); } catch (Exception ex) { return; } string[] roles = authTicket.UserData.Split(','); string NowRole = string.Empty; foreach (var item in roles) { NowRole += item; } if (!Role.Contains(NowRole)) //沒有權限訪問當前控制器 ACtion { System.Web.HttpContext.Current.Response.Redirect("/Home/Login"); } base.OnAuthorization(filterContext); } } }
2.2.9、新建一個MVC 控制器 ,命名為:MangerController,代碼如下:
上述代碼就不做演示了,大致過程是這樣的:
Manger/Index的訪問權限如下:
登錄用戶:
上述截圖已經很清晰了,不再作重復說明。
OK,上述代碼便是整個MVC 控制器 登錄驗證/授權認證的全部代碼。下面我們請出本文終極BOSS,如果接收並解析驗證接收的TOKEN。
3、下面介紹webAPI Controller 的認證授權
3.1、首先,我們自定義一個繼承自System.Web.Http 命名空間下的AuthorizeAttribute 屬性來解析並驗證TOKEN
代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Text; using System.Web; using System.Web.Http; using System.Web.Http.Controllers; using TestForToken.CommonCS; namespace TestForToken.Auth2._0 { public class ApiActionAuth : AuthorizeAttribute { public override void OnAuthorization(HttpActionContext context) { var authHeader = context.Request.Headers.FirstOrDefault(a => a.Key == "ApiAuthorization");//獲取接收的Token if (context.Request.Headers == null || !context.Request.Headers.Any() || authHeader.Key == null || string.IsNullOrEmpty(authHeader.Value.FirstOrDefault())) { Throw401Exception(context, "NoToken"); return; } var sendToken = authHeader.Value.FirstOrDefault(); //url獲取token var now = Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 5);//當前的時間戳 var dictPayload = DecodeToken(sendToken); if (dictPayload == null) { Throw401Exception(context, "InvalidToken"); } double iat = dictPayload["iat"]; double exp = dictPayload["exp"]; //檢查令牌的有效期 if (!(iat < now && now < exp))//如果當前時間戳不再Token聲明周期范圍內,則返回Token過期 { Throw401Exception(context, "TokenTimeout"); } //獲取Token的自定義鍵值對 int UserId = dictPayload["UserId"]; string UserName = dictPayload["UserName"]; string UserPwd = dictPayload["UserPwd"]; string UserRole = dictPayload["UserRole"]; //把toke用戶數據放到 HttpContext.Current.User 里 ClientUserData clientUserData = new ClientUserData() { UserId = UserId, UserName = UserName, UserPwd = UserPwd, UserRole = UserRole }; if (HttpContext.Current != null) { HttpContext.Current.User = new UserPrincipal(clientUserData); } } private static IDictionary<string, dynamic> DecodeToken(string token) { try { var dictPayload = JWT.JsonWebToken.DecodeToObject(token, CommonToken.SecretKey) as IDictionary<string, dynamic>; return dictPayload; } catch (Exception ex) { return null; } } private static void Throw401Exception(HttpActionContext actionContext, string exceptionString) { var response = HttpContext.Current.Response; throw new HttpResponseException( actionContext.Request.CreateErrorResponse(System.Net.HttpStatusCode.Unauthorized, exceptionString ?? "Unauthorized")); } private static string RequestToString(HttpRequestMessage request) { var message = new StringBuilder(); if (request.Method != null) message.Append(request.Method); if (request.RequestUri != null) message.Append(" ").Append(request.RequestUri); return message.ToString(); } } }
3.2、新增一個存儲解析Token結果的類,命名為SysHelper.cs,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Security.Principal; using System.Web; namespace TestForToken.Auth2._0 { public class SysHelper { public static UserPrincipal CurrentPrincipal { get { return HttpContext.Current.User as UserPrincipal; } } } public class UserPrincipal : ClientUserData, IPrincipal { public IIdentity Identity { get; private set; } public string[] Roles { get; set; } public UserPrincipal(ClientUserData clientUserData) { this.Identity = new GenericIdentity(string.Format("{0}", clientUserData.UserId)); this.UserId = clientUserData.UserId; this.UserName = clientUserData.UserName; this.UserPwd = clientUserData.UserPwd; this.UserRole = clientUserData.UserRole; } public bool IsInRole(string role) { if (Roles.Any(r => r == role)) { return true; } else { return false; } } } public class ClientUserData { public int UserId { get; set; } public string UserName { get; set; } public string UserPwd { get; set; } public string UserRole { get; set; } } }
3.3、公共Token生成方法修改如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; /*---------------------------------------------------------------- Copyright (C) 2017 陳卧龍 文件名:TestForToken.CommonCS 文件功能描述:Token相關操作 ----------------------------------------------------------------*/ namespace TestForToken.CommonCS { public class CommonToken { public static string SecretKey = "This is a private key for Server";//這個服務端加密秘鑰 屬於私鑰 public static string GetToken(TokenInfo M) { var jwtcreated = Math.Round((DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 5); var jwtcreatedOver = Math.Round((DateTime.UtcNow.AddHours(2) - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds + 5);//TOKEN聲明周期二小時 var payload = new Dictionary<string, dynamic> { {"iss", M.iss},//非必須。issuer 請求實體,可以是發起請求的用戶的信息,也可是jwt的簽發者。 {"iat", jwtcreated},//非必須。issued at。 token創建時間,unix時間戳格式 {"exp", jwtcreatedOver},//非必須。expire 指定token的生命周期。unix時間戳格式 {"aud", M.aud},//非必須。接收該JWT的一方。 {"sub", M.sub},//非必須。該JWT所面向的用戶 {"jti", M.jti},//非必須。JWT ID。針對當前token的唯一標識 {"UserId", M.UserId},//自定義字段 用於存放當前登錄人賬戶信息 {"UserName", M.UserName},//自定義字段 用於存放當前登錄人賬戶信息 {"UserPwd", M.UserPwd},//自定義字段 用於存放當前登錄人登錄密碼信息 {"UserRole", M.UserRole},//自定義字段 用於存放當前登錄人登錄權限信息 }; return JWT.JsonWebToken.Encode(payload, SecretKey, JWT.JwtHashAlgorithm.HS256); } } public class TokenInfo { public TokenInfo() { iss = "簽發者信息"; aud = "http://example.com"; sub = "HomeCare.VIP"; jti = DateTime.Now.ToString("yyyyMMddhhmmss"); UserId = 1; UserName = "jack.chen"; UserPwd = "jack123456"; UserRole = "HomeCare.Administrator"; } // public string iss { get; set; } public string aud { get; set; } public string sub { get; set; } public string jti { get; set; } public int UserId { get; set; } public string UserName { get; set; } public string UserPwd { get; set; } public string UserRole { get; set; } } }
3.4、定義一個MVC API Controller 代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; using System.Web.Http; using TestForToken.Auth2._0; namespace TestForToken.Controllers { public class MangerApiController : ApiController { [ApiActionAuth] [HttpGet] public string GetStr() { int UserId = SysHelper.CurrentPrincipal.UserId; string UserName = SysHelper.CurrentPrincipal.UserName; string UserPwd = SysHelper.CurrentPrincipal.UserPwd; string UserRole = SysHelper.CurrentPrincipal.UserRole; return "當前登錄的第三方用戶信息如下,UserId:" + UserId + ",UserName:" + UserName + ",UserPwd:" + UserPwd + ",UserRole:" + UserRole; } } }
OK,有了上述代碼我們就可以模擬TOKEN驗證了,模擬步驟如下:/
3.5、模擬TOKEN驗證:
3.5.1、生成TOKEN,代碼如下:
using System; using System.Collections.Generic; using System.Linq; using System.Web; using System.Web.UI; using System.Web.UI.WebControls; using TestForToken.CommonCS; namespace TestForToken { public partial class WebForm1 : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { Response.Write(CommonToken.GetToken(new TokenInfo())); } } }
生成的TOKEN為:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiLnrb7lj5HogIXkv6Hmga8iLCJpYXQiOjE1MDk2OTEyODQsImV4cCI6MTUwOTY5ODQ4NCwiYXVkIjoiaHR0cDovL2V4YW1wbGUuY29tIiwic3ViIjoiSG9tZUNhcmUuVklQIiwianRpIjoiMjAxNzExMDMwMjQxMTkiLCJVc2VySWQiOjEsIlVzZXJOYW1lIjoiamFjay5jaGVuIiwiVXNlclB3ZCI6ImphY2sxMjM0NTYiLCJVc2VyUm9sZSI6IkhvbWVDYXJlLkFkbWluaXN0cmF0b3IifQ.IryLo19SSghi34LD1PNIOmzgzavQrnmGBD42pdojXtg
3.5.2、將獲取的TOKEN(有效期兩個小時)返回至客戶端,客戶端將獲取的TOKEN放在 請求頭 Headers 中,模擬請求如下(PostMan):
OK,將上述TOKEN隨便去掉一個字母,請求結果如下:
過期的TOKEN,請求如下:
OK,關於TOKEN更詳細的驗證,還需要大家自行完善,本篇博客我僅僅只驗證了TOKEN是否正確,沒有作進一步的驗證,大家可根據項目需求,主動完善TOKEN驗證代碼。
例如:讀取TOKEN存放的用戶名,密碼,角色等信息再作數據庫驗證。或者如同上述的MVC 控制器控制,驗證角色等信息,總之,,,,,,,不寫了,太累,還有任務沒完成呢。
哈!見諒!