美文网首页iOS技术资料我滴学习iOS
什么是 JWT -- JSON WEB TOKEN

什么是 JWT -- JSON WEB TOKEN

作者: Dearmadman | 来源:发表于2016-04-22 16:10 被阅读154253次

    什么是JWT

    Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。

    起源

    说起JWT,我们应该来谈一谈基于token的认证和传统的session认证的区别。

    传统的session认证

    我们知道,http协议本身是一种无状态的协议,而这就意味着如果用户向我们的应用提供了用户名和密码来进行用户认证,那么下一次请求时,用户还要再一次进行用户认证才行,因为根据http协议,我们并不能知道是哪个用户发出的请求,所以为了让我们的应用能识别是哪个用户发出的请求,我们只能在服务器存储一份用户登录的信息,这份登录信息会在响应时传递给浏览器,告诉其保存为cookie,以便下次请求时发送给我们的应用,这样我们的应用就能识别请求来自哪个用户了,这就是传统的基于session认证。

    但是这种基于session的认证使应用本身很难得到扩展,随着不同客户端用户的增加,独立的服务器已无法承载更多的用户,而这时候基于session认证应用的问题就会暴露出来.

    基于session认证所显露的问题

    Session: 每个用户经过我们的应用认证之后,我们的应用都要在服务端做一次记录,以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。

    扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求还必须要请求在这台服务器上,这样才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。

    CSRF: 因为是基于cookie来进行用户识别的, cookie如果被截获,用户就会很容易受到跨站请求伪造的攻击。

    基于token的鉴权机制

    基于token的鉴权机制类似于http协议也是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录了,这就为应用的扩展提供了便利。

    流程上是这样的:

    • 用户使用用户名密码来请求服务器
    • 服务器进行验证用户的信息
    • 服务器通过验证发送给用户一个token
    • 客户端存储token,并在每次请求时附送上这个token值
    • 服务端验证token值,并返回数据

    这个token必须要在每次请求时传递给服务端,它应该保存在请求头里, 另外,服务端要支持CORS(跨来源资源共享)策略,一般我们在服务端这么做就可以了Access-Control-Allow-Origin: *

    那么我们现在回到JWT的主题上。

    JWT长什么样?

    JWT是由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串。就像这样:

    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    JWT的构成

    第一部分我们称它为头部(header),第二部分我们称其为载荷(payload, 类似于飞机上承载的物品),第三部分是签证(signature).

    header

    jwt的头部承载两部分信息:

    • 声明类型,这里是jwt
    • 声明加密的算法 通常直接使用 HMAC SHA256

    完整的头部就像下面这样的JSON:

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

    然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.

    eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
    

    playload

    载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息包含三个部分

    • 标准中注册的声明
    • 公共的声明
    • 私有的声明

    标准中注册的声明 (建议但不强制使用) :

    • iss: jwt签发者
    • sub: jwt所面向的用户
    • aud: 接收jwt的一方
    • exp: jwt的过期时间,这个过期时间必须要大于签发时间
    • nbf: 定义在什么时间之前,该jwt都是不可用的.
    • iat: jwt的签发时间
    • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。

    公共的声明
    公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

    私有的声明
    私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

    定义一个payload:

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

    然后将其进行base64加密,得到Jwt的第二部分。

    eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
    

    signature

    jwt的第三部分是一个签证信息,这个签证信息由三部分组成:

    • header (base64后的)
    • payload (base64后的)
    • secret

    这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

    // javascript
    var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
    
    var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    将这三部分用.连接成一个完整的字符串,构成了最终的jwt:

      eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
    

    注意:secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。

    如何应用

    一般是在请求头里加入Authorization,并加上Bearer标注:

    fetch('api/user/1', {
      headers: {
        'Authorization': 'Bearer ' + token
      }
    })
    

    服务端会验证token,如果验证通过就会返回相应的资源。整个流程就是这样的:

    jwt-diagram

    总结

    优点

    • 因为json的通用性,所以JWT是可以进行跨语言支持的,像JAVA,JavaScript,NodeJS,PHP等很多语言都可以使用。
    • 因为有了payload部分,所以JWT可以在自身存储一些其他业务逻辑所必要的非敏感信息。
    • 便于传输,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
    • 它不需要在服务端保存会话信息, 所以它易于应用的扩展

    安全相关

    • 不应该在jwt的payload部分存放敏感信息,因为该部分是客户端可解密的部分。
    • 保护好secret私钥,该私钥非常重要。
    • 如果可以,请使用https协议

    相关文章

      网友评论

      • 天涯芳草_d773:jwt能防止重放攻击?
        85344816dd8d:不能,不过可以在payload里加时间,有时效控制在一个范围内。
      • 3e67250f3d5d:有些评论,在下实在看不下去
        JWT的playload就是透明的,token解base64就可以看到(如果有不方便公开的数据,可以换成对称加密算法去加密,只不过会影响速度)

        对于有人说修改playload数据可以伪造token,我真的怀疑你的智商。修改playload,验签都过不了的。
      • AlanSun2:那如果我把整个token复制,是不是可以随便发送请求了!还是要https的喽?
      • b39e5d1a36c6:别逗我了 base64是加密 还对称解密😂 现在学php的难道都是半路出家的么
      • 0c45406e8da8:看到你的文章,觉得写得很不错,也很有分享精神。我们侠课岛正好在找远程录制课程视频或图文教程的朋友,我们会给到课程的需求大纲,每一节课程需要你来详细展开写一些代码举例和讲解清楚,对经验积累和创新能力有一定的要求。有兴趣联系我。微信:zhimadt
      • 63d56cb00578:依然只是一个string,在客户端拿到后还是可以在别的机器上伪造身份,只不过不能替换payload部分的用户信息罢了,但是完全可以冒充他人身份。这个仅是解决服务端对用户身份信息的内存占用问题,安全性没有什么提高的。
      • ea6b437801e0:你好,我想请问一下,非单点登录情况 使用JWT怎么能退出其他地方登录的该账号
      • 1d54259501c3:原文链接:https://jwt.io/introduction/ 作者辛苦了。但是这种形式进行身份验证还是存在暴力破解的风险见文章https://www.notsosecure.com/crafting-way-json-web-tokens/;目前正在学习这块。多谢作者翻译。
      • 安之若素_bf71:编码是编码 加密是加密 摘要是摘要,
      • 热心市民小陈:不错,比分布式session更省事,就是要保护好secret
      • jahem:可以
      • 随手写写:本质上是用计算特性替代存储特性
      • 虞愚yu:如果是将一个用户的权限、角色放在payload中生成token,当后台改变了用户的权限如何判断该token失效呢?比如用户会员的例子,当用户充会员后拥有的权限变了,之前分发出去的token应该失效,这一失效判断操作如何做简单一点?
        虞愚yu:@虞愚yu 通过得到 request 来区分请求 是无状态还是有状态的,这里我是通过得到header里面有没有token参数来判断的,你可以参考下
        虞愚yu:@itmajing
        public class CustomizedDefaultSubjectFactory extends DefaultWebSubjectFactory {

        Logger logger = Logger.getLogger(CustomizedDefaultSubjectFactory.class);

        private DefaultSessionStorageEvaluator storageEvaluator;

        public CustomizedDefaultSubjectFactory(DefaultSessionStorageEvaluator storageEvaluator) {
        this.storageEvaluator = storageEvaluator;
        }

        @Override
        public Subject createSubject(SubjectContext context) {
        this.storageEvaluator.setSessionStorageEnabled(true);
        WebSubjectContext wsc = (WebSubjectContext)context;
        ServletRequest servletRequest = wsc.resolveServletRequest();
        HttpServletRequest request = WebUtils.toHttp(servletRequest);
        Enumeration headerNames = request.getHeaderNames();
        while (headerNames.hasMoreElements()){
        this.storageEvaluator.setSessionStorageEnabled(false);
        String headerName = headerNames.nextElement().toString();
        if (headerName.equalsIgnoreCase("token")){//header中有token参数,说明是stateless请求
        context.setSessionCreationEnabled(false);
        //logger.info("=========STATELESS====不创建session");
        break;
        }
        }
        return super.createSubject(context);
        }
        }
        majing:同问
      • HMoe9:学习前,先mark
      • xcFelix:JWT部分基本就是翻译的https://jwt.io/introduction/
      • 江东_61b3:讲的很好,谢谢啦
      • Chensihan:客户端如何保存token?保存在cookie里面,是不是不能单点登录了
      • b3fc90f48787:jwt 技术确实很好,但是我有个问题,jwt每次登录和不登陆都是不同的加密字符串,但是有些场景会不会有问题,比如注册和忘记密码的短信数量,这个是不是需要绕过jwt来做处理呢?
        b3fc90f48787:或者有没有好的方式呢? 我之前接触过类似的处理,是登录和不登陆都是一个加密的字符串,信息用缓存保存在服务器,但是不太喜欢这种方式,类似session的方式。
      • 微码疯狂:哪位大神能讲解一下jwt的安全问题
        1d54259501c3:传递若不采用ssl加密,完全存在第三方劫持,获取登录凭证的风险,拿到token就能用。然后本身这种加密就对权限没有严格区分,普通用户拿到自己的JWT完全存在通过暴力破解将秘钥破解出来的风险,然后拿到秘钥了,岂不是想上谁上谁。还是个复杂度的问题,采用https,然后秘钥采用比较复杂的,增加安全系数。
      • venue:关于token的刷新,是否有什么好的办法
      • 1e062daf85b9:公司招聘PHP开发工程师的岗位,2年左右php开发工作经验,薪资8k-15k,上班地点广州天河区,有意向简历投递:302563219@qq.com
      • 1e062daf85b9:公司招聘PHP开发工程师的岗位,2年左右php开发工作经验,薪资8k-15k,上班地点广州天河区,有意向简历投递:302563219@qq.com
      • 2aa60bf45dc2:是 payload 不是 playload
      • d646d90b6359:不错不错,收藏了。

        推荐下,源码圈 300 胖友的书单整理:http://t.cn/R0Uflld


      • 张玮1994:客户端发送请求是加个Bearer标注有什么意义啊?
      • marlondu:两个问题想请教一下
        1. 如何解决重放攻击,别人窃取到一个token是不是就可以畅通无阻了?
        2. 如何让一个token立即失效,比如我修改密码后,希望能让其他客户端登陆的地方全部强制登出
        心尘宁静:jwt只是缓解了session对于服务器的压力 安全性也只是相对加强了点,并不是就真的安全了,就是像session,人家得到你的sessionID 还不是一样畅通无阻了。
      • pneo:文章有一些错误,摘要、签名、编码、加密是不同的三个概念,这里被混淆了。
        使用base64url是把JSON编码,其实只不过是先扁平化再用64个可读无冲突字符来表达,毫无加密效果。SHA256的摘要只是为JSON数据生成一个“指纹”,防止被篡改,属于完整性范畴,也无任何加密效果,摘要不等于签名,签名是用私钥加密摘要。所以Token本身并没有任何加密机制,它依赖于HTTPS的通道保密能力。不过应该可以自己为Token增加加密机制,这就带来了额外的开销。
        几杯懒散:@划过天空阿忠 MD5不太常用 了 jwt
        79c8281f290c:一般不都是MD5签名吗?
      • 506adb294152:使用jwt,页面端需要进行怎样改造?所有的请求都要修改?
      • 李苏来也:playload是不是写错了?应该是payload
      • a991b6f98cfa:base64加密JWT不是可以被解密了吗?那这内容如何才能安全呢?还是我没看懂呢?谢谢
        72040b122b42:第三个参数才是真正钥匙,如果这个你泄露了那就没法了
      • a991b6f98cfa:有一点不明,还望楼主不惜解惑,万分感谢!就是这JWT是否有专门的类库去实现吗?还是要自己去手动构造这JWT的JSON结构呢?
        f053a00ac7bd:搜jwt.io
      • 远去之情_b302:signature是由header payload加密得来的。jwt为什么还要包含后者? 是不是客户端无法解密
        義_ee92:客户端可以解密,需要暴力解密,大概需要几百年甚至千年
        marlondu:是的,前两段相当于明文,第三段是密文,客户端无法解密,服务端可以解密(不可解密也行),其实就是检查前两段与第三段是否匹配。
      • 0b71107fa7a5:要是别人拿到你的TOKEN是不是意味着可以伪造你的身份
        biaoqianwo:我也有次问题,token被别人用了,就不安全了啊。安全是怎么保证的呢?
        0b71107fa7a5: 貌似就算是加上https也避免不了,别人要是截取的是你非对称加密后的数据是不是意味着还是可以伪造
        紫陌轻风_4865:我觉得也是,所以token的安全性要通过https来保证
      • CemB:新手不懂,能用大白话解释一下吗,谢谢~
      • kaojistream:流程写得很明白
      • ikuma:secret私钥是唯一的,还是每个用户一个?如果是唯一的泄露了风险就比较大了,每个用户一个那就跟存每个用户的token没什么区别了
        majing:@enumlin 假如私钥是定时刷新, 如果用户刚登录完成这边服务器刷新, 是不是要重新登录了?
        enumlin:一般都是随机生产,每次服务启动或者定时刷新。连自己都不知道密匙。
        此博废弃_更新在个人博客:这个确实有问题,私钥的安全性需要保证
      • 长孙门客:有一个问题,secret私钥是如何生成的?保存在哪?如何保存?
        iBlocks:@biaoqianwo 这不就是多此一举了吗,每次访问还需要到数据库里面取出secret解密一下,还不如用session来快,secret是系统管理员定义的全局密码,所有用户的token都是用这个secret加密的,理论上要想获得secret一种是尝试暴力破解这个token获得secret但可能需要几百年,另一种就是攻破服务器。如果系统安全做得更完善的话,这个secret需要定期更换,以免被骇客撞死猫碰对了。
        biaoqianwo:可以和用户名、密码一样保存在数据库中,作为用户的一个属性存在
        kaojistream:自定义的
      • SHIJII:"通常而言session都是保存在内存中" 通常都是存在硬盘里的。。。。
        5ddb8a47a61f:php的默认是文件session,java的是放到内存中的
      • 孙沛2010:这篇讨论的不错,
        TOKEN有效期,需要自己处理
      • wonder:你是不是用JWT把Token认证完全代表了。JWT只是Token认证的一种吧。
      • niunan:支持支持
      • liunewshine:我总算看明白了,JWT解决的不是数据传输安全,这是https,ssl等解决的问题,JWT解决的是服务器端不用存储TOKEN或者SESSION,因为根据JWT就能获取到用户信息,所以貌似用处不大,服务端维护token也耗费不了多少性能
        liunewshine:@孙沛2010 不用存储也可以的,验证签名正确的话就取出userid,存储了干嘛
        孙沛2010:还是没明白,服务器生成token是不是要储存起来?还是要存的,不存怎么验证
        fa29b9feb9e4:看使用场景,在多应用单点登录保持用户一致性效果很好,再也不用去保存用户的回话了
      • liunewshine:token也就是jwt在登录之后和过期之前,是不变的,我无论怎么更改发送的数据,永远会认证成功。因为签名只是对token本身签名,和发送的数据无关
        8de2143074a2:@秋无迹 那么问一下客户端把jwt的token存在哪?localstorage?比cookie安全在哪里呢
        8aa7960f5b55:@liunewshine 我也是 没理解这个安全性在哪。。好像就是将原来的存在session的一些信息保存在了jwt中。然而,JWT什么时候过期,怎么使其过期我都没懂。。。
        liunewshine:@liunewshine 不理解安全在哪
      • kylesean:Access-Control-Allow-Origin: *。服务端这样做不好吧,应该指定具体的 “域”
      • lowkey2046:`然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分.`

        更正一下,base64 不是加密,只是对字符进行编码。 :no_mouth:
        凯勒桑:@划过天空阿忠 什么叫较真了,搞技术本来就是严谨的好伐
        79c8281f290c:你较真了啊
        Chicago_437c:文章所有提到的base64加密,是不是直接可以通过window.btoa()实现?
      • 09b3061e35b6:长知识
      • KBD001小龙:谢谢分享!
      • 8f4e8af1e4ef:非常感谢~~~
      • 半隻饅頭:1、用户使用用户名密码来请求服务器
        2、服务器进行验证用户的信息
        3、服务器通过验证发送给用户一个token
        4、客户端存储token,并在每次请求时附送上这个token值
        5、服务端验证token值,并返回数据

        有个问题请教一下:
        第三步的时候服务器生成token是不是要储存起来?
        如果储存,一般储存到那里?
        iBlocks:@风中的茄子 很好理解,你刷新的token其实是一个新的token跟你之前的token没有任何关联,只要你之前的那个token没有过期,其他页面一样不会有影响
        c2ebf0bf7565:token 不用存吧。我用secret加密验证签名部分是否一样就可以了吧,只要没过期。关键是如何刷新token更新过期时间。如果我开了多个页面,其中一个页面请求更新了过期时间,那其它开的页面是否有影响。
        Henizyang:我一般存在缓存里面 redis 存在数据库太慢了
      • 呆若牦牛:解释的最清楚的一片文章:)
      • 蕲婼圵渁:

        没理解这有什么卵用?
        假如有一个转账的操作,user-a 转账给 user-b, 在传输的过程中被拦截到后,把 user-b 改成 user-c,token原封不动的发送过去,
        这样就变成了 user-a 转账给了 user-c了

        fetch('api/transaction', {
        headers: {
        'Authorization': 'Bearer ' + token
        }
        body: {
        'from': 'user-a',
        'to': 'user-b',
        'amount': 500
        }
        })
        蕲婼圵渁:@baiwenl
        看清楚了,我说的第一段,第二段都没有变化,token也没有变化,被篡改的是发送到服务器的内容
        Henizyang:通过接受到的请求的第1 2段加密得到第三段和以前正确的第三段对比。
        baiwenl:jwt内容分三段,前两段都可以解析并被客户端修改,但是只要被修改过,那么发送到服务器后重新通过前两段计算出的第三段内容就和用户传递过来的不匹配了,这个jwt就废了。别说修改第三段,除非你先把服务器上用来加密的secret弄到手。
      • yljphp:不错的文章
      • needrunning:这个文章不错。 通过token 鉴别用户和权限。 通过sign保证api时效性和参数完整性。 token和sign都做了 基本上可以算是完整的安全机制了。 主流的平台验证都是基于这两方面的扩展
      • 云雀先生:楼主 请问,用这种方法,我怎么才能在服务端上验证呢?我对比?还是存库再进行对比?
        d2c1e6d1dc7e:@北小余 :relaxed: 没有注意看时间。我的我的
        云雀先生:@Koche 都半年过去了。我会了:sweat::sweat::sweat:
        d2c1e6d1dc7e:不需要存库。jwt的payload中可以存放用户名。服务端拿到jwt之后,从中解析出用户名,然后根据用户名验证。
      • choukin:服务端如何验证jwt?
        7e9ab808220f:@FtdConcept 嗯。是的就是这个道理。
        FtdConcept:@l3n641 没看懂? 是这样吗,服务器先生成jwt,然后客户端拿着第三部分,和客户端自己的第一和第二部分再发到服务端,服务端再根据客户端的第一、第二部分,算出第三部分再和客户端传过来的第三部分比较?
        7e9ab808220f:已经知道 header +‘.’+playload 加密得到第三部分内容。然后一起返回给客户端。客户端发送请求数据的时候会带上jwt。服务端根据 header 里面加密算法和plaload。生成第三部分内容。然后对比 第三部分内容。
      • dongwenbo:不错,学习了,武装到牙齿
      • 小阳撒::+1:楼主写的很不错。最后安全那可以使用jwe加上rsa加密。这样会安全点。
        33bb9e6feb42:你意思是secret部分用rsa加密?

      本文标题:什么是 JWT -- JSON WEB TOKEN

      本文链接:https://www.haomeiwen.com/subject/ipdxrttx.html