【转】JSON WEB TOKEN,简单谈谈TOKEN的使用及在C#中的实现

转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 控制器控制,验证角色等信息,总之,,,,,,,不写了,太累,还有任务没完成呢。

哈!见谅!

项目源码位置http://download.csdn.net/download/wolongbb/10102574

原文地址:https://www.cnblogs.com/janeaiai/p/11236347.html