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

2020-02-03 14:23:16 +08:00
 firhome
我是前端,利用假期时间在家想学习 后端知识。

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

就我目前来说我知道的。

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

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

3.jwt ???

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

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


以上是我的疑问和理解,不知道是否正确。希望后端同学帮忙指导一下,谢谢~
6368 次点击
所在节点    程序员
29 条回复
justfindu
2020-02-03 14:27:23 +08:00
如果使用有状态的 jwt, 是可以加入到 blacklist 使它失效的.
justfindu
2020-02-03 14:28:09 +08:00
然后服务端每次会先验证有效期,再通过 blacklist 验证是否存在.
LengthMin
2020-02-03 14:30:00 +08:00
你的理解是对的。
JWT 中是通过后端设置的一个密钥生成的,更改密钥的值就可以使其他的 Token 验证不通过。

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

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

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

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

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


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


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

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


优点是:可控(登录是否失效)
缺点是:每次都需要从数据库拿数据(可以改成从 Redis 拿)
adekyou06
2020-02-03 15:43:29 +08:00
# 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
2020-02-03 15:44:58 +08:00
這是我关于 jwt 的笔记,楼主可以看看
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
2020-02-03 15:58:08 +08:00
看你以前的帖子,问“登录拦截”问题,感觉前端都没摸透啊,还是别盲目追求广度吧。现在的项目基本上都是 JWT,也就是“token”,楼上大哥们都说完了。
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
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
2020-02-03 21:42:11 +08:00
jwt 是无 cookie 状态的 session 加摘要验证。session 简化了客户标识与客户标识所产生的服务端临时数据关联,一般采用 cookie 存客户标识,也可以无 cookie 状态直接带 GET sessionid=XXXXX,加上消息摘要算法与 JWT 并无两样。然后 COOKIE 一般都是可逆加密的服务端标识。只要不瞎写,3 种安全性一模一样。
micean
2020-02-03 22:48:17 +08:00
jwt 其实就是一个 base64 的字符串,不算是加密,服务器解码之后再校验一下里面的签名(签名也是它自己签的)

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

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

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

不需要服务器端存储 sessions 的情况下用 jwt。
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
2020-02-04 00:26:01 +08:00
| 但是我的疑问来了。那么这个 key 是不是任何人拿到了都可以通过验证?服务端提前生成了 key,所以没办法在过期日期前让 key 失效? 所以失效的办法只有更改服务端的加密算法,让所有的 key 都失效而达到目的?


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

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

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


----

一般也不改加密算法,你说的倒有可能是改 seed。
但一般不这么做。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/641834

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX