登录以及认证授权方案
登录以及认证授权方案
概要
登录是每个网站中都会用到的一个必备功能,但是如何实现一个优秀的登录功能,如何根据自己的项目来选择一个适合自己的登录方案?
今天我们就来介绍几种常用的登录方案。
- Cookie + Session 登录
- Token 登录
- SSO 单点登录
- OAuth 第三方登录
一、Cookie + Session登录
Cookie + Session 的登录方式是目前最经典的一种登录方式,现在仍然有大量的企业在使用。
在使用 Cookie + Session 进行会话管理时,服务端会将与用户相关的信息存储在 Session 中,而客户端则通过 Session ID 来标识和访问这些信息。
下面是具体的实现流程:
1. 用户初次登录时
1)用户访问 a.com/pageA,并输入密码登录。
2)服务器验证密码无误后,会创建 SessionId,并将它保存起来。
3)服务器端响应这个 HTTP 请求,并通过 Set-Cookie 头信息,将 SessionId 写入 Cookie 中。每次客户端发起请求时,都会携带这个SessionId。
说明:Session 可以存储在内存、Redis 或数据库中,取决于应用需求。
2. 第一次登录完成之后,后续的访问就可以直接使用 Cookie 进行身份验证了
1)用户访问 a.com/pageB 页面时,会自动带上第一次登录时写入的 Cookie。
2)服务器端比对 Cookie 中的 SessionId 和保存在服务器端的 SessionId 是否一致。
3)如果一致,则身份验证成功,访问页面;如果无效,则需要用户重新登录。
3. 存在的问题
1)由于服务器端需要对接大量的客户端,也就需要存放大量的 SessionId,这样会导致服务器压力过大。
2)如果服务器端是一个集群,为了同步登录态,需要将 SessionId 同步到每一台机器上,无形中增加了服务器端维护成本。
3)由于 SessionId 存放在 Cookie 中,所以无法避免 CSRF 攻击。
二、Token登录
为了解决 Cookie + Session 机制暴露出的诸多问题,我们可以使用 Token 的登录方式。
Token 是通过服务端生成的一串字符串,以作为客户端请求的一个令牌。当第一次登录后,服务器会生成一个 Token 并返回给客户端,客户端后续访问时,只需带上这个 Token 即可完成身份认证。
Token 机制实现流程:
1. 用户首次登录时
1)用户访问 a.com/pageA,输入账号密码,并点击登录。
2)服务器端验证账号密码无误,创建 Token。
3)服务器端将 Token 返回给客户端,由客户端自由保存。
2. 后续页面访问时:
1)用户访问 a.com/pageB 时,带上第一次登录时获取的 Token。
2)服务器端验证该 Token,有效则身份验证成功,无效则踢回重新的登录。
3. Token 生成方式
最常见的Token生成方式是使用JWT。详情请参考《基于JWT的token认证》。
三、SSO 单点登录
单点登录(Single Sign-On)是指在同一帐号平台下的多个应用系统中,用户只需登录一次,即可访问所有相互信任的应用系统。本质就是在多个应用系统中共享登录状态。举例来说,百度贴吧和百度地图是百度公司旗下的两个不同的应用系统,如果用户在百度贴吧登录过之后,当他访问百度地图时无需再次登录,那么就说明百度贴吧和百度地图之间实现了单点登录。
SSO 机制实现流程
1. 用户首次访问时,需要在认证中心登录:
1)用户访问网站 a.com 下的 pageA 页面。
2)由于没有登录,则会重定向到认证中心,并带上回调地址 www.sso.com?return_uri=a.com/pageA,以便登录后直接进入对应页面。
3)用户在认证中心输入账号密码,提交登录。
4)认证中心验证账号密码有效,然后重定向 a.com?ticket=123 带上授权码 ticket,并将认证中心 sso.com 的登录态写入 Cookie。
5)在 a.com 服务器中,拿着 ticket 向认证中心确认,授权码 ticket 真实有效。
6)验证成功后,服务器将登录信息写入 Cookie(此时客户端有 2 个 Cookie 分别存有 a.com 和 sso.com 的登录态)。
2. 认证中心登录完成之后,继续访问 a.com 下的其他页面:
这个时候,由于 a.com 存在已登录的 Cookie 信息,所以服务器端直接认证成功。
如果认证中心登录完成之后,访问 b.com 下的页面:
这个时候,由于认证中心存在之前登录过的 Cookie
,所以也不用再次输入账号密码,直接返回第 4 步,下发 ticket
给 b.com
即可。
说明:上面提到的授权码ticket ,可以采用 JWT 或 sessionId 的方式,二者都可以作为授权凭证进行传递和验证。具体选择哪种方式,取决于系统的设计需求、复杂性以及扩展性考虑。
四、 SSO 机制实现方式
单点登录主要有三种实现方式:
- 认证中心
- 父域 Cookie
- LocalStorage 跨域
注意:单点登录在使用Cookie 或 LocalStorage 跨域实现时,有一个关键的前提条件是:共同拥有相同的顶级域名。
1. 认证中心
我们可以部署一个认证中心,认证中心就是一个专门负责处理登录请求的独立的 Web 服务。
用户统一在认证中心进行登录,登录成功后,认证中心记录用户的登录状态,并将 Token 写入 Cookie。(注意这个 Cookie 是认证中心的,应用系统是访问不到的)
应用系统检查当前请求有没有 Token,如果没有,说明用户在当前系统中尚未登录,那么就将页面跳转至认证中心进行登录。由于这个操作会将认证中心的 Cookie 自动带过去,因此,认证中心能够根据 Cookie 知道用户是否已经登录过了。如果认证中心发现用户尚未登录,则返回登录页面,等待用户登录,如果发现用户已经登录过了,就不会让用户再次登录了,而是会跳转回目标 URL ,并在跳转前生成一个 Token,拼接在目标 URL 的后面,回传给目标应用系统。
应用系统拿到 Token 之后,还需要向认证中心确认下 Token 的合法性,防止用户伪造。确认无误后,应用系统记录用户的登录状态,并将 Token 写入 Cookie,然后给本次访问放行。(这个 Cookie 是当前应用系统的,其他应用系统是访问不到的)当用户再次访问当前应用系统时,就会自动带上这个 Token,应用系统验证 Token 发现用户已登录,于是就不会有认证中心什么事了。
总结:此种实现方式相对复杂,支持跨域,扩展性好,是单点登录的标准做法。
2. 父域 Cookie
父域 Cookie 跨域是一种常见的 SSO 解决方案,利用浏览器的 Cookie 机制,通过将认证信息存储在父域下的 Cookie 中,子域共享这个 Cookie,从而实现跨域的登录状态保持。
1)Cookie的特点
Cookie 的作用域由 domain 属性和 path 属性共同决定。如果将 Cookie 的 domain 属性设置为当前域的父域,那么就认为它是父域 Cookie。Cookie 有一个特点,即父域中的 Cookie 被子域所共享,也就是说,子域会自动继承父域中的 Cookie。
2)实现方案
利用 Cookie 的这个特点,可以将 Session Id(或 Token)保存到父域中就可以了。我们只需要将 Cookie 的 domain 属性设置为父域的域名(主域名),同时将 Cookie 的 path 属性设置为根路径,这样所有的子域应用就都可以访问到这个 Cookie 了。具体方案如下:
- 设置 Cookie:在父域(如 example.com)设置一个跨域的 Cookie,并在其中存储用户的认证信息(如 JWT 或 sessionId)。
- 跨域访问:子域(如 app1.example.com 和 app2.example.com)通过浏览器发送请求时,浏览器会自动携带父域下的 Cookie(如果设置了跨域访问)。
- 共享认证信息:所有子域可以访问这个 Cookie,验证用户的身份信息,并实现 SSO。
举个例子:
Set-Cookie: sso_token=xxxx; Domain=example.com; Path=/; Secure; HttpOnly
Domain=example.com:使得所有 example.com 下的子域名都能访问该 Cookie。
Secure 和 HttpOnly:增强安全性,确保 Cookie 只能通过 HTTPS 和 HTTP 协议访问。
前提条件:
要求应用系统的域名需建立在一个共同的主域名之下,如 tieba.baidu.com 和 map.baidu.com,它们都建立在 baidu.com 这个主域名之下,那么它们就可以通过这种方式来实现单点登录。
总结:此种实现方式比较简单,但不支持跨主域名。
3. LocalStorage 跨域
单点登录的关键在于,如何让 Session Id(或 Token)在多个域中共享。但是 Cookie 是不支持跨主域名的,而且浏览器对 Cookie 的跨域限制越来越严格。
在前后端分离的情况下,完全可以不使用 Cookie,我们可以选择将 Session Id (或 Token )保存到浏览器的 LocalStorage 中,让前端在每次向后端发送请求时,主动将 LocalStorage 的数据传递给服务端。这些都是由前端来控制的,后端需要做的仅仅是在用户登录成功后,将 Session Id (或 Token )放在响应体中传递给前端。
在这样的场景下,单点登录完全可以在前端实现。前端拿到 Session Id (或 Token )后,除了将它写入自己的 LocalStorage 中之外,还可以通过特殊手段将它写入多个其他域下的 LocalStorage 中。
方案示例:
下面的方案介绍都是基于父域:example.com,子域:sub.example.com
1)使用 iframe 实现跨域写入
通过嵌套跨域的 iframe,可以利用父页面和子页面之间的消息传递来实现跨域写入。
实现方式:
- 父域 example.com 通过 iframe 嵌套子域 sub.example.com。
- 父域生成 Token 后,通过 postMessage 将 Token 发送到子域的 iframe。
- 子域接收到消息后,将 Token 写入自己的 LocalStorage。
代码示例:
// 创建 iframe 指向子域 const iframe = document.createElement('iframe'); iframe.src = 'https://sub.example.com/storage.html'; iframe.style.display = 'none'; document.body.appendChild(iframe); // 发送 Token 到子域 iframe.onload = () => { iframe.contentWindow.postMessage({ token: 'your-session-id' }, 'https://sub.example.com'); };
子域监听来自父域的消息,接收到消息后,将 Token 写入自己的 LocalStorage:
// 监听来自父域的消息 window.addEventListener('message', (event) => { if (event.origin === 'https://example.com') { const { token } = event.data; if (token) { localStorage.setItem('sessionToken', token); } } });
2) 使用服务器中转跨域写入
客户端通过主域将 Token 写入服务器,其他子域通过服务器中转获取并写入自己的 LocalStorage。
举个例子:
主域 example.com 发送 Token 到服务器:
fetch('https://auth-server.com/store-token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ token: 'your-session-id' }) });
子域通过 AJAX 请求服务器获取 Token 并存储到 LocalStorage:
fetch('https://auth-server.com/get-token') .then(response => response.json()) .then(data => { if (data.token) { localStorage.setItem('sessionToken', data.token); } });
总结:此种实现方式完全由前端控制,几乎不需要后端参与,同样支持跨域。
4. SSO 单点登录退出
目前我们已经完成了单点登录,在同一套认证中心的管理下,多个产品可以共享登录态。现在我们需要考虑退出了,即:在一个产品中退出了登录,怎么让其他的产品也都退出登录?
原理其实不难,可以在每一个产品在向认证中心验证 ticket(token) 时,其实可以顺带将自己的退出登录 api 发送到认证中心。
当某个产品 c.com 退出登录时:
- 清空 c.com 中的登录态 Cookie。
- 请求认证中心 sso.com 中的退出 api。
- 认证中心遍历下发过 ticket(token) 的所有产品,并调用对应的退出 api,完成退出。
四、OAuth2.0 第三方登录
OAuth 2.0 是 OAuth 1.0 的一个升级版本,并在安全性、灵活性和使用便利性上做了大量的改进。
OAuth 第三方登录方式通常使用以下三种方式:
OAuth 机制实现流程
这里以微信开放平台的接入流程为例:
1)首先,a.com 的运营者需要在微信开放平台注册账号,并向微信申请使用微信登录功能。
2)申请成功后,得到申请的 appid、appsecret。
3)用户在 a.com 上选择使用微信登录。
4)这时会跳转微信的 OAuth 授权登录,并带上 a.com 的回调地址。
5)用户输入微信账号和密码,登录成功后,需要选择具体的授权范围,如:授权用户的头像、昵称等。
6)授权之后,微信会根据拉起 a.com?code=123 ,这时带上了一个临时票据 code。
7)获取 code 之后, a.com 会拿着 code 、appid、appsecret,向微信服务器申请 token,验证成功后,微信会下发一个 token。
8)有了 token 之后, a.com 就可以凭借 token 拿到对应的微信用户头像,用户昵称等信息了。
9)a.com 提示用户登录成功,并将登录状态写入 Cookie,以作为后续访问的凭证。
其他平台的接入方式可以去对应得官方文档查看,流程基本类似。
五、总结
上面四种登录实现方案,基本囊括了现有的登录验证方案,原理以及实现流程基本都了解。
1. Cookie + Session 历史悠久,适合于简单的后端架构,需开发人员自己处理好安全问题。
2. Token 方案对后端压力小,适合大型分布式的后端架构,但已分发出去的 token ,如果想收回权限,就不是很方便了。
3. SSO 单点登录
- SSO 单点登录适用于中大型企业,想要统一内部所有产品的登录方式的情况。
- SSO 单点登录主要关注 身份认证,允许用户在多个系统中只登录一次。
4. OAuth2.0第三方登录
- 简单易用,对用户和开发者都友好,但第三方平台很多,需要选择合适自己的第三方登录平台。
- OAuth2.0 登录主要关注授权,让第三方应用能够代表用户访问其资源。
- OAuth2.0 可以作为单点登录的一种实现方式。OAuth2.0 是一种授权框架,但它的认证过程(Authorization Code Grant 流程)也可以用来实现 SSO。当你在一个系统中登录后,OAuth2.0 可以作为身份验证的手段,在多个应用之间共享用户的登录状态。
参考链接:
https://juejin.cn/post/6933115003327217671