V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
firhome
V2EX  ›  程序员

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

  •  
  •   firhome · 2020-02-03 14:23:16 +08:00 · 6357 次点击
    这是一个创建于 1541 天前的主题,其中的信息可能已经有所发展或是发生改变。
    我是前端,利用假期时间在家想学习 后端知识。

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

    就我目前来说我知道的。

    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-02-03 14:27:23 +08:00
    如果使用有状态的 jwt, 是可以加入到 blacklist 使它失效的.
    justfindu
        2
    justfindu  
       2020-02-03 14:28:09 +08:00
    然后服务端每次会先验证有效期,再通过 blacklist 验证是否存在.
    LengthMin
        3
    LengthMin  
       2020-02-03 14:30:00 +08:00
    你的理解是对的。
    JWT 中是通过后端设置的一个密钥生成的,更改密钥的值就可以使其他的 Token 验证不通过。

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

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

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

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

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


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


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

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


    优点是:可控(登录是否失效)
    缺点是:每次都需要从数据库拿数据(可以改成从 Redis 拿)
    adekyou06
        10
    adekyou06  
       2020-02-03 15:43:29 +08:00   ❤️ 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-02-03 15:44:58 +08:00
    這是我关于 jwt 的笔记,楼主可以看看
    jaynos
        12
    jaynos  
       2020-02-03 15:55:34 +08:00
    歪个楼,我认为用 jwt 作为用户 token 有点不太合适。具体网上有一大堆文章讲为啥不合适,我就不多 BB 了。

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

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

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

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

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

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

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

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

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

    不需要服务器端存储 sessions 的情况下用 jwt。
    freakxx
        19
    freakxx  
       2020-02-04 00:14:42 +08:00
    其实没必要纠结形式。

    鉴权 (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) 有没解的过程。
    freakxx
        20
    freakxx  
       2020-02-04 00:26:01 +08:00
    | 但是我的疑问来了。那么这个 key 是不是任何人拿到了都可以通过验证?服务端提前生成了 key,所以没办法在过期日期前让 key 失效? 所以失效的办法只有更改服务端的加密算法,让所有的 key 都失效而达到目的?


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

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

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


    ----

    一般也不改加密算法,你说的倒有可能是改 seed。
    但一般不这么做。
    nvkou
        21
    nvkou  
       2020-02-04 02:09:19 +08:00 via Android
    jwt 在 header 里的话,header 加个 ref 不就能知道请求页面 URL 了?不就能防御了?
    676529483
        22
    676529483  
       2020-02-04 09:20:21 +08:00
    @freakxx key 本来就没法解决中间人劫持的问题,毕竟 http 是无状态的。想要限制可以用时间戳来生成签名,避免重放攻击。当然,如果攻击者连你生成签名的源码都有,没法根本排除,比如爬虫,只是增加了攻击成本
    wangyzj
        23
    wangyzj  
       2020-02-04 13:58:24 +08:00
    @justfindu #2 这个问题我也困扰过
    吊销凭证如果价格 blacklist 是不是就失去了 jwt 的优势了
    能否有更好的办法尽量避免 io
    justfindu
        24
    justfindu  
       2020-02-04 14:29:15 +08:00
    @wangyzj #23 设置更短的有效时间
    wangyzj
        25
    wangyzj  
       2020-02-04 14:43:12 +08:00
    @justfindu 那看来是没有及时生效的方案了
    justfindu
        26
    justfindu  
       2020-02-04 15:12:23 +08:00
    @wangyzj #25 看你自己系统取舍, 任何都是有 IO 的,
    lxk11153
        27
    lxk11153  
       2020-02-06 19:02:29 +08:00
    为什么会有密码放 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-02-07 14:15:40 +08:00
    fix #27 比如 Nginx 搭的静态文件服务器就没有 cookie 和 session 了,所以上面说的也要做调整。懂我意思就行
    wanguorui123
        29
    wanguorui123  
       2020-02-29 17:01:58 +08:00
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3322 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 12:28 · PVG 20:28 · LAX 05:28 · JFK 08:28
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.