有后端同学给我讲一下判断是否登录吗? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
firhome

有后端同学给我讲一下判断是否登录吗?

  •  
  •   firhome 2020 年 2 月 3 日 6966 次点击
    这是一个创建于 2267 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我是前端,利用假期时间在家想学习 后端知识。

    目前遇到的问题就是关于登录这块。

    就我目前来说我知道的。

    1.登录后,账号,密码(加密)放 cookie 里 [不安全不推荐]

    2.登录后生成一个 sessionId 放到客户端 cookie 里。只放 session 的话 不保险(资源和重启服务),所以要 session + Redis

    3.jwt ???

    这个 jwt 我就有点云里雾里 不太懂,我自己理解下来 就是 把自定义的文本信息加密(类似图片转 base64 编码)后,把加密后的 key 给客户端,然后客户端每次访问 header 里带这个 key 过来就行。key 里加密的时候定义过期时间,服务端拿到后解密,所以服务端也不用存任何东西。

    但是我的疑问来了。那么这个 key 是不是任何人拿到了都可以通过验证?服务端提前生成了 key,所以没办法在过期日期前让 key 失效? 所以失效的办法只有更改服务端的加密算法,让所有的 key 都失效而达到目的?


    以上是我的疑问和理解,不知道是否正确。希望后端同学帮忙指导一下,谢谢~
    29 条回复    2020-02-29 17:01:58 +08:00
    justfindu
        1
    justfindu  
       2020 年 2 月 3 日
    如果使用有状态的 jwt, 是可以加入到 blacklist 使它失效的.
    justfindu
        2
    justfindu  
       2020 年 2 月 3 日
    然后服务端每次会先验证有效期,再通过 blacklist 验证是否存在.
    LengthMin
        3
    LengthMin  
       2020 年 2 月 3 日
    你的理解是对的。
    JWT 中是通过后端设置的一个密钥生成的,更改密钥的值就可以使其他的 Token 验证不通过。

    关于提前失效,每次把分发的 token 存到数据库,服务器用代码各种判断也能实现
    imn1
        4
    imn1  
       2020 年 2 月 3 日
    先学走,再学跑
    先别管安全什么的,搞清楚登录后写什么变量,写到哪里,最简单那种
    jswh
        5
    jswh  
       2020 年 2 月 3 日
    你所谓的 jwt 加密后的 key 就是数据本身。不用其他的东西,简单的办法就是直接把过期时间写到数据里面,后端读取解密之后,判断一下是否过期就行了。
    luopengfei14
        6
    luopengfei14  
       2020 年 2 月 3 日 via iPhone
    简单点的登陆:客户端登录成功后,后端会返回给前端一个大的随机字符串,可以叫 token。后端和前端都要保存这个用户的 token 或者 sessionid。以后客户端都会拿这个用户 ID 和 sessionid 传给后台,后端检查这个用户 ID 和 sessionID 是否有效。
    eason1874
        7
    eason1874  
       2020 年 2 月 3 日   2
    传统 session 在多服务器之间有一个同步问题,用户登录需要在全部服务器同步,不然用户二次访问连接到其他服务器的时候就找不到状态。

    多服务器都保存比较浪费资源,JWT 就是为了解决这个问题,全部登录信息明文返回附带一个签名,其他服务器不保存登录状态,只要使用登录信息来签名,得到结果跟前端发回签名一致就认为有效。

    但 JWT 也带来一个新问题,因为无状态,不能主动废弃登录信息,只能等到过期日期才失效。

    所以,具体怎么用,自己选择吧。有的 JWT 是只能获取登录状态和普通信息,关键信息还得二次验证,有的 JWT 是有效时间特别短,频繁签发。
    luopengfei14
        8
    luopengfei14  
       2020 年 2 月 3 日 via iPhone
    一般情况下后台只保存用户的最新 sessionID,旧的会被覆盖。既是前端传旧的 ID,服务端检查出 sessionID 与最新的不匹配,返回错误。
    szvone
        9
    szvone  
       2020 年 2 月 3 日
    前段时间自己写的一个鉴权逻辑(参考 jwt ):

    账号密码登录,服务端验证后,返回 token,前端验证后,将 token 缓存,并在每次发起 http 请求的时候放在协议头里面请求


    token 分为三段 账号:失效时间戳:校验码


    校验码的计算逻辑就是账号加密码加失效时间戳然后 MD5,可以加点其他的固定的字符混淆

    然后服务端判断逻辑就是先判断时间是否过期,在判断校验码是否正确,最后从数据库取出来用户数据


    优点是:可控(登录是否失效)
    缺点是:每次都需要从数据库拿数据(可以改成从 Redis 拿)
    adekyou06
        10
    adekyou06  
       2020 年 2 月 3 日   3
    # JWT

    公司有个项目需要更换验证方式,恰好我在负责,因此学习了一下如何实现 JWT Authorization。

    JWT 是用来替换 Session 的一种解决方案。因此它不能有大量的计算,必须尽可能的少计算;也不能存储私密的内容。

    在设计 JWT 时,需要分成 header、payload、signature 三部分。这三部分都是在后端计算,返回给前端的只是一个 Token 字符串。

    header 存储 JWT 元数据。具体而言就是:JWT 是用什麽算法加密的。
    ```ruby
    {
    "alg":"sha256",
    "typ":"JWT"
    }
    ```

    payload 存储具体数据。比如登录用户的 ID。记住,因 payload 默认不加密,仅做 base64 编码,了安全考虑,尽量不要存太私密的东西。
    ```ruby
    {
    "iss":"abc.com", //签发人
    "exp":time()+600, //过期时间,10 分钟后
    "nbf":time()+2, //生效时间,2 秒后
    "iat":time(), //签发时间
    "uid":uid //userid 用户 ID
    }
    ```

    上面的例子里,最重要的是 exp 和 uid。exp(过期时间) 如果不做限制,一但 JWT 泄漏,任何人都可以用它来登录,永远有效。uid(用户 ID) 是我们用来替代 session,识别用户的信息,也是我们这个 payload 存在的目的。

    signature 是签名。它用于保证前两个数据没有被人改过。将前两个数据(header, payload)的 base64 编码 用 "." 连接起来,再进行加密。也仅仅在签名的生成上,用了一次加密算法。
    ```ruby
    HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret)
    ```

    secret 是我们自定义的密钥。

    在上面的三部分生成完毕之后,用 "." 连接起来,传给前端。以后每次请求,都要使用 JWT 来验证身份。因 payload 和 header 都不做加密,因此前端传来时,可以反 base64 解开,看信息。最后,再用 签名 验证一下信息是否是造的就好了。
    adekyou06
        11
    adekyou06  
       2020 年 2 月 3 日
    是我关于 jwt 的笔记,楼主可以看看
    awm47
        12
    awm47  
       2020 年 2 月 3 日
    歪个楼,我认为用 jwt 作为用户 token 有点不太合适。具体网上有一大堆文章讲为啥不合适,我就不多 BB 了。

    https://www.jianshu.com/p/af8360b83a9f (随便找的)

    我的做法是登录后直接用 AES+Data({account: xxx, version: timestamp})来生成一个密文作为 cookie,同时保存一个 session。重启服务的话,session 失效就根据 cookie 重新生成一份 session。
    hyy1995
        13
    hyy1995  
       2020 年 2 月 3 日
    看你以前的帖子,问“登录拦截”问题,感觉前端都没摸透啊,还是别盲目追求广度吧。现在的项目基本上都是 JWT,也就是“token”,楼上大哥们都说完了。
    DavidNineRoc
        14
    DavidNineRoc  
       2020 年 2 月 3 日
    1. session session 状态的维护一般是靠浏览器
    * 你使用浏览器访问网页有一个的 cookie id,然后当你使用 session 之后,这个客户端的 cookie id 会和服务端的 session 关联,每次请求浏览器会自动带上 cookie id,然后你的编程语言会根据 cookie i 取到对应的 session。
    * session 和 redis 无关系,因为 session 可以保存为文件形式,可以保存到数据库。编程语言会以某种机制处理过期的 session,如 PHP 会以 10000 分之一的 概率处理,( 10000 只是配置),就是每次请求都有 1/10000 几率处理

    2. jwt 里保存有过期时间
    * 使用 jwt,就需要手动维护一个关联关系,(使用 session 是浏览器帮忙维护了)
    * 如果需要提前过期,那么你就需要一个 blacklist, 也就是黑名单。
    * 每个 token 在服务端验证就是 是否有效,时间戳是否过期,是否在黑名单,然后提取关键字段如 id 登录
    JamesR
        15
    JamesR  
       2020 年 2 月 3 日
    很复杂,不是 1 天 2 天就能学会,得 1-2 周差不多。

    后端专门有个函数用来验证登录,用户每打开个页面或者操作个啥,就会验证 cookie 里存的“安全令牌”,判断是不是登录或者过期等等。

    cookie 存放“安全令牌”,内容示例:
    HmacSHA1(securetoken, <securetokennumber><expirytime>@<userID>)
    securetoken 与 securetokennumber 是某种索引。

    安全令牌会定期刷新,从而导致存储在 Cookie 或 HTTP 会话中的身份验证状态会定期进行更新。
    此定期更新具有两个优点:
    1.闲置一段时间后,登录会话将超时:如果处理的请求处于过期状态的身份验证状态,则该请求将被视为未经身份验证。
    2.如果 Cookie 被盗或 HTTP 会话被劫持,则身份验证状态会在合理的时间内过期,以尝试防止窃取身份验证。
    areless
        16
    areless  
       2020 年 2 月 3 日
    jwt 是无 cookie 状态的 session 加摘要验证。session 简化了客户标识与客户标识所产生的服务端临时数据关联,一般采用 cookie 存客户标识,也可以无 cookie 状态直接带 GET sessiOnid=XXXXX,加上消息摘要算法与 JWT 并无两样。然后 COOKIE 一般都是可逆加密的服务端标识。只要不瞎写,3 种安全性一模一样。
    micean
        17
    micean  
       2020 年 2 月 3 日
    jwt 其实就是一个 base64 的字符串,不算是加密,服务器解码之后再校验一下里面的签名(签名也是它自己签的)

    sessionId 也是令牌,web 框架默认设置 sessionId 的 cookie 在浏览器端是受保护的(记不太清了),如果你自己设计一个签名方式的话(比如把 jwt 做在 sessionId 里)也不需要 redis

    任何人拿到令牌就相当于拿到钥匙,让 jwt 提前失效的话,像前面说的,存一份黑名单,黑名单的存活时间就是 jwt 的剩余时间。但是这样的话,无状态的设计又变回有状态了……
    Hellert
        18
    Hellert  
       2020 年 2 月 3 日 via Android
    服务端实现一个 session manager,用于分配 sessionID,加载保存 session,存储 session 特定数据,比如当前 userID 等等。

    可以在内存中实现,也可以持久化到 Redis,MySQL 等。

    不需要服务器端存储 sessions 的情况下用 jwt。
    metamask
        19
    metamask  
       2020 年 2 月 4 日
    其实没必要纠结形式。

    鉴权 (authentication) 的本质是知道请求是谁,
    这个过程大概就是 request - authentication - response。

    你 request 方式是没关系的,你从 header 进来,从 cookie 进来,从 params 进来,从 body 进来,目的只有一个,就是让服务器可以知道你是谁。

    比如你说
    | 1.登录后,账号,密码(加密)放 cookie 里 [不安全不推荐]
    你可以假设服务器有个函数为: auth_with_username_and_password(username, password) -> bool
    只要为 true,那么证明账号密码都是对的,只要这一步成功,那么就把这个鉴权信息放在上下文 (context) 那么这个过程就可以继续传递下去

    |2.登录后生成一个 sessionId 放到客户端 cookie 里。只放 session 的话 不保险(资源和重启服务),所以要 session + Redis

    这个我感觉有些人云亦云了,
    你可以当成是解耦的过程,
    auth_with_username_and_password(username, password) -> str

    这个 str 可以有 2 种返回方式,
    一种直接通过 response 返回,你拿到 str 之后前端存到哪也是没关系的,反正用的时候带上;
    一种是通过 set-cookie 的方式,直接把这个写进 cookie 里;

    那么 auth 的方式就是通过
    [auth_with_username_and_password, auth_with_session_id]

    auth_with_session_id(session_id) -> bool

    auth_with_session_id 这里怎么存都是没关系的,
    你存数据库,存 redis,存本地文件都可以,只要拿得到并且有效就可以。

    这个东西好处是,避免明码泄露账号密码,但 session 漏出去也是一样的。


    | 3.jwt ???
    一般说 有状态,无状态,这里你可以理解 jwt 无状态,是指
    jwt 已经包含了主要的鉴权信息
    jwt 的 j 也表明了是一个 json 结构,再做一层 decode 操作

    这里跟 2 比的好处是,无论你 session 放哪,你去到服务器有一个查询的操作,
    你用 jwt 的话,那么只需要解密+校验有效期就可以了。

    那么 auth 的方式就是通过

    auth_with_username_and_password(username, password) -> str

    auth 方式可为
    [auth_with_username_and_password, auth_with_token]

    auth_with_token(token) -> bool

    ====

    所以你可以把这个过程看成解耦 + 安全 + 优解。

    至于用 redis 和 用其他去做,都是优化
    你就算记在本子里,每次用户登录,你从本子找,然后发现对应上了,就通过,那么是一样的,无非用户需要等你慢慢找。
    所以怎么判断,其实就是 f(x) 有没解的过程。
    metamask
        20
    metamask  
       2020 年 2 月 4 日
    | 但是我的疑问来了。那么这个 key 是不是任何人拿到了都可以通过验证?服务端提前生成了 key,所以没办法在过期日期前让 key 失效? 所以失效的办法只有更改服务端的加密算法,让所有的 key 都失效而达到目的?


    鉴权的值只要拿到都是可以通过验证的。

    需要在过期前让其失效,那么办法就是像 @justfindu 说的 做多一个 blacklist
    但一般不要存 token 进去,而是解开后像校验日期一样,校验某个 unique key,

    这里是为了防止某些人恶作剧,生成 N 个 token,然后可以塞爆。


    ----

    一般也不改加密算法,你说的倒有可能是改 seed。
    但一般不这么做。
    nvkou
        21
    nvkou  
       2020 年 2 月 4 日 via Android
    jwt 在 header 里的话,header 加个 ref 不就能知道请求页面 URL 了?不就能防御了?
    676529483
        22
    676529483  
       2020 年 2 月 4 日
    @freakxx key 本来就没法解决中间人劫持的问题,毕竟 http 是无状态的。想要限制可以用时间戳来生成签名,避免重放攻击。当然,如果攻击者连你生成签名的源码都有,没法根本排除,比如爬虫,只是增加了攻击成本
    wangyzj
        23
    wangyzj  
       2020 年 2 月 4 日
    @justfindu #2 这个问题我也困扰过
    吊销凭证如果价格 blacklist 是不是就失去了 jwt 的优势了
    能否有更好的办法尽量避免 io
    justfindu
        24
    justfindu  
       2020 年 2 月 4 日
    @wangyzj #23 设置更短的有效时间
    wangyzj
        25
    wangyzj  
       2020 年 2 月 4 日
    @justfindu 那看来是没有及时生效的方案了
    justfindu
        26
    justfindu  
       2020 年 2 月 4 日
    @wangyzj #25 看你自己系统取舍, 任何都是有 IO 的,
    lxk11153
        27
    lxk11153  
       2020 年 2 月 6 日
    为什么会有密码放 cookie 里?不会呀

    0.1 登录不登录,都会有 sessionId 的,用来标识属于相同的会话(比如浏览器 A 访问是 sIdA,浏览器 B 访问是 sIdB,浏览器 A 里刷新一下的这次请求用什么来标识属于浏览器 A ?就是这个 sessionId,不然服务器区分不了)
    0.2 cookie, session 属于 HTTP 相关,都会有的,至于下面 1,2 的标题只是按实现来区分,并不是说 2 就没有 cookie 和 session 了
    0.3 cookie 里用什么 name 来存 sessionId 的值,由各类型服务器自定

    1. cookie + session
    客户端: cookie 里存 sessionId (一串可以说是无意义的字符,maybe 类 uuid 之类的格式)
    服务端: session,可以看做 Map1<sessionId, Map2<String, ?>>结构,存在服务器程序内存中
    服务端收到登录请求,从请求里取到 cookie 取到 sessionId,然后把需要的用户信息存入 Map2 中
    服务端收到后续请求,从请求里取到 cookie 取到 sessionId,然后取到 Map2 的用户即为当前登录用户
    So: 当存在多台服务器来负载就存在一个问题,当前页面里的请求 A 分配到服务器 A,请求 B 却分配到了服务器 B,就会导致请求 B 是无登录用户状态。解决方案比如同一个请求 IP 分配到同一个服务器这样的初级方案,但不排除 ip 变更了,所以把 session 集中化存储是可行方案(比如存到同一个库 /redis 里)

    2. token / jwt
    简单理解 maybe(猜的):
    服务器根据登录请求把需要的用户信息加密后返回,客户端拿到后存在 cookie 里
    服务器收到后续请求,获得这个加密信息解密后即为当前登录用户
    这样:服务端 session 也减少了存储,负载情况下也不需要单独的资源来集中存储 session。反正各有优缺点吧。
    lxk11153
        28
    lxk11153  
       2020 年 2 月 7 日
    fix #27 比如 Nginx 搭的静态文件服务器就没有 cookie 和 session 了,所以上面说的也要做调整。懂我意思就行
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2815 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 121ms UTC 07:29 PVG 15:29 LAX 00:29 JFK 03:29
    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