Cookie + session 实现
服务器端的 SessionId 可能存放在很多地方,例如:内存、文件、数据库等。
第一次登录完成之后,后续的访问就可以直接使用 Cookie 进行身份验证了:
缺点:
- 服务器需要对接大量客户端,所以需要存放大量的sessionId, 这样会导致服务器压力过大
- 服务器端如果是一个集群,为了同步登陆状态,需要将sessionId同步到每一台机器,加大了服务端维护成本
- 由于SessionId存放在Cookie中,所以无法避免CSRF攻击。
Token登录
Token 是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 即可完成身份认证。
JWT
最常见的 Token 生成方式是使用 JWT(Json Web Token),它是一种简洁的,自包含的方法用于通信双方之间以 JSON 对象的形式安全的传递信息。
上文中我们说到,使用 Token 后,服务器端并不会存储 Token,那怎么判断客户端发过来的 Token 是合法有效的呢?
答案其实就在 Token 字符串中,其实 Token 并不是一串杂乱无章的字符串,而是通过多种算法拼接组合而成的字符串,我们来具体分析一下。
JWT 算法主要分为 3 个部分:header(头信息),playload(消息体),signature(签名)。
header 部分指定了该 JWT 使用的签名算法:
header = '{"alg":"HS256","typ":"JWT"}' // `HS256` 表示使用了 HMAC-SHA256 来生成签名。
playload 部分表明了 JWT 的意图:
payload:
- iss (issuer):签发人
- exp (expiration time):过期时间
- sub (subject):主题
- aud (audience):受众
- nbf (Not Before):生效时间
- iat (Issued At):签发时间
- jti (JWT ID):编号
也可以自定义字段,比如包含usename/role等的payload:
payload = '{"loggedInAs":"admin","iat":1422779638}' //iat 表示令牌生成的时间
signature 部分为 JWT 的签名,主要为了让 JWT 不能被随意篡改,签名的方法分为两个步骤:
- 输入
base64url编码的 header 部分、.、base64url编码的 playload 部分,输出 unsignedToken。 - 输入服务器端私钥、unsignedToken,输出 signature 签名。
const base64Header = encodeBase64(header)
const base64Payload = encodeBase64(payload)
const unsignedToken = `${base64Header}.${base64Payload}`
const key = '服务器私钥'
signature = HMAC(key, unsignedToken)
最后的 Token 计算如下:
const base64Header = encodeBase64(header)
const base64Payload = encodeBase64(payload)
const base64Signature = encodeBase64(signature)
token = `${base64Header}.${base64Payload}.${base64Signature}`
服务器在判断 Token 时:
const [base64Header, base64Payload, base64Signature] = token.split('.')
const signature1 = decodeBase64(base64Signature)
const unsignedToken = `${base64Header}.${base64Payload}`
const signature2 = HMAC('服务器私钥', unsignedToken)
if(signature1 === signature2) {
return '签名验证成功,token 没有被篡改'
}
const payload = decodeBase64(base64Payload)
if(new Date() - payload.iat < 'token 有效期'){
return 'token 有效'
}
1、服务端根据用户登录状态,将用户信息加密到token中,返给客户端
2、客户端收到服务端返回的token,存储在cookie中
3、客户端和服务端每次通信都带上token,可以放在http请求头信息中,如:Authorization字段里面
4、服务端解密token,验证内容,完成相应逻辑
参考文章: jwt定义:[juejin.cn/post/684490…]
SSO登录
- 用户访问网站
a.com下的 pageA 页面。 - 由于没有登录,则会重定向到认证中心,并带上回调地址
www.sso.com?return_uri=a.com/pageA,以便登录后直接进入对应页面。 - 用户在认证中心输入账号密码,提交登录。
- 认证中心验证账号密码有效,然后重定向
a.com?ticket=123带上授权码 ticket,并将认证中心sso.com的登录态写入 Cookie。 - 在
a.com服务器中,拿着 ticket 向认证中心确认,授权码 ticket 真实有效。 - 验证成功后,服务器将登录信息写入 Cookie(此时客户端有 2 个 Cookie 分别存有
a.com和sso.com的登录态)。
目前我们已经完成了单点登录,在同一套认证中心的管理下,多个产品可以共享登录态。现在我们需要考虑退出了,即:在一个产品中退出了登录,怎么让其他的产品也都退出登录? 原理其实不难,可以回过头来看第 5 步,每一个产品在向认证中心验证 ticket 时,其实可以顺带将自己的退出登录 api 发送到认证中心。 当某个产品 c.com 退出登录时:
- 清空
c.com中的登录态 Cookie。 - 请求认证中心
sso.com中的退出 api。 - 认证中心遍历下发过 ticket 的所有产品,并调用对应的退出 api,完成退出。
OAuth方式登录
- 首先,
a.com的运营者需要在微信开放平台注册账号,并向微信申请使用微信登录功能。 - 申请成功后,得到申请的 appid、appsecret。
- 用户在
a.com上选择使用微信登录。 - 这时会跳转微信的 OAuth 授权登录,并带上
a.com的回调地址。 - 用户输入微信账号和密码,登录成功后,需要选择具体的授权范围,如:授权用户的头像、昵称等。
- 授权之后,微信会根据拉起
a.com?code=123,这时带上了一个临时票据 code。 - 获取 code 之后,
a.com会拿着 code 、appid、appsecret,向微信服务器申请 token,验证成功后,微信会下发一个 token。 - 有了 token 之后,
a.com就可以凭借 token 拿到对应的微信用户头像,用户昵称等信息了。 a.com提示用户登录成功,并将登录状态写入 Cooke,以作为后续访问的凭证。
总结比较
- Cookie + Session 历史悠久,适合于简单的后端架构,需开发人员自己处理好安全问题。
- Token 方案对后端压力小,适合大型分布式的后端架构,但已分发出去的 token ,如果想收回权限,就不是很方便了。
- SSO 单点登录,适用于中大型企业,想要统一内部所有产品的登录方式。
- OAuth 第三方登录,简单易用,对用户和开发者都友好,但第三方平台很多,需要选择合适自己的第三方登录平台。