tnblog
首页
登录

使用 JSON WEB TOKEN (jwt) 验证

81人阅读 2019/6/27 14:01 评论:2 手机 收藏 关注
分类: C#

一、什么JSON Web Tokens?

    JSON Web Tokens是一种开放的行业标准  RFC 7519方法,用于在双方之间安全地表示索赔。

    JWT.IO允许您解码,验证和生成JWT。其中。JWT 支持任何语言的解码。

    官网地址:https://jwt.io/

二、JWT 的结构

    JWT由三部分组成:

        1.Header     :头信息

            标头通常由两部分组成:令牌的类型,即JWT,以及正在使用的签名算法

            如:

    {  "alg": "HS256",  "typ": "JWT"}

            然后,这个JSON被编码Base64Url,形成JWT的第一部分。


        2.Payload     :有效载荷

            令牌的第二部分是有效负载,其中包含声明。声明是关于实体(通常是用户)和其他数据的声明。

            示例有效负载可以是:

        {  "sub": "1234567890",  "name": "John Doe",  "admin": true}

            然后,有效负载经过Base64Url编码,形成JSON Web令牌的第二部分。由于JWT 支持任何语言的解码,所以不要在Payload 中放置重要信息

        3.Signature    :签名

            如:

    HMACSHA256(        
      base64UrlEncode(header) + "." +
      base64UrlEncode(payload),
      secret)

            签名由三部分组成,base64编码后的表头、base64编码后的有效载荷和秘钥,三者中间由 '.' 字符连接。这里举例的是采用HMACSHA256算法加密的。

            其中secret秘钥是注册后创建app之后生成的(此秘钥非常重要,不要告诉别人哟)。


三、代码实现(以net core 为例):

    1.首先引用 Microsoft.IdentityModel.Tokens.dll。net core 2.1版本中内置了这个库,其他版本要引用。

    2.为了方便,我把整个jwt结构写了配置,并在项目中引用。

     

        创建了对应的配置节点实体:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace fwt.Models
{
    /// <summary>
    /// jwt
    /// </summary>
    public class JWTTokenModel
    {
        public JWTHeader header { get; set; }

        public JWTPayLoad payLoad { get; set; }

        public string secret { get; set; }

    }
    /// <summary>
    /// header
    /// </summary>
    public class JWTHeader
    {
        public string alg { get; set; }

        public string typ { get; set; }
    }
    /// <summary>
    /// payLoad
    /// </summary>
    public class JWTPayLoad
    {
        public string iss { get; set; }

        public string aud { get; set; }

        public string age { get; set; }

        public string sub { get; set; }

        public long nbf { get; set; }

        public long exp { get; set; }
    }
}

通过构造函数依赖注入,在Controller中获取配置信息

Startup 中:

services.Configure<JWTTokenModel>(Configuration.GetSection("JWTSetting"));

 然后编写jwt帮助类,分别创建jwttoken和验证jwttoken

using fwt.Models;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;

namespace fwt
{
    /// <summary>
    /// token 帮助类
    /// </summary>
    public static class TokenContext
    {
        /// <summary>
        /// 创建jwttoken,源码自定义
        /// </summary>
        public static string CreateToken(JWTPayLoad payLoad, string secret, JWTHeader header = null)
        {
            if (header == null)
            {
                header = new JWTHeader()
                {
                    alg = "HS256",
                    typ = "JWT"
                };
            }
            //添加jwt可用时间(应该必须要的)
            var now = DateTime.UtcNow;
            payLoad.nbf = ToUnixEpochDate(now);//可用时间起始
            payLoad.exp = ToUnixEpochDate(now.Add(TimeSpan.FromSeconds(3600)));//可用时间结束,其中3600是有效时间,根据实际情况自定义

            var encodedHeader = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(header));
            var encodedPayload = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(payLoad));

            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secret));
            var encodedSignature = Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(encodedHeader, ".", encodedPayload))));

            var encodedJwt = string.Concat(encodedHeader, ".", encodedPayload, ".", encodedSignature);
            return encodedJwt;
        }

        /// <summary>
        /// 创建jwtToken,采用微软内部方法,默认使用HS256加密,如果需要其他加密方式,请更改源码
        /// 返回的结果和CreateToken一样
        /// </summary>
        public static string CreateTokenByHandler(Dictionary<string, object> payLoad, string secret)
        {

            var now = DateTime.UtcNow;

            // Specifically add the jti (random nonce), iat (issued timestamp), and sub (subject/user) claims.
            // You can add other claims here, if you want:
            var claims = new List<Claim>();
            foreach (var key in payLoad.Keys)
            {
                var tempClaim = new Claim(key, payLoad[key]?.ToString());
                claims.Add(tempClaim);
            }


            // Create the JWT and write it to a string
            var jwt = new JwtSecurityToken(
                issuer: null,
                audience: null,
                claims: claims,
                notBefore: now,
                expires: now.Add(TimeSpan.FromSeconds(3600)),
                signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.ASCII.GetBytes(secret)), SecurityAlgorithms.HmacSha256));
            var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);
            return encodedJwt;
        }

        /// <summary>
        /// 验证身份 验证签名的有效性,
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <param name="validatePayLoad">自定义各类验证; 是否包含那种申明,或者申明的值, </param>
        /// 例如:payLoad["aud"]?.ToString() == "roberAuddience";
        /// 例如:验证是否过期 等
        public static bool Validate(string encodeJwt, Func<Dictionary<string, object>, bool> validatePayLoad,string secret)
        {
            var success = true;
            var jwtArr = encodeJwt.Split('.');
            var header = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[0]));
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));

            var hs256 = new HMACSHA256(Encoding.ASCII.GetBytes(secret));
            //首先验证签名是否正确(必须的)
            success = success && string.Equals(jwtArr[2], Base64UrlEncoder.Encode(hs256.ComputeHash(Encoding.UTF8.GetBytes(string.Concat(jwtArr[0], ".", jwtArr[1])))));
            if (!success)
            {
                return success;//签名不正确直接返回
            }
            //其次验证是否在有效期内(也应该必须)
            var now = ToUnixEpochDate(DateTime.UtcNow);
            success = success && (now >= long.Parse(payLoad["nbf"].ToString()) && now < long.Parse(payLoad["exp"].ToString()));

            //再其次 进行自定义的验证
            success = success && validatePayLoad(payLoad);

            return success;
        }
        /// <summary>
        /// 获取jwt中的payLoad
        /// </summary>
        /// <param name="encodeJwt"></param>
        /// <returns></returns>
        public static Dictionary<string, object> GetPayLoad(string encodeJwt)
        {
            var jwtArr = encodeJwt.Split('.');
            var payLoad = JsonConvert.DeserializeObject<Dictionary<string, object>>(Base64UrlEncoder.Decode(jwtArr[1]));
            return payLoad;
        }
        public static long ToUnixEpochDate(DateTime date) =>
            (long)Math.Round((date.ToUniversalTime() - new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero)).TotalSeconds);
    }
}

控制器调用

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using fwt.Models;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json;
using System.Text;
using System.Security.Cryptography;
using Microsoft.Extensions.Options;

namespace fwt.Controllers
{
    public class HomeController : Controller
    {
        private readonly JWTTokenModel _jWTTokenModel;
        //依赖注入
        public HomeController(IOptions<JWTTokenModel> jWTTokenModel)
        {
            _jWTTokenModel = jWTTokenModel.Value;
        }

        public IActionResult Index()
        {
            //获取token
            string tokenstr = TokenContext.CreateToken(_jWTTokenModel.payLoad, _jWTTokenModel.secret, _jWTTokenModel.header);

            //自定义验证
            Func<Dictionary<string, object>, bool> func = delegate (Dictionary<string, object> s)
            {
                if (s["iss"].ToString() == _jWTTokenModel.payLoad.iss
                    && s["aud"].ToString() == _jWTTokenModel.payLoad.aud
                    && s["sub"].ToString() == _jWTTokenModel.payLoad.sub)
                {
                    return true;
                }
                return false;
            };
            //验证token
            bool success = TokenContext.Validate(tokenstr, func, _jWTTokenModel.secret);
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }


    }
}

ok,原理差不多就是这个样子。官网中也提供了解码的调试器,可以解码生成的token

将生成的token复制到左边即可。


使用场景:比如webapi接口的token验证,就可以用到jwt,先获取登录后的token,把token传入authorization中才能进行下面接口的调用。


总结一哈:由于JSON比XML更简洁,因此在编码时它的大小也更小,使得JWT比SAML更紧凑。这使得JWT成为在HTML和HTTP环境中传递的不错选择。

JSON解析器在大多数编程语言中很常见,因为它们直接映射到对象。相反,XML没有自然的文档到对象映射。这使得使用JWT比使用SAML断言更容易。


个人理解:jwt其实是一种规范,一种加密验证的解决方案,更多的其实是给一个思路,一个标准,不一定要完全按照jwt的格式去加密。一定程度上

可以取代session和cookie进行用户认证,两者各有优缺点。




评价
关注
Decorating heart
文章
6
评论
8
分类
16
关注
16
{{item.ArticleTitle}}
{{item.BlogName}} : {{item.Content}}