前端登陆指南
前言
前端登陆的四种方式介绍以及应用
关键词:Cookie/Session、Token、SSO单点登录、OAuth第三方登陆
验证登陆
- Cookie/Session
- Token
- SSO 单点登陆
- OAuth第三方登陆
cookie session
登陆流程:首次登陆
- 用户访问
a.com/pageA
并输入密码登陆 - 服务器验证密码无误后,会创建Session ID并将其保存起来
- 服务端响应这个请求,并通过Set-Cookie将Session ID写入Cookie中
再次登陆:
- 用户访问
a.com/pageB
自动带上第一次登陆写入的Cookie - 服务端对比Cookie中的SessionID和保存在服务端的SessionID是否一致
- 一致则成功
cookie session存在的问题
- 服务器需要对接大量的客户端,导致服务器压力大。
- 如果服务器是一个集群,为了同步登陆态,就需要将SessionID同步到每一台机器上,无形中增加了服务器的维护成本。
- 由于SessionID存放在Cookie中所以无法避免CSRF攻击
token登陆
为了解决cookie+session暴露出来的问题,我们可以使用token的登陆方式
token是服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登陆完成后,服务器会生成一个token并返回给客户端,客户端后续访问只要带上这个token就可以完成身份验证。
token机制实现流程
首次登陆:
- 用户输入账号密码,并点击登录。
- 服务器端验证账号密码无误,创建 Token。
- 服务器端将 Token 返回给客户端,由客户端自由保存。
后续页面访问时:
- 用户访问 a.com/pageB 时,带上第一次登录时获取的 Token。
- 服务器端验证 Token ,有效则身份验证成功。
Token 机制的特点
根据上面的案例,我们可以分析出Token的优缺点:
- 服务器端不需要存放token,所以不会对服务器造成压力,即使服务器集群,也不需要增加维护成本。
- Token可以存放在前端的任何地方,可以不用保存在Cookie中,提升了页面的安全性。
- Token下发之后,只要在生效时间内,就一直有效,如果服务器想收回此token的权限,并不容易。
Token的生成方式
最常见的Token生成方式是使用JWT,他是一种简洁的,自包含的方法用于通信双方之间以JSON对象的形式安全的传递信息。
上文中我们说到,使用Token后,服务器端并不会存储Token,那怎么判断客户端的token是否合法有效?
答案其实就在Token字符串中,其实Token并不是一串杂乱无章的字符串,而是通过多种算法拼接组合而成的字符串。如下:
JWT算法主要分成了三个部分
- header 头信息
- payload 消息体
- signature 签名
header部分指定了JWT使用的签名算法:1
header = '{"alg":"HS256","typ":"JWT"}' // `HS256` 表示使用了 HMAC-SHA256 来生成签名。
payload部分指明了JWT的使用意图1
payload = '{"loggedInAs":"admin","iat":1422779638}' //iat 表示令牌生成的时间
signature部分为JWT的签名 主要为了让JWT不能随意更改,签名的方法分为两个步骤
- 输入base64url编码的header部分,base64url编码的payload部分,输出unsignedToken
- 输入服务器端私钥,unsignedToken,输出signature签名
1 | const base64Header = encodeBase64(header) |
最后Token计算如下:1
2
3
4
5const base64Header = encodeBase64(header)
const base64Payload = encodeBase64(payload)
const base64Signature = encodeBase64(signature)
token = `${base64Header}.${base64Payload}.${base64Signature}`
服务器在判断token的时候1
2
3
4
5
6
7
8
9
10
11
12
13
14const [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 有效'
}
有了 Token 之后,登录方式已经变得非常高效,接下来我们介绍另外两种登录方式。
SSO单点登陆
单点登陆指的是在公司内部搭建一个公共的认证中心,公司下的所有产品的登陆都可以在认证中心中完成,一个产品在认证中心登陆后,再去访问另外一个产品,可以不用再次登陆,即可获取登陆状态。
SSO机制实现流程
分为同域和不同域。
同域的单点登陆
比如现在有一个域名叫zlinni.cn
,然后我的其他页面分别为:1
2
3
4a.zlinni.cn
b.zlinni.cn
c.zlinni.cn
login.zlinni.cn//sso登陆系统
那么此时我们只要满足在login中登陆,然后abc三个都登陆就完成了sso
实现的方式就是利用了cookie的path和domain,我们设置domain为.zlinni.cn
,然后path为/
就能实现所有的域名都共享login的cookie,就实现了单点登陆。
不同域的单点登陆
用户首次访问需要在认证中心登陆:
- 用户先访问
a.com
下面的pageA页面,然后系统发现它未登陆,给它重定向到sso认证中心 - 在sso中注册,提交登陆
- sso验证账号密码有效,重定向到
a.com
并带上授权码ticket,再将sso的登录态写入cookie - 此时a就拿着ticket去sso中验证是否有效。
- 在a的服务器中拿着ticket向sso验证确认真实性
- 验证成功后,服务器将写入两个信息到cookie,一个是ticket作为a的登录态,一个是sso的token记录sso的登录态
抛出疑问:为什么要用ticket?这个问题我们后面再讲
然后登陆完成访问a下面的其他页面
此时我们的cookie中携带了ticket,只要到服务端验证是否在有效的时间内即可。
访问b页面的时候(这个b页面是认证中心下面的不同产品,如果要做例子的话,可以认为a是新浪微博,b是新浪博客)
此时由于我们的b是不同源的页面,所以cookie中是没有ticket的(!!!!非常重要),但是它重定向到sso中,sso中我们的cookie是存在登录态的,所以此时不需要再注册等操作,验证token之后,直接给b下发ticket即可。b再去验证ticket真实性即可完成登陆。
为什么需要Ticket
ticket的存在是为了解决不同域携带的cookie不一样的问题
以上过程就解释了我们要用ticket的理由,因为a和b不是同源页面所以不会携带同样的ticket,但是sso中拥有登录态token所以我们会下发ticket到b中验证。
SSO单点登陆退出
单点登陆使得多个产品共享登录态,但是怎么做到在一个产品中退出登陆,其他的产品也退出呢?
这里其实要做的事情很简单,比如说退出这个bcom的登陆:
- 首先我们要清除掉b中的ticket
- 认证中心sso中清除对应的cookie
- sso遍历所有对应cookie下发了ticket的产品并删除。
总结sso
sso就是类似于Nginx+ticket+token
的组合.其中,我们利用它的重定向规则,可以将token存在sso的cookie中,将ticket返回到具体的页面存在cookie中,然后再认证完成登陆
OAuth第三方登陆
由于sso实际上还是比较麻烦的,所以我们可以利用大厂提供的第三方登陆系统实现登陆
我们不关心这个完成授权的过程,但是要知道用户点击到返回登陆的这个过程发生了什么
- 首先用户在acom点击用户登陆,跳转到OAuth,并带上回调地址acom
- 用户二维码验证之后,微信返回临时票据code
- a带着code和自己申请的appid、appsecret,向微信的服务器申请token,验证成功微信下发token
- 有了token之后,a就可以向微信服务器获取用户的头像和昵称等信息。并将用户的登录态写入cookie以便下一次登陆。