登陆是每一个网站中都常常用到的一个功能,在页面上咱们输入帐号密码,敲一下回车键,就登陆了,但这背后的登陆原理你是否清楚呢?今天咱们就来介绍几种经常使用的登陆方式。前端
HTTP 是一种无状态的协议,客户端每次发送请求时,首先要和服务器端创建一个链接,在请求完成后又会断开这个链接。这种方式能够节省传输时占用的链接资源,但同时也存在一个问题:每次请求都是独立的,服务器端没法判断本次请求和上一次请求是否来自同一个用户,进而也就没法判断用户的登陆状态。算法
为了解决 HTTP 无状态的问题,Lou Montulli 在 1994 年的时候,推出了 Cookie。数据库
Cookie 是服务器端发送给客户端的一段特殊信息,这些信息以文本的方式存放在客户端,客户端每次向服务器端发送请求时都会带上这些特殊信息。
有了 Cookie 以后,服务器端就可以获取到客户端传递过来的信息了,若是须要对信息进行验证,还须要经过 Session。后端
客户端请求服务端,服务端会为此次请求开辟一块内存空间,这个即是 Session 对象。
有了 Cookie 和 Session 以后,咱们就能够进行登陆认证了。api
Cookie + Session 的登陆方式是最经典的一种登陆方式,如今仍然有大量的企业在使用。浏览器
用户首次登陆时:安全
a.com/pageA
,并输入密码登陆。服务器端的 SessionId 可能存放在不少地方,例如:内存、文件、数据库等。
第一次登陆完成以后,后续的访问就能够直接使用 Cookie 进行身份验证了:服务器
a.com/pageB
页面时,会自动带上第一次登陆时写入的 Cookie。虽然咱们使用 Cookie + Session 的方式完成了登陆验证,但仍然存在一些问题:微信
为了解决 Session + Cookie 机制暴露出的诸多问题,咱们可使用 Token 的登陆方式。cookie
Token是服务端生成的一串字符串,以做为客户端请求的一个令牌。当第一次登陆后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 便可完成身份认证。
用户首次登陆时:
后续页面访问时:
a.com/pageB
时,带上第一次登陆时获取的 Token。根据上面的案例,咱们能够分析出 Token 的优缺点:
最多见的 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 = '{"loggedInAs":"admin","iat":1422779638}' //iat 表示令牌生成的时间
signature 部分为 JWT 的签名,主要为了让 JWT 不能被随意篡改,签名的方法分为两个步骤:
base64url
编码的 header 部分、 .
、base64url
编码的 playload 部分,输出 unsignedToken。const base64Header = encodeBase64(header) const base64Payload = encodeBase64(payload) const unsignedToken = `${base64Header}.${base64Payload}` const key = '服务器私钥' signature = HMAC(key, unsignedToken)
最后的 Token 计算以下:
最后的 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 有效' }
有了 Token 以后,登陆方式已经变得很是高效,接下来咱们介绍另外两种登陆方式。
单点登陆指的是在公司内部搭建一个公共的认证中心,公司下的全部产品的登陆均可以在认证中内心完成,一个产品在认证中心登陆后,再去访问另外一个产品,能够不用再次登陆,便可获取登陆状态。
用户首次访问时,须要在认证中心登陆:
a.com
下的 pageA 页面。www.sso.com?return_uri=a.com/pageA
,以便登陆后直接进入对应页面。a.com?ticket=123
带上受权码 ticket,并将认证中心 sso.com
的登陆态写入 Cookie。a.com
服务器中,拿着 ticket 向认证中心确认,受权码 ticket 真实有效。a.com
和 sso.com
的登陆态)。认证中心登陆完成以后,继续访问 a.com
下的其余页面:
这个时候,因为 a.com
存在已登陆的 Cookie 信息,因此服务器端直接认证成功。
若是认证中心登陆完成以后,访问 b.com
下的页面:
这个时候,因为认证中心存在以前登陆过的 Cookie,因此也不用再次输入帐号密码,直接返回第 4 步,下发 ticket 给 b.com
便可。
目前咱们已经完成了单点登陆,在同一套认证中心的管理下,多个产品能够共享登陆态。如今咱们须要考虑退出了,即:在一个产品中退出了登陆,怎么让其余的产品也都退出登陆?
原理其实不难,能够回过头来看第 5 步,每个产品在向认证中心验证 ticket 时,其实能够顺带将本身的退出登陆 api 发送到认证中心。
当某个产品 c.com
退出登陆时:
c.com
中的登陆态 Cookie。sso.com
中的退出 api。在上文中,咱们使用单点登陆完成了多产品的登陆态共享,但都是创建在一套统一的认证中心下,对于一些小型企业,未免太麻烦,有没有一种登陆可以作到开箱即用?
实际上是有的,不少大厂都会提供本身的第三方登陆服务,咱们一块儿来分析一下。
这里以微信开放平台的接入流程为例:
a.com
的运营者须要在微信开放平台注册帐号,并向微信申请使用微信登陆功能。a.com
上选择使用微信登陆。a.com
的回调地址。a.com?code=123
,这时带上了一个临时票据 code。a.com
会拿着 code 、appid、appsecret,向微信服务器申请 token,验证成功后,微信会下发一个 token。a.com
就能够凭借 token 拿到对应的微信用户头像,用户昵称等信息了。a.com
提示用户登陆成功,并将登陆状态写入 Cooke,以做为后续访问的凭证。本文介绍了 4 种常见的登陆方式,原理应该你们都清楚了,总结一下这 4 种方案的使用场景: