1
justfindu 2020-02-03 14:27:23 +08:00
如果使用有状态的 jwt, 是可以加入到 blacklist 使它失效的.
|
2
justfindu 2020-02-03 14:28:09 +08:00
然后服务端每次会先验证有效期,再通过 blacklist 验证是否存在.
|
3
LengthMin 2020-02-03 14:30:00 +08:00
你的理解是对的。
JWT 中是通过后端设置的一个密钥生成的,更改密钥的值就可以使其他的 Token 验证不通过。 关于提前失效,每次把分发的 token 存到数据库,服务器用代码各种判断也能实现 |
4
imn1 2020-02-03 14:30:06 +08:00
先学走,再学跑
先别管安全什么的,搞清楚登录后写什么变量,写到哪里,最简单那种 |
5
jswh 2020-02-03 14:31:55 +08:00
你所谓的 jwt 加密后的 key 就是数据本身。不用其他的东西,简单的办法就是直接把过期时间写到数据里面,后端读取解密之后,判断一下是否过期就行了。
|
6
luopengfei14 2020-02-03 14:33:53 +08:00 via iPhone
简单点的登陆:客户端登录成功后,后端会返回给前端一个大的随机字符串,可以叫 token。后端和前端都要保存这个用户的 token 或者 sessionid。以后客户端都会拿这个用户 ID 和 sessionid 传给后台,后端检查这个用户 ID 和 sessionID 是否有效。
|
7
eason1874 2020-02-03 14:37:37 +08:00 2
传统 session 在多服务器之间有一个同步问题,用户登录需要在全部服务器同步,不然用户二次访问连接到其他服务器的时候就找不到状态。
多服务器都保存比较浪费资源,JWT 就是为了解决这个问题,全部登录信息明文返回附带一个签名,其他服务器不保存登录状态,只要使用登录信息来签名,得到结果跟前端发回签名一致就认为有效。 但 JWT 也带来一个新问题,因为无状态,不能主动废弃登录信息,只能等到过期日期才失效。 所以,具体怎么用,自己选择吧。有的 JWT 是只能获取登录状态和普通信息,关键信息还得二次验证,有的 JWT 是有效时间特别短,频繁签发。 |
8
luopengfei14 2020-02-03 14:37:38 +08:00 via iPhone
一般情况下后台只保存用户的最新 sessionID,旧的会被覆盖。既是前端传旧的 ID,服务端检查出 sessionID 与最新的不匹配,返回错误。
|
9
szvone 2020-02-03 14:44:19 +08:00
前段时间自己写的一个鉴权逻辑(参考 jwt ):
账号密码登录,服务端验证后,返回 token,前端验证后,将 token 缓存,并在每次发起 http 请求的时候放在协议头里面请求 token 分为三段 账号:失效时间戳:校验码 校验码的计算逻辑就是账号加密码加失效时间戳然后 MD5,可以加点其他的固定的字符混淆 然后服务端判断逻辑就是先判断时间是否过期,在判断校验码是否正确,最后从数据库取出来用户数据 优点是:可控(登录是否失效) 缺点是:每次都需要从数据库拿数据(可以改成从 Redis 拿) |
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 解开,看信息。最后,再用 签名 验证一下信息是否是僞造的就好了。 |
11
adekyou06 2020-02-03 15:44:58 +08:00
這是我关于 jwt 的笔记,楼主可以看看
|
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。 |
13
hyy1995 2020-02-03 15:58:08 +08:00
看你以前的帖子,问“登录拦截”问题,感觉前端都没摸透啊,还是别盲目追求广度吧。现在的项目基本上都是 JWT,也就是“token”,楼上大哥们都说完了。
|
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 登录 |
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 会话被劫持,则身份验证状态会在合理的时间内过期,以尝试防止窃取身份验证。 |
16
areless 2020-02-03 21:42:11 +08:00
jwt 是无 cookie 状态的 session 加摘要验证。session 简化了客户标识与客户标识所产生的服务端临时数据关联,一般采用 cookie 存客户标识,也可以无 cookie 状态直接带 GET sessionid=XXXXX,加上消息摘要算法与 JWT 并无两样。然后 COOKIE 一般都是可逆加密的服务端标识。只要不瞎写,3 种安全性一模一样。
|
17
micean 2020-02-03 22:48:17 +08:00
jwt 其实就是一个 base64 的字符串,不算是加密,服务器解码之后再校验一下里面的签名(签名也是它自己签的)
sessionId 也是令牌,web 框架默认设置 sessionId 的 cookie 在浏览器端是受保护的(记不太清了),如果你自己设计一个签名方式的话(比如把 jwt 做在 sessionId 里)也不需要 redis 任何人拿到令牌就相当于拿到钥匙,让 jwt 提前失效的话,像前面说的,存一份黑名单,黑名单的存活时间就是 jwt 的剩余时间。但是这样的话,无状态的设计又变回有状态了…… |
18
Hellert 2020-02-03 23:12:48 +08:00 via Android
服务端实现一个 session manager,用于分配 sessionID,加载保存 session,存储 session 特定数据,比如当前 userID 等等。
可以在内存中实现,也可以持久化到 Redis,MySQL 等。 不需要服务器端存储 sessions 的情况下用 jwt。 |
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) 有没解的过程。 |
20
freakxx 2020-02-04 00:26:01 +08:00
| 但是我的疑问来了。那么这个 key 是不是任何人拿到了都可以通过验证?服务端提前生成了 key,所以没办法在过期日期前让 key 失效? 所以失效的办法只有更改服务端的加密算法,让所有的 key 都失效而达到目的?
鉴权的值只要拿到都是可以通过验证的。 需要在过期前让其失效,那么办法就是像 @justfindu 说的 做多一个 blacklist 但一般不要存 token 进去,而是解开后像校验日期一样,校验某个 unique key, 这里是为了防止某些人恶作剧,生成 N 个 token,然后可以塞爆。 ---- 一般也不改加密算法,你说的倒有可能是改 seed。 但一般不这么做。 |
21
nvkou 2020-02-04 02:09:19 +08:00 via Android
jwt 在 header 里的话,header 加个 ref 不就能知道请求页面 URL 了?不就能防御了?
|
22
676529483 2020-02-04 09:20:21 +08:00
@freakxx key 本来就没法解决中间人劫持的问题,毕竟 http 是无状态的。想要限制可以用时间戳来生成签名,避免重放攻击。当然,如果攻击者连你生成签名的源码都有,没法根本排除,比如爬虫,只是增加了攻击成本
|
23
wangyzj 2020-02-04 13:58:24 +08:00
|
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。反正各有优缺点吧。 |
28
lxk11153 2020-02-07 14:15:40 +08:00
fix #27 比如 Nginx 搭的静态文件服务器就没有 cookie 和 session 了,所以上面说的也要做调整。懂我意思就行
|
29
wanguorui123 2020-02-29 17:01:58 +08:00
|