请教大家一个关于 RESTFul 认证的问题:如何保证一个用户只能有一个 JWT( Json Web Token) - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mynameisny
V2EX    问与答

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

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

    本人菜鸟,目前在开发几个 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     Solana     3507 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 10:40 PVG 18:40 LAX 03:40 JFK 06:40
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86