V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
mynameisny
V2EX  ›  问与答

请教大家一个关于 RESTFul 认证的问题:如何保证一个用户只能有一个 JWT( Json Web Token)

  •  
  •   mynameisny · 2016-11-15 17:09:07 +08:00 · 19083 次点击
    这是一个创建于 2728 天前的主题,其中的信息可能已经有所发展或是发生改变。

    本人菜鸟,目前在开发几个 RESTFul API 。现在遇见一个头疼的问题,希望大家不吝指教:

    如何能保证一个用户在一段时间内只能有一个 Token 是可用的?

    ---- 用户在某一个时刻只能有一个有效 Token ,即,用户已有一个有效 Token 时,再次请求 Token 时要作废之前生成的那个 Token 。

    实际的业务流程是:

    • 需要调用者先使用用户名密码去签定身份
    • 鉴定成功,服务器返回一个 Token
    • 调用者之后再调用其他 API 时就在 HTTP Authorization Header 中带着这个 Token

    目前采用的方案是:

    • 使用 jjwt 生成 Token ,保存在 Redis 中,以用户名作为 Key
    • 通过设置 Redis 键的 TTL 来实现 Token 自动过期
    • 通过在 Servlet Filter 中拦截请求判断 Token 是否有效
    • 由于 Redis 是基于 Key-Value 进行存储,因此可以实现新的 Token 将覆盖旧的 Token ,保证一个用户在一个时间段只有一个可用 Token

    问题和纠结是:

    • 查阅了好多资料都提到 RESTFul API 通常被设计成无状态的,采用 JWT ( Json Web Token )进行认证是不需要将生成的 Token 保存在 Session 中的,那现在我的方案明显就是相悖的。
    • 在我现在的方案中,如果用户担心旧的 Token 已经被泄漏,可以重新生成一个 Token ,那么旧的 Token 就会过期,这是很好的结果。但是如果用户写好的某个调用程序正在执行,不小心又重新生成了一个 Token ,那么调用就会停止。如何避免这种情况?
    第 1 条附言  ·  2016-11-15 17:57:48 +08:00

    感谢大家的回复!

    目前主要纠结的还是Json Web Token既然不建议保存在服务器,那么服务器又怎么能保证同一时间段只能为一个用户维护一个Token呢?

    23 条回复    2016-11-16 15:24:35 +08:00
    Clarencep
        1
    Clarencep  
       2016-11-15 17:18:37 +08:00
    “但是如果用户写好的某个调用程序正在执行,不小心又重新生成了一个 Token ,那么调用就会停止。” -- 这种情况一般不需要考虑, 可以参考下各大开发平台的 API (如微信和支付宝)。
    mynameisny
        2
    mynameisny  
    OP
       2016-11-15 17:21:08 +08:00
    @Clarencep 哦哦哦。了解了,这种属于应该操作失误吧。不知纠结一是不是可以给些指点?想保证唯一 Token ,应该怎样搞……
    ty89
        3
    ty89  
       2016-11-15 17:32:39 +08:00
    看你正文里面写到,“ token 保存在 Redis 中,以用户名作为 Key “,这样的话如果你的生成 token 的算法没毛病,就可以认为是唯一的了。不知道你纠结的点在哪里。
    ty89
        4
    ty89  
       2016-11-15 17:34:55 +08:00
    至于第二点,你担心得完全多余了。
    mynameisny
        5
    mynameisny  
    OP
       2016-11-15 17:43:19 +08:00
    @ty89 纠结之处在于好多资料上说, JWT 生成 Token 是不应该放在服务器端保存起来的,但是不记录它的状态怎么能知道它只有一份呢?
    xxxyyy
        6
    xxxyyy  
       2016-11-15 17:47:14 +08:00 via Android
    你反过来做就行了,只保存吊销的 token ,在鉴权时查看下 token 是否在 redis 中,如果在就拒绝就可以了,在保存到 redis 的 token 还可以设置一个 ttl ,时间是 token 的过期时间,这样就不需要手动去清理 redis 里的 token 了。
    mynameisny
        7
    mynameisny  
    OP
       2016-11-15 17:52:28 +08:00
    @xxxyyy 感谢回复,我现在就是这么做的呢,依赖于 Redis 的 TTL ,过期了就会把那个 Token 自动的删除,如果 Token 不存在了就不准调用。关键的问题还是这个 Token 要保存的问题,是不是我的这个情景里是必须要在服务器保持一份 Token 呢?
    xxxyyy
        8
    xxxyyy  
       2016-11-15 17:54:05 +08:00 via Android
    @mynameisny 为什么要保存 token ?
    Herobs
        9
    Herobs  
       2016-11-15 17:56:48 +08:00 via Android
    token 本身就携带了足够多的信息,包括过期时间。泄漏的话,只会是在有效期内存在风险,这个是可以接受的。

    把 token 存起来是完全没有意义的, JWT 就是用来去中心化,达到 stateless 的目的。
    mynameisny
        10
    mynameisny  
    OP
       2016-11-15 17:58:45 +08:00
    @xxxyyy 是希望保证让一个用户在一段时间内只有一个 Token 是有效的。
    mcfog
        11
    mcfog  
       2016-11-15 18:00:55 +08:00
    简单研究过 jwt 。个人不建议用 jwt 作 session 用途。原因基本和你的纠结一样, jwt 最大的好处就是把数据带到前端去转了一圈,从而能实现无状态服务端,但 session 这件事天生就是关于维持状态的,和 jwt 是矛盾的。有状态的服务直接发 uuid 作 session key 就好

    另外 jwt 之前很多开源实现暴露过致命漏洞(客户端可以直接构造加密方式为不加密的 token 通过校验)这也让我对 jwt 的影响分大减,说白了 jwt 就是参数头+body 的一个约定而已,和自己定协议唯一的区别就是他开源,有三方库,但这些库的质量如果没保证的话,还不如自己定协议靠谱。毕竟自己写代码出 bug 那是自己水平不够测试不严,用三方库出 bug 那完全是吃苍蝇

    建议阅读:

    http://cryto.net/~joepie91/blog/2016/06/13/stop-using-jwt-for-sessions/
    https://auth0.com/blog/critical-vulnerabilities-in-json-web-token-libraries/
    mynameisny
        12
    mynameisny  
    OP
       2016-11-15 18:02:26 +08:00
    @Herobs 对对,我就是看到了很多关于 JWT stateless 方面的介绍才来求助的。如果不考虑泄漏的情况,只是不希望用户有多个 Token 同时可用,是不是我现在的这个问题就限制在必须是有一个“中心”了呢?
    Herobs
        13
    Herobs  
       2016-11-15 18:06:03 +08:00 via Android
    @mynameisny 不希望用户有多个 token 同时有效的目的是什么,如果非要达到这个目的,可以考虑为每个用户维护一个递增的整数,来达到节省资源的目的。
    xxxyyy
        14
    xxxyyy  
       2016-11-15 18:08:23 +08:00 via Android
    要保证让一个用户在一段时间内只有一个 Token 是有效,这就产生状态了,需要服务器端保存这个状态才行。
    mynameisny
        15
    mynameisny  
    OP
       2016-11-15 18:11:43 +08:00
    @mcfog 受教受教!! JWT 和 Session 关于状态果真是矛盾的。其实我现在的方案中是在借用了 JWT 生成 Token 的功能而已,完全没有使用它无状态的特性。我只是享受到了一个确定草案的开源实现而已……如您所言,我完成可以自己实现一个约定,能解密调用者传递来的 Token ,解析出我想要信息,并保证 Token 唯一就可以了。
    mynameisny
        16
    mynameisny  
    OP
       2016-11-15 18:12:42 +08:00
    @xxxyyy 是是是,感谢众高手们的指点,是我误用 JWT 了,我确实是需要带“状态”的 Token 。
    ctsed
        17
    ctsed  
       2016-11-15 18:59:20 +08:00 via iPhone
    我打开俩帖子,第一个帖子需要刷新一下才能回复?
    caixiexin
        18
    caixiexin  
       2016-11-15 19:04:32 +08:00 via Android
    用 jwt 做认证的话,不需要在服务的保存它,这有篇文章讲的挺清楚的。
    http://blog.leapoahead.com/2015/09/07/user-authentication-with-jwt/
    Koyoter
        19
    Koyoter  
       2016-11-15 19:17:24 +08:00
    token 绑定 ip 或者 mac 地址?变了就算下线
    mynameisny
        20
    mynameisny  
    OP
       2016-11-15 19:48:37 +08:00
    @caixiexin mcfog 11 楼和 xxxyyy 14 楼的回复是很精僻的, Token 本身是不需要状态的,但是限定了它在一段时间只有唯一实例,就是产生状态了,一旦产生了状态了就成了“ SESSION ”
    mynameisny
        21
    mynameisny  
    OP
       2016-11-15 19:52:43 +08:00
    @Koyoter 可能是误解了,并不是讨论 Token 绑定用户的问题,同一用户同一 IP 同一 MAC ,在同一时刻生成了多个有效 Token 。
    future0906
        22
    future0906  
       2016-11-15 22:32:34 +08:00
    @mynameisny

    我没有用过 JWT ,但是我猜应该是类似于 signed-json 的东西,这个和 token 本质上是有区别的,用来做简单的鉴权可以,真正做 API 的话必须保存一个 token=>auth_state_dict 的一个关系。这个好处是服务端可以控制这个鉴权的信息(权限变更之类的)

    你关键的问题在于对 RESTFul 对 stateless 的误解,所谓的 stateless 是对 HTTP 层来说的,对于 HTTP 后面的资源根本没有办法做到 stateless (不然怎么保存用户数据?)。

    所以传统意义的 session ( http 层)肯定不能用,只能是存在数据库中,以保证服务可以 fail-over 。
    mynameisny
        23
    mynameisny  
    OP
       2016-11-16 15:24:35 +08:00
    @future0906 您提到的“ stateless 是对 HTTP 层来说的”这点理解了,但是不能将这个 Token 放在 session 中实现 fail-over 这块还是不怎么明白,烦请再帮忙讲一下。

    可不可以只在用户的 SESSION 作用域中保存一份唯一的 Token ,再次请求 Token 的时候就生成并覆盖 SESSION 中这个 Token 。同时保证每次拦截认证都是从 SESSION 获取这个唯一 Token ?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   880 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 22:46 · PVG 06:46 · LAX 15:46 · JFK 18:46
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.