背景
- jwt是无状态的,除了自动失效之外无法手动过期
举个例子:
- 当我们的token泄露或者密码泄露,别人冒充自己做一些非法操作(比如删除自己的作品)
- 这时候我们无法让账号强行下线,会存在安全隐患
我的需求
解决密码泄露/token泄露无法强制下线的安全问题
解决方法
方案1:黑名单
- 当用户密码泄露或者token泄露时,将用户加入黑名单,限制用户的操作
存在的问题
举个例子:
- token的有效期是1天,
- 用户token泄露,将用户加入黑名单,用户花了1小时修改密码,并移出黑名单(用户需要正常使用)
- 但是泄露的旧的token依旧有效,非法用户可以继续操作
解决方案
- 双重验证
- 先验证是否黑名单
- 再验证token的颁发时间是否在密码修改时间之后
问题
- 整套逻辑会变得比较复杂,并且一次访问需要验证两次redis(使用lua可以一次性验证),性能还不如使用session
成本估算:
- 条件: 100万总QPS,我们使用了redis的lua脚本,每次需要一次请求redis
- redis压力100万QPS
- redis性能瓶颈: 10wQPS;按50%的cpu使用率是比较安全的,每台机器5wQPS
- 需要机器数量: 100w/5w=20台
- 每台机器成本: 2000元/月
总成本: 2000*20=40000元/月
方案2: 双token刷新
- 用户登录时,生成两个token,一个是正常的token,一个是刷新token,刷新token的有效期是1天,正常token的有效期是5s
- 当正常token失效时,用户可以使用刷新token重新生成一个新的正常token
核心解决的问题
- 提高用户的体验(不会再使用中突然下线,因为token可以自动刷新)
存在的问题
- 只能解决正常(短期)token泄露的问题(有效期短,别人拿到就已经过期了),刷新token泄露/密码泄露无法解决
方案3: token与session结合
- 根据方案2的思路,我们可以将token与session结合
- 当用户登录时,生成一个token,有效期是5s; 同时生成一个session,存在 redis中有效期1天,每次使用自动续期
工作原理:
- 请求时使用token,token失效时,使用session刷新token
泄露的情况与解决方案
- token泄露: token有效期短,别人拿到就已经过期了
- session泄露: 可以手动删除,限制用户的操作
- 密码泄露: 删除session,限制用户的操作;将用户更改为异常状态,限制用户登录(MySQL层)
这样我们就解决了密码泄露/token泄露无法强制下线的安全问题
成本估算
- 条件: 100万总QPS
- token验证不需要请求redis,只有使用session时才需要请求redis
- redis压力分摊到5秒钟,redis的压力20wQPS
- redis性能瓶颈: 10wQPS;按50%的cpu使用率是比较安全的,每台机器5wQPS
- 需要机器数量: 20w/5w=4台
- 每台机器成本: 2000元/月
总成本: 2000*4=8000元/月
成本比方案1低了80%
方案4: 长连接(websocket)
实现原理:
- 创建长链接(websocket)的时候需要进行验证(比如:使用 session 验证),
- 创建连接之后保持与服务端的连接,请求就不再需要鉴权了
优点:
- 减少反复的鉴权的开销
- 主动推送信息(高实时性)
缺点:
- 长连接会占用服务器资源,连接数过多时可能导致性能问题。
安全问题
密码泄露:
- 删除 session,标记用户状态
session 泄露: - 没有用,一个 session 只允许一个连接,只要之前的没有断开,其他的使用 session 连接不上.
可以更好的解决安全问题,还可以进行安全警告(比如异地登录,异设备登录等等),告知客户有人正在非法访问他的账号.