轉https://www.cnblogs.com/chenwolong/p/Token.html
首先說下什么是 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生成的具體代碼如下:
View Code
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代碼如下:
View Code
2.2.8、新建一個登錄驗證屬性,繼承自:System.Web.Mvc.AuthorizeAttribute,代碼如下:(千呼萬喚始出來啊......~_~)
View Code
2.2.9、新建一個MVC 控制器 ,命名為:MangerController,代碼如下:

上述代碼就不做演示了,大致過程是這樣的:
Manger/Index的訪問權限如下:

登錄用戶:

上述截圖已經很清晰了,不再作重復說明。
OK,上述代碼便是整個MVC 控制器 登錄驗證/授權認證的全部代碼。下面我們請出本文終極BOSS,如果接收並解析驗證接收的TOKEN。
3、下面介紹webAPI Controller 的認證授權
3.1、首先,我們自定義一個繼承自System.Web.Http 命名空間下的AuthorizeAttribute 屬性來解析並驗證TOKEN
代碼如下:
View Code
3.2、新增一個存儲解析Token結果的類,命名為SysHelper.cs,代碼如下:
View Code
3.3、公共Token生成方法修改如下:
View Code
3.4、定義一個MVC API Controller 代碼如下:
View Code

OK,有了上述代碼我們就可以模擬TOKEN驗證了,模擬步驟如下:/
3.5、模擬TOKEN驗證:
3.5.1、生成TOKEN,代碼如下:
View Code
生成的TOKEN為:eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiLnrb7lj5HogIXkv6Hmga8iLCJpYXQiOjE1MDk2OTEyODQsImV4cCI6MTUwOTY5ODQ4NCwiYXVkIjoiaHR0cDovL2V4YW1wbGUuY29tIiwic3ViIjoiSG9tZUNhcmUuVklQIiwianRpIjoiMjAxNzExMDMwMjQxMTkiLCJVc2VySWQiOjEsIlVzZXJOYW1lIjoiamFjay5jaGVuIiwiVXNlclB3ZCI6ImphY2sxMjM0NTYiLCJVc2VyUm9sZSI6IkhvbWVDYXJlLkFkbWluaXN0cmF0b3IifQ.IryLo19SSghi34LD1PNIOmzgzavQrnmGBD42pdojXtg
3.5.2、將獲取的TOKEN(有效期兩個小時)返回至客戶端,客戶端將獲取的TOKEN放在 請求頭 Headers 中,模擬請求如下(PostMan):

OK,將上述TOKEN隨便去掉一個字母,請求結果如下:

過期的TOKEN,請求如下:

OK,關於TOKEN更詳細的驗證,還需要大家自行完善,本篇博客我僅僅只驗證了TOKEN是否正確,沒有作進一步的驗證,大家可根據項目需求,主動完善TOKEN驗證代碼。
例如:讀取TOKEN存放的用戶名,密碼,角色等信息再作數據庫驗證。或者如同上述的MVC 控制器控制,驗證角色等信息,總之,,,,,,,不寫了,太累,還有任務沒完成呢。
哈!見諒!

