5.4 KiB
用户认证
cookie、session、token、jwt、单点登录 https://zhuanlan.zhihu.com/p/281414244
cookie
- 狭义 cookie 过时技术
- 单个保存的数据不能超过 4 k, 单个域名数量有限
- 影响传输效率
- 明文存储在客户端, 安全性相对较低
- ==cookie: 只是浏览器存储的一小块数据, 也能用来存 token session, 每次浏览器发送请求就会带上 cookie 的数据==
- 发送 cookie:
res.cookie('username','admin')
- 解析 cookie: cookie-parser // 先 npm 下载再使用
app.use(cookieParser())
- 读取 cookie: req. cookie. username
- cookie 是有有效期的, 一般是默认一次会话 (到浏览器关闭前)
- 设置有效期
res.cookie('username','admin', {expires:new Date(2088,11,1)})
或者res.cookie('username','admin', {maxAge:毫秒})
- 部分删除:
res.clearCookie('username')
相当于携带一个已过期的时间 - 全部删除: 发送到客户端后, 不能删除, 但可以发一个立即过期的, 来进行替换
session
- ==信息存在服务器, id 存一份在浏览器==, 只把信息对应的唯一 id 通过 cookie 发给用户, 用户请求数据在 cookie 里携带这个 id
- express-session
app.use(session({secret: "加密的盐"}))
- 在服务器存储用户信息, 只把唯一 id 通过 cookie 发给用户
- 存储:
req.session.username = xxx
- 默认有效期: 一次会话
- 设置有效期:
req.session.cookie.maxAge = 毫秒
- 销毁:
req.session.destroy(callback)
- 存储在内存里, 重启就失效, 需要持久化 (Redis)
token
==一种加密的文本字符串, 和 coookie session 不是同一维度的东西, 至于服务器存不存内存/redis ,浏览器存 cookie 或 storage, 通过 cookie 或者放请求头传给服务器, 和 token 无关==
session 的维护给服务端造成很大困扰,我们必须找地方存放它,又要考虑分布式的问题,甚至要单独为了它启用一套 Redis 集群 用户登录,服务端校验账号密码,获得用户信息, 把用户信息、token 配置编码成 token,发给浏览器
refresh token
性能原因
- access token 用来访问业务接口,由于有效期足够短,盗用风险小,也可以使请求方式更宽松灵活
- refresh token 用来获取 access token,有效期可以长一些,通过独立服务和严格的请求方式增加安全性;由于不常验证,==也可以如前面的 session 一样处理, 存服务器, 并且能主动使其失效==
jwt
json web token, token 的一种实现 优点: 只在服务端校验, 不用存储 缺点: 服务端不能主动让 token 失效, 吊销只能用类似黑名单的方法 一定别在 token 中存放敏感信息, 能通过 base 64 解密 使用场景: 有效期短 其他方案: redis token / memory session
jwt 格式:
base 64 加密后的 header 和 base 64 加密后的 payload 使用.连接组成的字符串 (头部在前),然后通过 header 中声明的加密方式进行加盐 secret 组合加密,然后就构成了 jwt 的第三部分
密钥 secret 是保存在服务端
服务器应用在接受到 JWT 后对头部和载荷再次以同样方法签名之后发现,自己计算出来的签名和接受到的签名不一样,那么就说明这个 Token 的内容被别人动过的,我们应该拒绝这个 Token
安装
npm install express-jwt
加入中间件
const expressJwt = require('express-jwt')
// let token = jwt.sign(用户数据,加密字符串,配置对象)
app.use(expressJwt({
secret: 'secret12345' // 签名的密钥 或 PublicKey
}).unless({
path: ['/login', '/signup'] // 指定路径不经过 Token 解析
}))
生成 Token
const jwt = require('jsonwebtoken')
app.post('/login', function (req, res) {
// 注意默认情况 Token 必须以 Bearer+空格 开头
const token = 'Bearer ' + jwt.sign(
{
_id: user._id,
admin: user.role === 'admin'
},
'secret12345',
{
expiresIn: 3600 * 24 * 7
}
)
res.json({
status: 'ok',
data: { token: token }
})
})
获取解析内容
在路由回调里通过 req. user 来访问
app.get('/protected', function (req, res) {
if (!req.user.admin)
return res.sendStatus(401)
res.sendStatus(200)
})
req. user 实际就是 JWT 的 payload 部分
{
_id: '5dbbc7daaf7dfe003680ba39',
admin: true,
iat: 1572587484,
exp: 1573192284
}
解析失败
如果解析失败,会抛出 UnauthorizedError,可以通过后置中间件来捕获
app.use(function (err, req, res, next) {
if (err.name === 'UnauthorizedError') {
res.status(401).send('invalid token')
}
})
修改结果字段
默认解析结果会赋值在 req. user,也可以通过 requestProperty 来修改
app.use(expressJwt({
secret: 'secret12345',
requestProperty: 'auth'
}))
单点登录
OAuth 2.0
一种第三方认证的解决方案