【问题标题】:如何在不使用库的情况下在 javascript 中解码 jwt 令牌?
【发布时间】:2016-11-27 20:47:03
【问题描述】:

如何使用 JavaScript 解码 JWT 的负载?没有图书馆。所以令牌只是返回一个可以被我的前端应用程序使用的有效负载对象。

示例令牌:xxxxxxxxx.XXXXXXXX.xxxxxxxx

结果就是payload:

{exp: 10012016 name: john doe, scope:['admin']}

【问题讨论】:

  • 它是如何编码的?只是做相反的事情。您将需要共享密钥。
  • 您可以尝试访问jwt.io 网站并获取它提供的 JavaScript 库。
  • 由于这个问题有一些流量,我想添加一个免责声明:如果你盲目地解码令牌的有效负载,而不验证签名,你可能(也可能不会)遇到安全问题!在盲目使用此 stackoverflow 问题中提供的任何代码之前,请确保您了解自己的安全架构。
  • @CarstenHoffmann 我该如何验证签名??
  • @SaurabhTiwari 通常您作为客户无法验证签名。请参阅此处stackoverflow.com/questions/59632301/,了解 JWT 是什么、签名和编码之间的区别以及打算如何使用。

标签: javascript jwt


【解决方案1】:

您可以定义和使用这一个线性函数:

jwtDecode = b => JSON.parse(Buffer.from(b.split('.')[1], 'base64').toString('binary'));

【讨论】:

    【解决方案2】:

    如果你使用 Node.JS, 您可以通过以下方式使用本机 Buffer 模块:

    const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImp0aSI6ImU3YjQ0Mjc4LTZlZDYtNDJlZC05MTZmLWFjZDQzNzhkM2U0YSIsImlhdCI6MTU5NTg3NzUxOCwiZXhwIjoxNTk1ODgxMTE4fQ.WXyDlDMMSJAjOFF9oAU9JrRHg2wio-WolWAkAaY3kg4';
    const tokenDecodablePart = token.split('.')[1];
    const decoded = Buffer.from(tokenDecodablePart, 'base64').toString();
    console.log(decoded)
    

    你很高兴 :-)

    【讨论】:

    • PS:这个令牌是使用jsonwebtoken.io制作的
    • 这里缩短为JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()),直接获取JS对象。
    【解决方案3】:

    如果使用node.js 16或更高版本,可以使用内置的base64url编码器/解码器。

    let payload = JSON.parse(Buffer.from(token.split(".")[1], "base64url"));
    

    【讨论】:

      【解决方案4】:

      jwt-decode.js 的 es-module 友好简化版

      function b64DecodeUnicode(str) {
        return decodeURIComponent(
          atob(str).replace(/(.)/g, function (m, p) {
            var code = p.charCodeAt(0).toString(16).toUpperCase();
            if (code.length < 2) {
              code = "0" + code;
            }
            return "%" + code;
          })
        );
      }
      
      function base64_url_decode(str) {
        var output = str.replace(/-/g, "+").replace(/_/g, "/");
        switch (output.length % 4) {
          case 0:
            break;
          case 2:
            output += "==";
            break;
          case 3:
            output += "=";
            break;
          default:
            throw "Illegal base64url string!";
        }
      
        try {
          return b64DecodeUnicode(output);
        } catch (err) {
          return atob(output);
        }
      }
      
      export function jwtDecode(token, options) {
        options = options || {};
        var pos = options.header === true ? 0 : 1;
        try {
          return JSON.parse(base64_url_decode(token.split(".")[pos]));
        } catch (e) {
          console.log(e.message);
        }
      }
      

      【讨论】:

      • 那个包 ES Module 不友好吗?
      • 为什么要把“-”换成“+”,把“_”换成“/”?
      【解决方案5】:

      如果你使用 Typescript vanilla JavaScript,这里有一个零依赖,准备复制粘贴到你的项目中的简单函数(基于@Rajan Maharjan 的回答)。

      这个答案特别好,不仅因为它不依赖于任何 npm 模块,还因为它不依赖于这里的一些其他解决方案正在使用的任何 node.js 内置模块(如Buffer)当然会在浏览器中失败(除非 polyfill,但首先没有理由这样做)。此外 JSON.parse 可能在运行时失败,这个版本(尤其是在 Typescript 中)将强制处理。 JSDoc 注释将使您的代码的未来维护者心存感激。 :)

      /**
       * Returns a JS object representation of a Javascript Web Token from its common encoded
       * string form.
       *
       * @template T the expected shape of the parsed token
       * @param {string} token a Javascript Web Token in base64 encoded, `.` separated form
       * @returns {(T | undefined)} an object-representation of the token
       * or undefined if parsing failed
       */
      export function getParsedJwt<T extends object = { [k: string]: string | number }>(
        token: string,
      ): T | undefined {
        try {
          return JSON.parse(atob(token.split('.')[1]))
        } catch {
          return undefined
        }
      }
      

      为了完成,这里也是原版 javascript 版本:

      /**
       * Returns a JS object representation of a Javascript Web Token from its common encoded
       * string form.
       *
       * @param {string} token a Javascript Web Token in base64 encoded, `.` separated form
       * @returns {(object | undefined)} an object-representation of the token
       * or undefined if parsing failed
       */
      export function getParsedJwt(token) {
        try {
          return JSON.parse(atob(token.split('.')[1]))
        } catch (error) {
          return undefined
        }
      }
      

      【讨论】:

      • 1.这不会对 JWT 的所有部分进行编码,而只是对其中一个部分进行编码:“Payload”被解码,而“Header”和“Signature”被丢弃。这在文档中值得注意。 2. 返回类型限制性太强,因为它只允许字符串和数字作为值,但是 RFC 7519, Section 3.1 中的例子。使用布尔值,说明问题。 3. 您的实现不检查 JWT 的签名是否与其有效负载匹配。这可能有问题,这取决于你的函数是如何使用的。这应该在文档中提及,因为它会带来安全风险。
      • @LorenzLeutgeb 所有有效的 cmets 我希望在 NPM 上发布的一个库的 github 问题中看到,该库旨在解决 JWT 解码/解析问题。鉴于这个 Stack Overflow 问题上的人很可能正在寻找 copy-pasta script 而不是库,我尽我所能平衡改进我在这里看到的所有其他答案,同时保持代码可读和可扩展/可维护。干杯,感谢您花时间澄清这些事情。
      【解决方案6】:

      在 Node.js (TypeScript) 中:

      import { TextDecoder } from 'util';
      
      function decode(jwt: string) {
          const { 0: encodedHeader, 1: encodedPayload, 2: signature, length } = jwt.split('.');
      
          if (length !== 3) {
              throw new TypeError('Invalid JWT');
          }
      
          const decode = (input: string): JSON => { return JSON.parse(new TextDecoder().decode(new Uint8Array(Buffer.from(input, 'base64')))); };
      
          return { header: decode(encodedHeader), payload: decode(encodedPayload), signature: signature };
      }
      

      对于jose by panva on GitHub,您可以使用最小的import { decode as base64Decode } from 'jose/util/base64url' 并将new Uint8Array(Buffer.from(input, 'base64')) 替换为base64Decode(input)。然后代码应该可以在浏览器和 Node.js 中运行。

      【讨论】:

        【解决方案7】:
        function parseJwt(token) {
          var base64Payload = token.split('.')[1];
          var payload = Buffer.from(base64Payload, 'base64');
          return JSON.parse(payload.toString());
        }
        
        let payload= parseJwt("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c");
        console.log("payload:- ", payload);
        

        如果使用node,可能需要使用buffer包:

        npm install buffer
        var Buffer = require('buffer/').Buffer
        

        【讨论】:

        • 我使用它而不是 atob,因为它已被弃用。
        【解决方案8】:

        这是我在研究这个问题后刚刚提出的一个功能更丰富的解决方案:

        const parseJwt = (token) => {
            try {
                if (!token) {
                    throw new Error('parseJwt# Token is required.');
                }
        
                const base64Payload = token.split('.')[1];
                let payload = new Uint8Array();
        
                try {
                    payload = Buffer.from(base64Payload, 'base64');
                } catch (err) {
                    throw new Error(`parseJwt# Malformed token: ${err}`);
                }
        
                return {
                    decodedToken: JSON.parse(payload),
                };
            } catch (err) {
                console.log(`Bonus logging: ${err}`);
        
                return {
                    error: 'Unable to decode token.',
                };
            }
        };
        

        以下是一些使用示例:

        const unhappy_path1 = parseJwt('sk4u7vgbis4ewku7gvtybrose4ui7gvtmalformedtoken');
        console.log('unhappy_path1', unhappy_path1);
        
        const unhappy_path2 = parseJwt('sk4u7vgbis4ewku7gvtybrose4ui7gvt.malformedtoken');
        console.log('unhappy_path2', unhappy_path2);
        
        const unhappy_path3 = parseJwt();
        console.log('unhappy_path3', unhappy_path3);
        
        const { error, decodedToken } = parseJwt('eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c');
        if (!decodedToken.exp) {
            console.log('almost_happy_path: token has illegal claims (missing expires_at timestamp)', decodedToken);
            // note: exp, iat, iss, jti, nbf, prv, sub
        }
        

        我无法在 StackOverflow 代码 sn-p 工具中使其可运行,但如果您运行该代码,您会看到以下大致内容:

        我让parseJwt 函数始终返回一个对象(在某种程度上是出于静态类型的原因)。

        这允许您使用以下语法:

        const { decodedToken, error } = parseJwt(token);
        

        然后您可以在运行时测试特定类型的错误并避免任何命名冲突。

        如果有人能想到对此代码进行任何不费力、高价值的更改,请随时为next(person) 编辑我的答案。

        【讨论】:

          【解决方案9】:

          您可以使用纯javascript atob() 函数将令牌解码为字符串:

          atob(token.split('.')[1]);
          

          或者直接解析成json对象:

          JSON.parse(atob(token.split('.')[1]));
          

          了解atob()btoa() 内置javascript 函数Base64 encoding and decoding - Web APIs | MDN

          【讨论】:

          • JWT uses base64url (RFC 4648 §5)。这个答案使用base64。这个答案是错误的。
          • @Pang 谢谢!我没有意识到有什么不同。这只是为我指出了我们的应用程序存在的解码错误的解决方案。
          【解决方案10】:

          答案来自GitHub - auth0/jwt-decode。更改了输入/输出以包括字符串拆分和返回对象 { header, payload, signature },这样您就可以传递整个令牌。

          var jwtDecode = function (jwt) {
          
                  function b64DecodeUnicode(str) {
                      return decodeURIComponent(atob(str).replace(/(.)/g, function (m, p) {
                          var code = p.charCodeAt(0).toString(16).toUpperCase();
                          if (code.length < 2) {
                              code = '0' + code;
                          }
                          return '%' + code;
                      }));
                  }
          
                  function decode(str) {
                      var output = str.replace(/-/g, "+").replace(/_/g, "/");
                      switch (output.length % 4) {
                          case 0:
                              break;
                          case 2:
                              output += "==";
                              break;
                          case 3:
                              output += "=";
                              break;
                          default:
                              throw "Illegal base64url string!";
                      }
          
                      try {
                          return b64DecodeUnicode(output);
                      } catch (err) {
                          return atob(output);
                      }
                  }
          
                  var jwtArray = jwt.split('.');
          
                  return {
                      header: decode(jwtArray[0]),
                      payload: decode(jwtArray[1]),
                      signature: decode(jwtArray[2])
                  };
          
              };
          

          【讨论】:

            【解决方案11】:

            工作 unicode 文本 JWT 解析器函数:

            function parseJwt (token) {
                var base64Url = token.split('.')[1];
                var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
                var jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
                    return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
                }).join(''));
            
                return JSON.parse(jsonPayload);
            };
            

            【讨论】:

            • 这个解决方案甚至可以在 Postman (tests tap) 中使用,因为它不需要任何额外的库安装。我用它从 auth-token 中提取用户 ID。
            • 注意:在 Postman 中,我必须从 JSON.parse(window.atob(base64)) 中删除“窗口”才能使其正常工作。只是return JSON.parse(atob(base64));,然后postman.setEnvironmentVariable("userId", parseJwt(jsonData.access_token));“access_token”在我的情况下是令牌响应值的关键(在你的情况下可能不同)。
            • 最好使用jwt-decode 模块,因为它很小,但处理起来更好。
            • 可能对某些人来说微不足道,但不要忘记通过添加 const atob = require('atob'); 来添加 atob 作为依赖项
            • 如果您使用的是 NodeJS,并且无法访问 atob,请改用:Buffer.from(base64, 'base64').toString()
            【解决方案12】:

            用于解码 JSON Web 令牌 (JWT) 的简单 NodeJS 解决方案

            function decodeTokenComponent(value) {
                const buff = new Buffer(value, 'base64')
                const text = buff.toString('ascii')
                return JSON.parse(text)
            }
            
            const token = 'xxxxxxxxx.XXXXXXXX.xxxxxxxx'
            const [headerEncoded, payloadEncoded, signature] = token.split('.')
            const [header, payload] = [headerEncoded, payloadEncoded].map(decodeTokenComponent)
            
            console.log(`header: ${header}`)
            console.log(`payload: ${payload}`)
            console.log(`signature: ${signature}`)
            

            【讨论】:

              【解决方案13】:

              基于此处的答案和here:

              const dashRE = /-/g;
              const lodashRE = /_/g;
              
              module.exports = function jwtDecode(tokenStr) {
                const base64Url = tokenStr.split('.')[1];
                if (base64Url === undefined) return null;
                const base64 = base64Url.replace(dashRE, '+').replace(lodashRE, '/');
                const jsonStr = Buffer.from(base64, 'base64').toString();
                return JSON.parse(jsonStr);
              };
              

              【讨论】:

                【解决方案14】:

                由于 nodejs 环境中不存在“window”对象, 我们可以使用以下代码行:

                let base64Url = token.split('.')[1]; // token you get
                let base64 = base64Url.replace('-', '+').replace('_', '/');
                let decodedData = JSON.parse(Buffer.from(base64, 'base64').toString('binary'));
                

                它非常适合我。希望对您有所帮助。

                【讨论】:

                • node js的完美答案
                • 这在 Node JS 上非常适合我
                【解决方案15】:

                我使用这个函数根据this答案获取payload、header、exp(Expiration Time)、iat(Issued At)

                function parseJwt(token) {
                  try {
                    // Get Token Header
                    const base64HeaderUrl = token.split('.')[0];
                    const base64Header = base64HeaderUrl.replace('-', '+').replace('_', '/');
                    const headerData = JSON.parse(window.atob(base64Header));
                
                    // Get Token payload and date's
                    const base64Url = token.split('.')[1];
                    const base64 = base64Url.replace('-', '+').replace('_', '/');
                    const dataJWT = JSON.parse(window.atob(base64));
                    dataJWT.header = headerData;
                
                // TODO: add expiration at check ...
                
                
                    return dataJWT;
                  } catch (err) {
                    return false;
                  }
                }
                
                const jwtDecoded = parseJwt('YOUR_TOKEN') ;
                if(jwtDecoded)
                {
                    console.log(jwtDecoded)
                }
                

                【讨论】:

                • 这个答案要好一些,但它有两个半问题。首先,它不检查签名(数组项 2)。其次,REPLACE 无法正常工作,因为它们错过了正则表达式上的“g”标志(只会替换 JWT 上第一次出现的 - 和 _,就像 Racing Tadpole 在另一篇文章中评论的那样)。还有一半:要解码数组项 0 和 1,您可以使用 FOR 循环,而不是复制整个代码(这是一个简短的代码,但可以提高效率,因为它的方式是 SPLIT 执行两次)。
                【解决方案16】:

                @Peheje 可以,但是你会遇到 unicode 的问题。 为了修复它,我使用https://stackoverflow.com/a/30106551/5277071上的代码;

                let b64DecodeUnicode = str =>
                  decodeURIComponent(
                    Array.prototype.map.call(atob(str), c =>
                      '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)
                    ).join(''))
                
                let parseJwt = token =>
                  JSON.parse(
                    b64DecodeUnicode(
                      token.split('.')[1].replace('-', '+').replace('_', '/')
                    )
                  )
                
                
                let form = document.getElementById("form")
                form.addEventListener("submit", (e) => {
                   form.out.value = JSON.stringify(
                      parseJwt(form.jwt.value)
                   )
                   e.preventDefault();
                })
                textarea{width:300px; height:60px; display:block}
                <form id="form" action="parse">
                  <textarea name="jwt">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkrDtGhuIETDs8OoIiwiYWRtaW4iOnRydWV9.469tBeJmYLERjlKi9u6gylb-2NsjHLC_6kZNdtoOGsA</textarea>
                  <textarea name="out"></textarea>
                  <input type="submit" value="parse" />
                </form>

                【讨论】:

                • +1 但如果 R​​acing Tadpole 对 Peheje 的回答的评论是正确的(替换调用只会替换第一个实例),那么同样的修复将适用于此。
                【解决方案17】:

                你可以使用jwt-decode,那么你可以这样写:

                import jwt_decode from 'jwt-decode';
                
                var token = 'eyJ0eXAiO.../// jwt token';
                
                var decoded = jwt_decode(token);
                console.log(decoded);
                /*{exp: 10012016 name: john doe, scope:['admin']}*/
                

                【讨论】:

                • “我的意思是没有图书馆。”
                • 他们是这个库的问题。主要是配合firefox在用。我遇到的问题是,如果一个令牌 == null 是由于注销或过期而导致的;这只是杀死了一个错误的页面。
                • @ApertureSecurity 你需要捕捉到这个错误,但不可否认这就是我不想使用这个库的原因
                • 这似乎不支持 GZIP。事实上,我找不到任何支持 GZIP 的 JS 库。
                【解决方案18】:

                我在jwt.io 找到了这段代码,它运行良好。

                //this is used to parse base64
                function url_base64_decode(str) {
                  var output = str.replace(/-/g, '+').replace(/_/g, '/');
                  switch (output.length % 4) {
                    case 0:
                      break;
                    case 2:
                      output += '==';
                      break;
                    case 3:
                      output += '=';
                      break;
                    default:
                      throw 'Illegal base64url string!';
                  }
                  var result = window.atob(output); //polifyll https://github.com/davidchambers/Base64.js
                  try{
                    return decodeURIComponent(escape(result));
                  } catch (err) {
                    return result;
                  }
                }
                

                在某些情况下(某些开发平台),
                the best answer(for now) 面临 base64 长度无效的问题。
                所以,我需要一种更稳定的方式。

                希望对你有帮助。

                【讨论】:

                  【解决方案19】:

                  jwt.io 的所有功能并不支持所有语言。在 NodeJs 中你可以使用

                  var decoded = jwt.decode(token);
                  

                  【讨论】:

                  • 如果没有库,您只需在令牌的第二部分执行 base64 解码 { var payload = token.split('.')[1]); } 然后执行 base64 解码 { var decodedData = atob(payload); }
                  【解决方案20】:

                  带有try-catch的简单函数

                  const parseJwt = (token) => {
                    try {
                      return JSON.parse(atob(token.split('.')[1]));
                    } catch (e) {
                      return null;
                    }
                  };
                  

                  谢谢!

                  【讨论】:

                  猜你喜欢
                  • 2015-10-02
                  • 1970-01-01
                  • 2020-05-12
                  • 2023-01-03
                  • 2020-12-18
                  • 2020-02-02
                  • 2016-10-08
                  • 2021-03-24
                  • 2021-10-23
                  相关资源
                  最近更新 更多