TOKEN的使用及在C#中的實現


轉自: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 控制器控制,驗證角色等信息,總之,,,,,,,不寫了,太累,還有任務沒完成呢。

哈!見諒!

項目源碼位置:http://download.csdn.net/download/wolongbb/10102574


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM