一、Session+Cookie登录
传统的session+cookie登录是一种有状态 的登录
1、传统的session+cookie 流程
- 浏览器登录发送账号和密码,服务端查找数据库验证用户
- 验证成功后,服务端把用户状态(登录状态,角色,权限等信息)存为Session,生成一个SessionId
- 通过接口将SessionId返回到浏览器,表示用户验证成功,登录完成。浏览器将SessionId存在cookie中
- 此后浏览器再请求业务接口,服务端发送http请求,并且会将cookie一起发送到服务端
- 服务端从cookie中拿到SessionID(如果没有SessionID就是第一次登录),和session中的数据进行比对,比对成功,并且有访问权限,就允许访问,否则就访问失败。
2、cookie
cookie是前端存储的一种,但相比于localStorage 等其他方式,借助HTTP头、浏览器能力,cookie可以做到前端无感知。一般过程是这样的:
- 在提供标记的接口,通过HTTP返回头的Set-Cookie字段,直接"种"到浏览器上
- 浏览器发起请求时,会自动把cookie通过HTTP请求头的cookie字段,带给接口
cookie是维持HTTP请求状态的基石,是最便捷的维持HTTP请求状态 的方式。
3、Session
Session的具体内容都是存储在服务端,只是给客户端一个SessionId存在cookie。Session的存储方式有:
- Redis:推荐用Redis存储,以key-value形式存,刚好符号sessionId-sessionData的场景,访问更快
- 内存:直接放到变量里,一旦服务重启就没有了
- 数据库:存在磁盘里,性能不高
Session的分布式问题
通常服务端是集群,而用户请求会走负载均衡,不一定就能请求到登录时的服务端。一旦用户后续接口请求到的机器和之前登录请求的机器不一致,或者登录请求的机器宕机了,session就没用了
解决方式:
把所有用户的session集中存储,我们可以用独立的Redis或者普通数据库(推荐是Redis)或者消息队列 ,这就是分布式session:
- 当用户第一次访问应用时,应用会为用户生成一个唯一的会话标识符(session ID),并将该标识符返回给用户
- 当用户发送后续请求时,会将会话标识符作为请求的一部分发送回服务器。服务器可以利用该会话标识符来识别用户,并获取保存在分布式存储中的会话状态 。
- 分布式存储可以是数据库、缓存、消息队列等,它们可以在多个应用服务器之间共享数据。当一个应用服务器接收到请求时,它可以根据会话标识符查询分布式存储,获取用户的会话状态,并进行相应的处理。
4、缺点:
-
随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高
-
session存储在服务端,如果用户很多的话,服务端保存大量数据,增加服务端的压力
-
服务从单服务到多服务会面临session共享问题,要考虑分布式的问题
二、token+cookie
流程 :
- 用户登录:用户使用用户名和密码发送登录请求到服务器。服务器验证用户的身份信息,并验证成功后生成一个唯一的令牌(token)。
- 令牌生成:服务器生成一个包含用户身份验证信息的令牌,并将其返回给客户端 。令牌通常是一个加密的字符串,其中包含了用户的身份信息、授权信息和有效期 等。
- 令牌存储:服务器将生成的令牌存储在服务器端的缓存或数据库 中,以便后续验证和访问控制。
- 令牌传递:客户端将接收到的令牌存储在本地,通常可以使用Cookie或LocalStorage 等技术来存储。
- 请求验证:当客户端发送后续请求到服务器时,需要将令牌放在请求中的请求头、参数或Cookie中,以便服务器进行验证。
- 令牌验证:服务器接收到请求后,从请求中获取令牌,并与服务器存储的令牌进行比较和验证 。验证包括检查令牌的有效性、合法性以及是否过期等。
- 授权访问:如果令牌验证通过,服务器会根据令牌中的身份信息进行授权验证,以确定用户是否有权限进行请求的操作。
- 响应返回:服务器根据验证和授权结果返回相应的响应结果给客户端。
优点 :
可以隐藏真实数据,避免明文传输。ie里存的是一个32位的uuid
使用于分布式/微服务,解决session共享问题
安全系数高
缺点 :
存在redis,必须依赖服务器,会占用服务器的资源
效率较低,不过比起传统的session+cookie好多了
三、JWT实现无状态登录
JWT全程是 JSON WEB TOKEN,JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息 ,也可以增加一些额外的其他业务逻辑所必须的声明信息,该token也可以直接被用于认证,也可被加密
JWT的组成部分 :
- Header:头,记录令牌类型、签名算法等
- Payload:有效载荷,携带一些用户信息
- Signature:签名,防止token被篡权,保证安全性。这个签名由头部和有效负载的Base64编码字符串以及使用指定算法加密的秘密密钥组成。
使用JWT登录过程:
-
用户提供用户名和密码进行认证。在认证成功后,服务器会生成一个JWT,在其中包含用户的一些身份信息,并使用秘钥 对其进行签名。
-
服务器将生成的JWT返回给客户端。客户端通常将JWT存储在本地,例如在 localStorage 或者cookie中。
-
客户端在后续的请求中,在请求的头部或者其他合适的位置添加JWT作为身份验证凭证。一般使用Authorization头部
-
服务器在接收到请求时,会验证JWT的有效性以及签名。
在进行签名验证时,需要使用之前生成JWT时使用的相同的秘钥 ,否则将无法正确验证签名,导致JWT无效;
服务端会检查JWT的各种声明和标准字段,如签发人(issuer)、受众人(audience)、过期时间(expiration time)等。根据业务需求,服务端可以自行定义额外的声明字段。
-
如果验证通过,表示用户是合法的,并且可以使用JWT中包含的信息来进行身份验证和授权。
优点:
- 实现无状态的认证机制,服务端不需要存储用户的会话信息,只需要校验JWT的合法性即可,减轻服务端的压力
- JWT可以带有自定义的信息,使得身份验证非常灵活 ,可自行定制所需的信息
- JWT被签名后,任何篡改都可以被检测到,提供了一定的安全性
缺点 :
- 建议不要放敏感数据,因为JWT是一种基于编码的token,使用base64进行编码,而不是加密 ,虽然JWT的签名可以确保token在传输过程中没有被篡改,但是无法保证token的内容不被解码和泄露 ;敏感数据应该存储在安全的服务器端,并仅在需要时通过安全的传输方式在服务端进行处理
- JWT一旦生成,无法吊销令牌,只能等待令牌自身过期 。所以需要注意的是,JWT的有效期应该设置合理,限制JWT的使用时间,并在过期后重新认证获取新的令牌
- 令牌长度与其包含用户信息多少正相关,传输开销较大
JWT如何保证不被篡改?
JWT使用签名 来防止被篡改。在生成JWT时,使用一个密钥对JWT的头部和有效负载进行签名。当服务端接收到JWT时,首先会对JWT进行解码,然后使用相同的密钥对解码后的JWT的头部和有效负载重新进行签名 。然后,将重新计算的签名与原始JWT中的签名进行比较。如果重新计算的签名与原始JWT中的签名不一致,说明JWT已被篡改,验证失败 。
通过使用签名来验证JWT的完整性,可以有效地防止JWT被篡改。任何对JWT进行篡改的尝试都会被服务端检测到。同时,由于JWT中包含了签名算法和秘密密钥,攻击者无法伪造一个合法的签名。需要注意的是,服务端验证JWT时,必须使用与生成JWT时相同的秘密密钥来进行签名验证,否则验证会失败。同时,为了增强安全性,应该选择足够强大的签名算法和秘密密钥,以防止密钥泄露和暴力破解的攻击 。
四、单点登录
当业务线越来也多,就会有更多业务系统分散到不同域名下(不同的系统),就需要(一次登录,全线通用)的能力,这就是单点登录。
1、"虚假的单点登录",使主域名相同
我们知道,cookie是有限制的,这个限制就是cookie的域(通常对应网站的域名),浏览器发送http请求时会自动携带与该域匹配的cookie,而不是所有cookie 。
因此,我们可以将web应用群中所有子系统的域名统一在一个顶级域名下,采用同域名共享cookie的方式。这样也能实现一次登录,全线通用。
但是共享cookie的方式存在众多局限:
-
所有的子系统域名要统一;
-
应用群各系统使用的技术要相同,并且共享cookie的方式是无法实现跨语言技术平台登录的
2、"真实"的单点登录
当主域名不同时,实现一次登录,全线使用,这才是真正的单点登录。在这种场景下,我们需要独立的认证服务,通常被称为 SSO(Single Sign On)
- 当用户访问A系统,没有登录凭证(ticket),A系统让它重定向到SSO
- 用户没有在SSO登录,SSO系统下就没有凭证(这个和ticket不是一个东西),用户输入账号和密码进行登录
- SSO校验账号密码成功,通过接口返回,做两件事:一在SSO系统种下凭证(记录用户的登录状态);二是给用户下发一个ticket
- 客户端拿到tikcet,保存起来,带着ticket再次访问系统A
- SSO校验ticket,成功后正常处理业务请求
- 此时用户第一次访问系统B,没有ticket,B系统让它重定向到SSO
- 因为用户在SSO登录过,SSO有凭证,不用再次登录,只需要下发ticket
- 客户端拿到ticket,保存起来,带着ticket请求系统B
- SSO校验ticket,成功后正常处理业务请求
只有SSO有登录账号密码的功能,其他系统都没有。用户在SSO登录后,SSO就会存储一个用户的登录凭证(这个是全局会话),然后根据重定向过来的地址,颁发要访问的系统的ticket。比如用户是从A系统重定向过来的,就颁发一个A系统的ticket(局部会话),客户端带着这个ticket就可以访问A系统了;当用户要访问B系统时,B系统将它重定向到SSO,已经登录了,所以SSO直接颁发可以访问B系统的ticket,客户端带着这个ticket就可以访问B系统了。这样就实现了一处登录,全线使用
但是这样有一个问题,SSO放回的ticket,浏览器要如何存,因为有很多个域名下,如何才能在下次访问A系统时带上这个ticket?因为浏览器对跨域有严格限制,所以我们需要在A域下保存这个A的ticket
- 在 SSO 域下,SSO 不是通过接口把 ticket 直接返回,而是通过一个带 code 的 URL 重定向到系统 A 的接口上 ,这个接口通常在 A 向 SSO 注册时约定
- 浏览器被重定向到 A 域下,带着 code 访问了 A 的 callback 接口,callback 接口通过 code去SSO中 换取 ticket
- 这个 code 不同于 ticket,code 是一次性的,暴露在 URL 中,只为了传一下换 ticket,换完就失效
- callback 接口拿到 ticket 后,在**自己的域下 set cookie ** 成功
- 在后续请求中,浏览器只需要把 cookie 中的 ticket 解析出来,然后去SSO 中 验证就可以了
五、总结
1、HTTP 是无状态的,为了维持前后请求,需要前端存储标记-----cookie
2、cookie 是一种完善的标记方式,通过 HTTP 头或 js 操作,有对应的安全策略,是大多数状态管理方案的基石
3、session 是一种状态管理方案,前端通过 cookie 存储 id,后端存储数据,但后端要处理分布式问题
4、token 是另一种状态管理方案,token 的编码技术,通常基于 base64,或增加加密算法防篡改;jwt 是一种成熟的编码方案,并且JWT实现了无状态登录,相较于传统的有状态登录,更加灵活,解放服务端
5、session 和 token 的对比就是「用不用cookie」和「后端存不存」的对比
6、单点登录要求不同域下的系统「一次登录,全线通用」,通常由独立的 SSO 系统记录登录状态、下发 ticket,各业务系统配合存储和认证 ticket