每月一争, 为什么 JWT 这么多诟病, 什么下线设备登录 JWT 不是很容易解决吗? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
SethShi
0.01D
V2EX    问与答

每月一争, 为什么 JWT 这么多诟病, 什么下线设备登录 JWT 不是很容易解决吗?

  •  
  •   SethShi 2023 年 6 月 19 日 6573 次点击
    这是一个创建于 946 天前的主题,其中的信息可能已经有所发展或是发生改变。

    JWT 的基本就不再过多阐述

    思考的问题前提: 比如我们面对一个千万级用户的系统. * 客户端需要和服务端交互, 假设 50% 的接口需要校验 * 10% 的接口需要获取个人信息

    • 如果是 token 那么代表着 50% 的流量都需要去查 db(任何 db 都可以)
    • 如果用 jwt 我可以减少到 10%, 甚至更少

    至于常见的 JWT 诟病解决方案

    • 两个小时没操作自动掉线
      • 分钟级别过期 access_token, 2h 过期 refresh_token, 完全可以做到
    • 黑名单, 强制下线功能
      • 分钟即被过期 access_token, token 里包含信息 {uid: xxxx, token_version: xxx}
      • 修改密码, 或者强制下线, 修改 db 里的 token_version
      • 当要刷新 token 的时候, 校验客户端 token 里的 token_version 是否等于 db 里的 token_version, 不等于强制下线.(是在每一次去校验还是在刷新 token 的时候去校验都可以)
    • 单台设备下线, 这个我认为是业务的逻辑
      • 分钟即被过期 access_token, token 里包含信息 {uid: xxxx, device_id: xxx}
      • 当要刷新 token 的时候, 校验客户端黑名单 db 里是否包含这个 device_id

    为什么要用 JWT ?

    • 使用 token 会有千万级用户请求 * (存 token + 查 token redis)... 用户名,xxx 存储,

    下线, 黑名单为什么不直接存储 token ?

    • token 可能千万级别, 但是黑名单保守估计不会超过 1w, 并且我可以把黑名单 token 有效期设置成 refres_token 的有效期(极大的减少了黑名单 token 的数量), 这样子当黑名单过期了, 那么 refresh_token 也不能再使用了(如果去颁发, 在黑名单中也颁发不了)
    • 如果一个用户可能登录 5 个设备, 那么存储的 token 量就是 1000w * 5, 而 jwt 就是 0 ~ 黑名单数量
    第 1 条附言    2023 年 6 月 19 日

    使用网站: http://www.redis.cn/redis_memory/

    1000w key 占用量

    key 为 32 位字符串, value 如下(我按 128 个字节算):

    { "id": 999999, "nickname": "seth", "vip_exp": 1687153583, "avatar": "https://wwww.baidu.com/a.png" } 

    网站得出的结果是: 2.4G,

    55 条回复    2023-10-07 17:50:07 +08:00
    SethShi
        1
    SethShi  
    OP
       2023 年 6 月 19 日
    还有 access_token 一个就够了, 为什么还要有 refresh_token, 除了官方说的安全之外, 就是可以作为两个时间点去使用.
    access_token 每次都要校验, 可以简单的校验, refresh_token 使用的频次比较少, 这时候可以做更完整的校验.
    LeegoYih
        2
    LeegoYih  
       2023 年 6 月 19 日
    用了 JWT 还要存 token 到服务端,每次还要查版本号和黑名单,不是脱裤子放屁吗?
    用户权限之类的更新了怎么办?难道让用户退出重新登录一遍吗?
    查一次缓存就能解决的事情,不要太小看 Redis 的性能了。

    而且 JWT 不是 100%安全的: https://github.com/yihleego/jwtcrack
    oldshensheep
        3
    oldshensheep  
       2023 年 6 月 19 日
    你这个都依赖 **分钟级别过期 access_token**

    你加个 token_version 那每次刷新 jwt 就要查询数据库,而且你刷新的非常频繁,这不又和原来一样
    同样的还有检查 device_id 要查询数据库
    giter
        4
    giter  
       2023 年 6 月 19 日
    我选择把 accessToken 与 refreshToken 存在 localStorage 中。既然要 JWT ,那就贯彻到底,给服务端彻底减负。
    nomagick
        5
    nomagick  
       2023 年 6 月 19 日
    jwt 真正的用途在于验证其他站点的用户和 token ,而不是自己站点的。
    不要为了用而用。
    ztxcccc
        6
    ztxcccc  
       2023 年 6 月 19 日
    别查库了谢谢
    amlee
        7
    amlee  
       2023 年 6 月 19 日
    jwt 的设计初衷就是要无状态,如果你的需求必须要求有状态,那么 jwt 的设计不适合你,为什么不用现成的 session 方案?
    这本质上是一个需求和设计是否匹配的问题,搞清问题本质,跟技术优劣,或者说能否给技术打补丁以满足需求没关系。
    板砖和锤子都能敲钉子,你非要说我能打磨一个完美的板砖去敲钉子,那我只能说脑子有包
    gogola
        8
    gogola  
       2023 年 6 月 19 日
    @LeegoYih #2
    暴力破解这玩意,不好说。
    CodeCodeStudy
        9
    CodeCodeStudy  
       2023 年 6 月 19 日
    token 存 redis 不就完事了?
    emric
        10
    emric  
       2023 年 6 月 19 日
    我这边用 last_login ,日期不相同就拒绝。
    感觉你说的大部分问题,都是修改以下 last_login 就行。
    SethShi
        11
    SethShi  
    OP
       2023 年 6 月 19 日
    @LeegoYih 我就问一下, 你使用 token 权限更新了怎么办? 这个和 jwt 没关系. 你权限更新其它方式该怎么做就怎么做. 不是我小看, 而是如果还不消耗存储, 不消耗 io 的方式为什么不讨论呢?

    @oldshensheep token 的方式, 按用户级别来说, 每次请求来都查 redis, 并且 jwt 包含千万级别的用户数据, 而刷新的时候查询, 减少了 90% 的请求, 并且 redis 的存储只用非常少
    @giter 没错了, 那肯定都要存客户端的, 和我说的没区别
    @nomagick 你在哪看的定义? 你说的是 oauth2 吧
    @ztxcccc 举个栗子
    @amlee 又来一个不看解决方案, 上来就说不查库的
    SethShi
        12
    SethShi  
    OP
       2023 年 6 月 19 日
    @CodeCodeStudy 话说不看场景直接就存?
    @emric 原理方案差不多的, 总是有些人在说为什么不存 redis 呢. 所以要讲这个东西.
    ellermister
        13
    ellermister  
       2023 年 6 月 19 日 via Android
    那每个 jwt 请求到服务端不都是要查一下有没有在 redis 黑名单列表里,千万级的用户也是千万级的查询啊,jwt 只是把 redis 存储大小改了,没改变查询次数。
    @seth19960929
    SethShi
        14
    SethShi  
    OP
       2023 年 6 月 19 日
    @LeegoYih 我看了两眼你这个代码, 上来来个碰撞代码就说不安全, 能拿出具体的文献出来吗.
    按你这样子说, 世界上没有安全的加密, 我只要写一个碰撞代码, 它们安全只是我现在没有碰撞到, 不代表我做不到.
    SethShi
        15
    SethShi  
    OP
       2023 年 6 月 19 日
    @ellermister 嗯嗯, 这是一个讨论帖子.
    两种方案:
    1. 只在刷新 token 的时候才查询是否在黑名单, 请求可以降低非常多, 有个问题就是会有延迟, 比如 5 分钟刷新一次 token, 那么就会加入后有 5 分钟延迟. 如果是部分场景完全可以用这种(只要 app 左右逻辑处理, 能及时失效, 如果有人窃取了 token, 那么它也只能使用 5 分钟, 到期了刷新就会得知被加入黑名单)
    2. 大小还是差别很多的. 往 redis 里存 32 位字符串 key 1000w 个看看. 我等会给一个答案
    8355
        17
    8355  
       2023 年 6 月 19 日
    只用 jwt 没有防御措施绝对是错误的行为

    单 ip 连续请求错误 token 值为不可信行为,这是风控策略应该做的事
    在敏感操作必要时进行密码检查

    仅传入 token 就给予最大用户操作权限本身就不合理
    shakaraka
        18
    shakaraka  
    PRO
       2023 年 6 月 19 日
    你说得对
    Masoud2023
        19
    Masoud2023  
       2023 年 6 月 19 日
    你文中的黑名单和单台设备下线,归根结底都是要去查库,那用 jwt 和不用 jwt 做 sessionid 有什么区别么
    SethShi
        20
    SethShi  
    OP
       2023 年 6 月 19 日
    @LeegoYih
    只要循环结束, 我一样能破解呀.

    for ($i = 1; $i < pow(32, 16); $i++) {
    $key = genKey($i);
    $result = openssl_decrypt(hex2bin('1fbf2605f954fad3ba18115000735aee'), 'aes-128-cbc', $key, 1, '0000000000000000');
    }
    SethShi
        21
    SethShi  
    OP
       2023 年 6 月 19 日
    @8355 这个是,敏感操作已经和是 token 还是 jwt 没关系
    @wunonglin 你说得对
    @Masoud2023 sessionid 存取 1000w 数据, 没有哪家的黑名单有 1000w 吧? 并且我可以在刷新 token 的时候再查 redis, 而不是每次请求.
    nothingistrue
        22
    nothingistrue  
       2023 年 6 月 19 日   1
    JWT 全称是 Javascript Web Token 。它与传统 Token 的唯一区别就是它采用非对称加密的方式。这有两个好处:一,可以自验证;二,可以携带更多的信息(传统 Token 采用 MD5 、SHA1 这样的散列加密方式,这是无法自验证和反解码的)。

    JWT 只是一个高级的 Token 而已,并不是会话 /用户跟踪方案,在会话跟踪方案上讨论 JWT 的优劣根本毫无意义。而 2 楼这种比较,是想把 Token 、会话跟踪、认证(用户)、授权(权限)糅合到一起去考虑,神仙看了都会躲。

    楼主说得实质上是 Token 采用 JWT 的会话跟踪方案。在会话跟踪方案上,用 JWT 代替普通 Token 、SessionId 等,除了开发难度略增之外,是只有好处没有坏处的。
    ellermister
        23
    ellermister  
       2023 年 6 月 19 日
    @seth19960929
    5 分钟的延迟并不能满足常见的需求

    比如,假设我登录了一个用户,拿到 jwt token ,此时点击注销,服务端加入黑名单。然后登录过程中 token 泄露或者前端只是选择性从 localstorage 删除,本质意义上服务端还存在。

    因为黑名单查询不是即时的,那我就可以在服务端通过改密码方式修改用户的密码,然后重新登录,又可以拿到一个新的 token 。(当然有人会说改密码要二步验证之类的,这是另外一种做法和产品需求范畴了)

    > 总之在有效期内仍可以做很多事情,或者不能使其立即下线,就有很多的安全隐患。

    有人说你这个强依赖于状态, 不能用无状态做, 那可能确实。但目前的大多数产品提出的需求或者我做过的产品,基础都要满足我所说的即时性。(注销了这个 KEY 就无效了, 在任何层面上他能操作能认证就是一个 BUG )

    我看了楼上,思考过确实对用户的系统不适合做 jwt ,只适合真正意义上的无状态需求(下载站鉴权、cdn 鉴权之类的、这些太少了,我基本没接触过)。而只适合所谓第三方鉴权的,服务对 服务之间的鉴权。


    虽然我也在用 jwt 、而且每次请求都必命中 redis 查验黑名单,99%请求命中一次数据库。
    大部分接口都需要获取用户的自身信息,为什么用户信息不全部存到 jwt ?因为不及时不可靠。

    有人说你这样用 jwt 比传统 token 都麻烦,存储的还多几倍的黑名单还基本都要查 redis 、数据库,何必 jwt ,但 jwt 能够方便对接前端及第三方这种共识特性,迫使选择了 jwt 。

    总之一句话,我不觉得 jwt 很好,我也觉得一身诟病。我选择 jwt 唯一的技术优势就是它只存黑名单,而传统 token 存白名单。能省点存储就省点吧。
    fivesmallq
        24
    fivesmallq  
       2023 年 6 月 19 日
    https://jwt.io

    JSON Web Token (JWT)
    @nothingistrue
    QlanQ
        25
    QlanQ  
       2023 年 6 月 19 日
    我是彩笔

    如果多端,类似 pc + 小程序 + app + h5 想要统一一种认证方式 jwt 不是唯一选择吗?

    如果多服务系统,比如 主站 Java ,活动页 php ,im go 这种,用 session 不是更麻烦吗?
    hsfzxjy
        26
    hsfzxjy  
       2023 年 6 月 19 日 via Android
    @nothingistrue 是 JSON Web Token
    mxT52CRuqR6o5
        27
    mxT52CRuqR6o5  
       2023 年 6 月 19 日
    我十分怀疑你的基于 jwt 的登录业务设计的是否正确
    我没看明白强制下线功能为啥可以不是每次请求都请求 redis ,那你咋知道当前的 redis 中的 token_version 是多少
    是因为你的强制下线功能不是实时的,会有 5 分钟延迟吗?
    mxT52CRuqR6o5
        28
    mxT52CRuqR6o5  
       2023 年 6 月 19 日
    @ellermister #23
    +1 ,安全的不完全等于完全的不安全,没见过哪家登录系统强制下线还有 5 分钟延迟的,5 分钟足够把账户里的钱偷光了
    hongfs
        29
    hongfs  
       2023 年 6 月 19 日
    @QlanQ JWT 并不是唯一,而且可能会让复杂度变高。我们现在的多端方案,同一个接口(也可以不同接口),带上不同端的 type 过来,因为不同 type 提交的内容存在差异,后端返回的是一个随机生成的 Token ( 40 位字符串)存放于 Redis ,可以做到每个端都可以有一个在线,可以做到当修改密码或者拉黑时全部端都可以一起下线。
    mxT52CRuqR6o5
        30
    mxT52CRuqR6o5  
       2023 年 6 月 19 日
    人家支付宝微信不比你的系统用户多多了,也没见人家用牺牲安全性的方法(强制踢下线有 5 分钟延迟)去提高系统性能
    QlanQ
        31
    QlanQ  
       2023 年 6 月 19 日
    @hongfs 我不太能区分,jwt 和 token 的区别,在我看来原理和作用差不太多,只是说 jwt 有一个标准的规则,
    如果有这种黑名单,强制下线的需求,我会将 token 存在 redis 中

    然后多端登录,就是 redis ,key 的规则问题了,同样可以实现修改密码登录失效的需求
    justfindu
        32
    justfindu  
       2023 年 6 月 19 日
    @QlanQ 没有什么区别, 因为它叫 JSON Web Token
    mmuggle
        33
    mmuggle  
       2023 年 6 月 19 日   1
    JWT 本身无状态,黑名单功能就是强行有状态了,但是只是针对黑名单 token ,总体来说,还是比传统 token 强一点。
    SethShi
        34
    SethShi  
    OP
       2023 年 6 月 19 日
    @nothingistrue 说的在理

    @ellermister 黑名单下线这个问题, 其实是一个取舍问题, 就是如果不能接受刷新 token 这段等待时间的话, 没什么好说的了, 就是每一次查询都走黑名单查询(其实很多系统第一次打开 app 或者重新登录才去校验), JWT 确实能省很多内存
    @QlanQ 嗯嗯, token 也是一种方式, 就是随机字符串存到 redis 映射出 uid, 现在说这种存 redis 和 jwt 哪种更好.
    @mxT52CRuqR6o5
    SethShi
        35
    SethShi  
    OP
       2023 年 6 月 19 日
    @mxT52CRuqR6o5 你这个话说的, 如果是和钱相关, 那和 JWT 有什么关系, 这时候不应该发短信, 人脸识别一堆校验, 这时候全面风控早就拦截好了, 你用 JWT 做这个事不合适.
    SethShi
        36
    SethShi  
    OP
       2023 年 6 月 19 日
    @QlanQ JWT 只是一个标准, 也是 token 的一种. JWT 黑名单用 redis 存的做法, 大家就会觉得为什么不直接把 token 直接存 redis 里.
    @mmuggle 1
    hongfs
        37
    hongfs  
       2023 年 6 月 19 日
    @seth19960929 #36 对大多数业务来说,其实 Redis 的成本也是很低的,比如你计算的 1000W key 那会有 2.4G 的一个内存使用,1000W 又不是一直在线的用户,所以内存的实际占用也是非常小的。即使你的用户在线率非常多,你需要对 Redis 进行集群化的资源成本也是非常低的。

    如果非要 JWT ,其实可以做成两阶段的,用户请求先 JWT 验证,然后 sha1 等方式减少存储空间,把 sha1 的值去 redis 里面查。
    QlanQ
        38
    QlanQ  
       2023 年 6 月 19 日
    @seth19960929 所以我说我分不清 jwt 和 token ,明明都是一个字符串而已,在我的认知里面,就是一个东西
    SethShi
        39
    SethShi  
    OP
       2023 年 6 月 19 日
    @hongfs 如果 token 有效期一个月, 1000w 肯定有的. 如果是抖音那种日活亿级别的, 那得多少内存存 token
    @QlanQ 普通的 token 无任何有效信息, 只是一个唯一值, JWT 的话可以通过 秘钥 从 token 里解析出 uid 之类的, 这样子有些场景只有 uid 就不用查数据库.
    daimubai
        40
    daimubai  
       2023 年 6 月 19 日
    如果存库的话,那确实 jwt 和普通的字符串没区别。因为最终都会查库的,就算不解析 token ,也能在库中查到当前用户的信息
    akira
        41
    akira  
       2023 年 6 月 19 日
    那个,你们家用户都是千万级的了么。。。
    GeruzoniAnsasu
        42
    GeruzoniAnsasu  
       2023 年 6 月 19 日   4
    看了半天在吵什么……

    > 普通的 token 无任何有效信息, 只是一个唯一值, JWT 的话可以通过 秘钥 从 token 里解析出 uid 之类的, 这样子有些场景只有 uid 就不用查数据库
    原来还就是车轱辘话翻来覆去讲,说到底 OP 发现的「优势」也就是 jwt 带信息,随机 token 不带信息而已,这不就是废话……


    > 至于常见的 JWT 诟病解决方案
    OP 的这段发散了这么多,加上他楼下的回复,其实就一句话: 退化成普通 session / oken 方案


    > 为什么要用 JWT ?
    > - 使用 token 会有千万级用户请求 * (存 token + 查 token redis)... 用户名,xxx 存储,
    文不达意。 根本原因其实就是 jwt 携带的信息可以短路一部分查库判断而已




    我来替 OP 总结一下他的看法:
    1. 在最坏场景下,jwt 退化成普通 session/token 方案,此时主要缺陷是 jwt 数据量引入的内存浪费,但即使在极大并发量条件下 redis 的性能也完全够用,所以可以忽略不计
    2. 在典型场景下,jwt 自带 session/token 方案需要查库才能获得的关联信息,如用户 id 等,在这些场合下能节省大量数据库连接资源


    所以这不就是什么「初识」、「入门」文章里就提到的东西么……
    hyperbin
        43
    hyperbin  
       2023 年 6 月 20 日 via Android
    @LeegoYih If you are very lucky or have a huge computing power ,相当于说国库不安全,如果你有灭国级的实力的话
    SethShi
        44
    SethShi  
    OP
       2023 年 6 月 20 日 via Android
    @daimubai 查一次和一万次能一样吗?这样子说的话,干嘛架构这么多,干嘛用这么多缓存,直接查数据库了
    @GeruzoniAnsasu 一直强调更多的怎么去解决 jwt 的问题,我后面说的这么多黑名单,刷新的机智话说都无视了吗
    @akira 这是一个讨论贴,可以直接 block
    QlanQ
        45
    QlanQ  
       2023 年 6 月 20 日
    @seth19960929
    嗯嗯, token 也是一种方式, 就是随机字符串存到 redis 映射出 uid, 现在说这种存 redis 和 jwt 哪种更好.

    本质都是一个字符串是吧,jwt 、token 傻傻分不清
    SethShi
        46
    SethShi  
    OP
       2023 年 6 月 20 日
    @QlanQ 对, json web token 从名字看出来它也是一种 token
    daimubai
        47
    daimubai  
       2023 年 6 月 20 日
    @seth19960929 #44 查一万次是怎么得来的?
    daimubai
        48
    daimubai  
       2023 年 6 月 20 日
    @GeruzoniAnsasu 他都不知道自己在说些什么
    chendy
        49
    chendy  
       2023 年 6 月 20 日
    JWT 适合自家服务之间通信,网关授权之后带上 JWT 请求后续服务,后续服务不需要再查用户权限之类的东西,降低用户中心服务的负载
    客户端还是适合最朴素的 token 模式( cookie 里的 sessionid 其实也是一种 token )
    SethShi
        50
    SethShi  
    OP
       2023 年 6 月 20 日
    @daimubai 1000w 用户都请求有没有 1000w 次, 什么怎么得来的.
    @chendy 就网上的帖子来看, JWT 很多时候还是给客户端用的, 而不是你说的内部服务
    v2Geeker
        51
    v2Geeker  
       2023 年 6 月 21 日 via iPhone
    我们公司是全线用的 OIDC 那一套,当然也就用了 JWT 。这套像介于传统 Session 和 无状态 JWT 之间,服务端和客户端双赢。
    chenlins
        52
    chenlins  
       2023 年 7 月 4 日 via iPhone
    @chendy 赞同,我觉得这是 JWT 的最佳实践
    8rmEHZ8WhVHVOb0E
        53
    8rmEHZ8WhVHVOb0E  
       2023 年 10 月 7 日
    你说的方案并没有节省 Redis 查询次数,只省略了 redis 内存而已,但是:
    Jwt 方案 token 长度往往有数百个字符串,并且根据 Payload 内容的增多而变的更长,每次请求都会把这些东西带上。
    而类 session 模型的 token ,token 字符串长度往往控制在几十个字符串长度。

    假设每个平均每个 jwt token 长度 250 个字符串,而 session 模式的 token 长度 50 个字符串,那么每个请求就多了 200 个字符串,如果你的网站每秒 1 万 QPS ,你的服务器带宽峰值大约会提高 40Mb

    40Mb 带宽明显比几 G redis 内存要贵鸭
    SethShi
        54
    SethShi  
    OP
       2023 年 10 月 7 日
    @xiaomada 服务器的带宽一般只计传输给客户端的, 不计客户端上传
    SethShi
        55
    SethShi  
    OP
       2023 年 10 月 7 日
    @xiaomada 请看原文, 只有当 refresh 的时候才去查询 redis. 至少减少 99% 的查询 redis 次数.
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     967 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 39ms UTC 19:54 PVG 03:54 LAX 11:54 JFK 14:54
    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