V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
a1528zhang
V2EX  ›  信息安全

OAuth 认证机制下,如果 Token 被窃取了是否意味了账号被盗?有办法减轻影响吗?

  •  
  •   a1528zhang · 36 天前 · 1897 次点击
    这是一个创建于 36 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我是一个刚接触 OAuth 的新手,最近刚接入了 auth0 的身份验证。

    我们的产品是一个 web app ,然后后端作为 auth0 的客户端,保存了 auth0 返回的 access Token, refresh Token , 然后将 ID Token 加密后放到前端,作为用户的登录凭证。用户调用我们的后端 api 的时候直接验证一下 ID Token 是不是合法,然后就直接做用户操作了。

    然后我们为了用户体验使用了 refresh Token 来刷新用户的 ID Token ,当 ID Token 过期的时候就调用一下后端的 api ,后端再到 auth0 去请求新的 token ,然后更新 access Token, refresh Token ,再把新的 ID Token 返回给用户。

    我的疑问是,当用户的 ID Token 被获取了,在这套机制下就等于是账号全部失窃了,因为攻击者可以利用 refresh Token 不断刷新有效期。所以要如何解决这个问题?我的这个实现流程是否有问题?

    希望有经验的大佬解答一下,感激不尽
    第 1 条附言  ·  36 天前
    为了实现我们后端可以在检查到异常登录的情况后,主动让特定的 ID Token 失效。
    我准备在每次刷新 token 的时候(我们的场景是使用 refresh Token 来刷新所有的 token )生成一个随机字符串带到 ID Token 中,这个随机字符串对应一个 refresh Token ,同时也会存到后端。

    那么当 ID Token 被盗后,在 ID Token 有效期过后,如果攻击者想要刷新 Token ,那么后端会生成一个 新的随机字符串,同时会下发一个新的 带随机字符串的 ID Token 给攻击者。
    这样当原用户用原 ID Token 登录时,后端发现这个 token 中的随机字符串与数据库中的不一致,禁止用户登录,这样用户就能感知到自己的账号被别人登录了。然后这时候用户再通过主动刷新 refreshToken 的手段,生成一个新的 ID Token ,和新的随机字符串,这样攻击者手上的 ID Token 就会失效。

    这个方案属于是自己想的,受限于知识面可能业界有标准做法,如果有其他好的方法还请指教,感谢各位大佬
    第 2 条附言  ·  36 天前
    再次读了一遍文章 https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/。
    突然意识到我之前对 refresh token 的理解可能出了偏差,在 官方文档 https://auth0.com/docs/secure/tokens/refresh-tokens/get-refresh-tokens 中,我理解的是 refresh token 非常重要,因为他可以让用户一直处于登录状态,所以重要程度基本等同于密码,于是我认为保存在后端时合理的行为。

    于是我一直保持这样的想法,所以我会遇到一个问题: 我前端的用户仅仅依靠 ID Token 就可以证明身份,同时因为我启用了 refresh token 后,用户可以用过期的 token 来获取新的 token ,于是,攻击者也可以仅依靠盗取 ID token 来不断刷新从而永久登录。就算我的后端更新了 refresh token 也一样,因为攻击者仅靠 ID token 就可以验证身份,从而调用 api ,然后后端就会读取最新的 refresh token 再返回新的 token 。

    然后我从上面的文章中的描述发现这个描述:Malicious User manages to steal Refresh Token 1 from Legitimate User. 也就是说 refresh token 是会被盗取的,所以这里文章用户假设的是 refresh token 也被放在了前端。
    而我启用了 refresh token rotation 机制后,会让 refresh token 本身也会经常刷新,也就是说 refresh token 本身不会像上面说的密码那么重要了。
    于是乎,如果我将 ID Token 和 refersh Token 放一起加密算出一个新的身份 token ,使用这个 token 来鉴权和刷新 token ,就会有以下场景:
    1. 如果用户 token 被盗取,攻击者在 token 过期后想要续期,这样 token 传到后台,会解码出 ID token 和 refresh Token ,后端验证两者都没问题,于是签算出新的 ID Token 和 refersh Token ,再次加密为身份 token 返回前端,攻击者再次拿到新的合法的身份 token 。
    2. 被盗者现在不知道信息被盗取,还在用原来的身份 token 调用续期,后端解码出 ID token 和 旧的 refresh token ,然后因为 refersh token 已经无效所以拒绝操作,这样用户会发现登录失效,就能够知道账号失窃。
    3. 用户再次执行登录流程,后端再次刷新 ID Token 和 refresh token ,再次加密为身份 token 返回给用户,等攻击者使用第一步中续期的新的 token 请求时,后端会发现解码出的 refresh token 已经失效,从而阻止操作。

    整个流程其实跟我上一个 append 中的随机字符串的功能差不多,总结一下其实也就是双 token 的刷新机制。
    希望这个学习过程对其他刚接触 Oauth 的人有帮助,同时也希望有经验的大佬继续提提建议
    第 3 条附言  ·  34 天前
    更新一下最终的决定。
    根据上面说的,这样实现出来的结果是一个单点登录,但是我们的业务需要 app 和网页端同时登录,同时又不能把另一端给挤下去。那这个方案就没有办法了。

    最终回到了仅用 ID Token 加密后作为身份凭证发给前端,前端可以使用这个 token 请求我们的后端 api ,然后用户的 token 存放在后端避免泄露,这样基本登录流程没有问题,但是会有上面说过的,如果 token 一旦泄露,基本也就等于密码泄露了。

    但是就如 @javalaw2010 说的,鉴权和安全是两个场景,我们这个场景把上述流程做好鉴权目的就达到了,至于安全可以交给日志分析或者 auth0 提供的告警功能实现。一般来说,只要用户电脑没有被植入病毒,token 被盗的可能性还是不高的,所以最终我们还是考虑了用户体验更好的一个方案。
    28 条回复    2024-10-16 18:00:51 +08:00
    linzyjx
        1
    linzyjx  
       36 天前
    refresh token 一般也是有有效期的吧?不过就是比较长而已。
    wonderfulcxm
        2
    wonderfulcxm  
       36 天前 via iPhone
    可以 revoke 原 app 的 secret code ,我记得 Google 在 refresh token 时需要提供 app id 和 app 的 secret 。
    MillaMaxwell
        3
    MillaMaxwell  
       36 天前
    refresh token 也有有效期,每次获取新的 refresh token 也需要进行验证就可以了
    patrickyoung
        4
    patrickyoung  
       36 天前 via Android
    看 auth0 文档去,okta 的文档写的很清楚…
    a1528zhang
        5
    a1528zhang  
    OP
       36 天前
    @linzyjx 一般来说 refresh token 的有效期在几个月,而且如果我使用 refresh token rotate 的机制,会导致后端总是会存放一个有效的 refresh token ,这样如果攻击者用盗来的 ID token 调用我后端的 api 来刷新 token ,就会一直有权限了
    a1528zhang
        6
    a1528zhang  
    OP
       36 天前
    @patrickyoung 文档翻了很多次了,可能我理解有误,没找到能解答疑问的内容
    RightHand
        7
    RightHand  
       36 天前 via Android
    token 被盗意味客户端被破解,无解
    a1528zhang
        8
    a1528zhang  
    OP
       36 天前
    @MillaMaxwell 我们用了 refresh token rotate 的机制,目的是为了实现免登录,会导致 refresh token 会不断更新,这样 refresh token 就总是会有一个有效的
    javalaw2010
        9
    javalaw2010  
       36 天前
    这套流程是没问题的。鉴权跟账号安全是两个场景。

    首先你要保证信道的安全,保证 token 的传输过程中不会被截获。那么此时 token 被截获的场景要么就是用户的设备被黑客控制了,要么就是你们的服务端被黑客控制了,而如何防止这两种情况的出现又都是别的话题了。

    其次,refresh token 应该是一次性的,用完就丢了。

    然后,你可以做一些风控,来弱化盗号的影响,比如限制登录的设备个数,举例限制 2 台设备,第 3 台设备登录后,要主动失效签发给第一台设备的所有 token 。比如 token 和 ip/地区/设备指纹绑定,不符合则认为是无效的 token 。比如引入二因素认证等等。
    a1528zhang
        10
    a1528zhang  
    OP
       36 天前
    @wonderfulcxm 那这样的话我们一个终端用户的 token 泄露就会导致所有的终端用户都需要重新登录一遍才行了。
    cheng6563
        11
    cheng6563  
       36 天前
    做单会话登录,就是用户登录会把其他会话都挤掉。
    iyiluo
        12
    iyiluo  
       36 天前
    定期失效+设备绑定+ip 绑定,看你想做到什么程度
    anonydmer
        13
    anonydmer  
       36 天前
    楼主你用错了吧,ID Token 这个 token 是不能用来访问 api 的,只是用来返回用户信息的; 调用 api 的仍然是 access token ,而它是的有效期是很短期的;
    a1528zhang
        14
    a1528zhang  
    OP
       36 天前
    @javalaw2010 明白了,感谢解答。基本的安全策略比如 https ,http only 的 cookies 等我们还是实现了的。
    同时我们的 refresh token 是每用一次就会更新一个新的存在后端。

    风控手段是必须的我们准备以后会做。

    其实我就是担心设备被黑客控制了以后,等于是用户账号永久失窃,而且连防御的手段都没有(除了刷新 client 的 app id 和 secret )。因为黑客可以用 token 不断刷新有效期。

    主要是刚接触这个领域,怕还有更好的实现方式我不知道。
    a1528zhang
        15
    a1528zhang  
    OP
       36 天前
    @cheng6563 这个我考虑过,但是如果被窃的用户长时间不登录就没法挤掉
    a1528zhang
        16
    a1528zhang  
    OP
       36 天前
    @iyiluo 嗯现在看来设备 ip 绑定是比较保险的做法,以后会做。因为我们做了 token 刷新的机制所以定期失效的手段没用了
    a1528zhang
        17
    a1528zhang  
    OP
       36 天前
    @anonydmer 是的,我理解 access token 是用于访问我们申请权限的服务的 api ,比如我们接入的 github 的账号,然后我们拿到的 access token 也仅用于访问 github 的 api 。

    ID Token 我是用作一个登录凭证,证明这个用户登录成功了,那么他可以调用我们服务端的 api 而不是 github 的 api 。所以我理解 ID Token 我应该没有用于请求,而是只用于身份证明。

    那么这个身份证明被盗用后,我们后端是否有手段可以让这个特定的身份证明失效呢?
    patrickyoung
        18
    patrickyoung  
       36 天前 via Android
    id token 不是鉴权用的…只是标识身份…

    https://auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
    patrickyoung
        19
    patrickyoung  
       36 天前 via Android
    楼主要细看一下 authorization 和 authentication 的区别,然后 oauth2 和 oidc 规范,然后去看 a0/okta 的 security feature ,所有的问题的答案都在这里
    a1528zhang
        20
    a1528zhang  
    OP
       36 天前
    @patrickyoung 是的,我把 ID Token 加密后作为身份认证,来访问我们自己的后端 api ,并没有访问授权方(比如 github )的 api 。所以应该不涉及 api 的调用,如果 我不用 ID Token 那么我也要自己生成一个 Token 给用户来证明他已经登录了。

    所以当这个 token 被盗取了后,攻击者和用户都可以使用同一个 token 来调用我们后端服务 api ,然后我们的 api 中包含一个刷新 token 的 api ,所以攻击者和用户就都能不断获取新的 token 来保持永久登录。
    a1528zhang
        21
    a1528zhang  
    OP
       36 天前
    @patrickyoung 感谢 我再去翻一下
    cat1879
        22
    cat1879  
       36 天前
    1 、refresh token 有时效性,一般都是两小时失效;
    2 、token 要用 app id 和 app secret 来换;
    3 、token 应该要结合单个用户再获取一个认证,用这个认证给个用户做授权;
    所以理论上 id secret token 这三个都在后台跑,token 在不同站点间交互可能会有泄露;用户认证授权有可能泄露但只对单个用户; 按你的描述我感觉你是不是少了一级,你是直接把 token 当公共参数丢给所有用户作为授权了
    a1528zhang
        23
    a1528zhang  
    OP
       36 天前
    @cat1879 我参考的参考他们的文档 https://auth0.com/docs/secure/tokens/refresh-tokens
    1. refresh token 的时效性一般来说是比较长的,但是 id 和 access token 的时效会很短,但是如果我使用 refresh token rotate 机制的话 refresh token 的存在时间也会比较短
    2. 我这边 token 只在第登录验证的时候会使用 appid 和 seceret 交互,但是之后可以使用 refresh token 进行刷新,就不需要 secret 了
    3. 确实理论上我应该把 token 全部放在后端,然后我的后端再做一个单独的授权 token 给我的前端。但是这个行为本质上是给前端一个唯一的身份证明,所以我选择吧 id token 加密后生成一个新的 token 来承担这个功能,这样我也不用做签算机制了,少点工作。
    fredweili
        24
    fredweili  
       36 天前
    你说的这个算法类似 PKCE
    token 可以在 server 端 invalidate 的,看 server 端的检测机制
    a1528zhang
        25
    a1528zhang  
    OP
       36 天前
    @fredweili 是的,其实就是 PKCE 了,但是 OAuth 签发的 Token 应该是无法主动失效的: https://auth0.com/docs/secure/tokens/revoke-tokens

    不过我们重新获取 refresh Token 后,会让旧的 refresh Token 失效;由于我自己签算的 Token 是使用 ID Token + refresh Token, 也就达到了让前端 Token 失效的效果
    Dlin
        26
    Dlin  
       35 天前
    access Token 就是拿来认证的,干嘛还要搞个 ID Token 。你这个问题我曾经也想过,任何认证方式在泄露 token 后都存在用户信息的不安全。你的想法是持票人的实现办法,可以做到单设备登录,用户可以感知被挤了下去了,算是加强了一些。
    Dlin
        27
    Dlin  
       35 天前
    refrash_token 只能使用一次,刷新后返回新的 refrashToken 。
    a1528zhang
        28
    a1528zhang  
    OP
       35 天前
    @Dlin 我理解 access token 用来访问的是授权方(比如 github 的 api ),我这里其实需要的是访问我们自己应用的后端,只需要一个登录成功的证明就行了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5369 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 08:55 · PVG 16:55 · LAX 00:55 · JFK 03:55
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.