Skip to content

登录方式

1. 传统用户名和密码登录

  • 特点:最基本的登录方式。
  • 流程:用户输入用户名和密码,服务器验证后允许访问。
  • 增强方式
    • 验证码(CAPTCHA):防止暴力破解。
    • 账户锁定:多次错误登录后锁定账户。
    • 密码强度检查:强制复杂密码策略。
  • 应用示例:各类论坛、电商网站(如 淘宝京东)。

登录流程概述

  1. 用户在前端(Vue) 输入用户名和密码。
  2. 前端调用后端(Spring Boot) 的登录 API。
  3. 后端验证用户信息
    • 检查用户名和密码是否正确。
    • 使用 哈希算法(如 BCrypt) 存储和验证密码。
  4. 后端生成 JWT Token 并返回给前端。
  5. 前端存储 JWT Token(通常使用 localStoragesessionStorage)。
  6. 后续 API 请求 使用 Authorization 头携带 JWT,实现登录后的身份认证。

2. 多因素认证(MFA)

  • 双因素认证(2FA):除了用户名和密码,还需要第二种验证方式。
    • 常见第二因素
      • 短信/邮件验证码(OTP)。
      • 基于时间的一次性密码(TOTP)(如 Google Authenticator)。
      • 硬件密钥(如 YubiKey)。

3. 社交账号登录(OAuth 2.0)

  • 使用第三方平台授权登录
    • 常见平台:Google、Facebook、GitHub、Twitter、WeChat 等。
    • 标准协议OAuth 2.0OpenID Connect
    • 优点:用户不需要额外注册,使用已有的社交账号登录。

4. 单点登录(SSO)

SSO 有什么好处?

  1. 用户角度 :用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。
  2. 系统管理员角度 : 管理员只需维护好一个统一的账号中心就可以了,方便。
  3. 新系统开发角度: 新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。

单点登录简介

  • 特点:用户登录一次即可访问多个关联系统。
    • 常用协议
      • SAML(Security Assertion Markup Language)
      • OAuth 2.0 / OpenID Connect
    • 应用场景:企业内部系统、教育机构等。

单点登录英文全称Single Sign On,简称就是SSO。它的解释是:在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

同域下的单点登录

一个企业一般情况下只有一个域名,通过二级域名区分不同的系统。比如我们有个域名叫做:a.com,同时有两个业务系统分别为:app1.a.com和app2.a.com。我们要做单点登录(SSO),需要一个登录系统,叫做:sso.a.com。

我们只要在sso.a.com登录,app1.a.com和app2.a.com就也登录了。通过上面的登陆认证机制,我们可以知道,在sso.a.com中登录了,其实是在sso.a.com的服务端的session中记录了登录状态,同时在浏览器端(Browser)的sso.a.com下写入了Cookie。那么我们怎么才能让app1.a.com和app2.a.com登录呢?这里有两个问题:

  • Cookie是不能跨域的,我们Cookie的domain属性是sso.a.com,在给app1.a.com和app2.a.com发送请求是带不上的。
  • sso、app1和app2是不同的应用,它们的session存在自己的应用内,是不共享的。

image

那么我们如何解决这两个问题呢?针对第一个问题,sso登录以后,可以将Cookie的域设置为顶域,即.a.com,这样所有子域的系统都可以访问到顶域的Cookie。我们在设置Cookie时,只能设置顶域和自己的域,不能设置其他的域。比如:我们不能在自己的系统中给baidu.com的域设置Cookie。

Cookie的问题解决了,我们再来看看session的问题。我们在sso系统登录了,这时再访问app1,Cookie也带到了app1的服务端(Server),app1的服务端怎么找到这个Cookie对应的Session呢?这里就要把3个系统的Session共享,如图所示。共享Session的解决方案有很多,例如:Spring-Session。这样第2个问题也解决了。

同域下的单点登录就实现了,但这还不是真正的单点登录。

不同域下的单点登录

同域下的单点登录是巧用了Cookie顶域的特性。如果是不同域呢?不同域之间Cookie是不共享的,怎么办?

这里我们就要说一说CAS流程了,这个流程是单点登录的标准流程。
cas_flow_diagram

上图是CAS官网上的标准流程,具体流程如下:

  1. 用户访问app系统,app系统是需要登录的,但用户现在没有登录。
  2. 跳转到CAS server,即SSO登录系统,以后图中的CAS Server我们统一叫做SSO系统。 SSO系统也没有登录,弹出用户登录页。(一般使用第三方SSO系统,如Auth0)
  3. 用户填写用户名、密码,SSO系统进行认证后,将登录状态写入SSO的session,浏览器(Browser)中写入SSO域下的Cookie。
  4. SSO系统登录完成后会生成一个ST(Service Ticket),然后跳转到app系统,同时将ST作为参数传递给app系统。
  5. app系统拿到ST后,从后台向SSO发送请求,验证ST是否有效。
  6. 验证通过后,app系统将登录状态写入session并设置app域下的Cookie。

至此,跨域单点登录就完成了。以后我们再访问app系统时,app就是登录的。接下来,我们再看看访问app2系统时的流程。

  1. 用户访问app2系统,app2系统没有登录,跳转到SSO。
  2. 由于SSO已经登录了,不需要重新登录认证。
  3. SSO生成ST,浏览器跳转到app2系统,并将ST作为参数传递给app2。
  4. app2拿到ST,后台访问SSO,验证ST是否有效。
  5. 验证成功后,app2将登录状态写入session,并在app2域下写入Cookie。

这样,app2系统不需要走登录流程,就已经是登录了。SSO,app和app2在不同的域,它们之间的session不共享也是没问题的。

有的同学问我,SSO系统登录后,跳回原业务系统时,带了个参数ST,业务系统还要拿ST再次访问SSO进行验证,觉得这个步骤有点多余。他想SSO登录认证通过后,通过回调地址将用户信息返回给原业务系统,原业务系统直接设置登录状态,这样流程简单,也完成了登录,不是很好吗?

其实这样问题时很严重的,如果我在SSO没有登录,而是直接在浏览器中敲入回调的地址,并带上伪造的用户信息,是不是业务系统也认为登录了呢?这是很可怕的。

总结

单点登录(SSO)的所有流程都介绍完了,原理大家都清楚了。总结一下单点登录要做的事情:

  • 单点登录(SSO系统)是保障各业务系统的用户资源的安全 。
  • 各个业务系统获得的信息是,这个用户能不能访问我的资源。
  • 单点登录,资源都在各个业务系统这边,不在SSO那一方。 用户在给SSO服务器提供了用户名密码后,作为业务系统并不知道这件事。 SSO随便给业务系统一个ST,那么业务系统是不能确定这个ST是用户伪造的,还是真的有效,所以要拿着这个ST去SSO服务器再问一下,这个用户给我的ST是否有效,是有效的我才能让这个用户访问。

参考:

5. 无密码登录(Passwordless Authentication)

  • 方式
    • 一次性链接/验证码:用户输入邮箱/手机号,收到登录链接或验证码。
    • 生物识别:如指纹、面部识别(通过 WebAuthn)。
    • 硬件认证:如 FIDO2/WebAuthn 标准,结合安全密钥(YubiKey)。

6. 生物识别登录

  • 基于设备的生物识别技术
    • 指纹面部识别虹膜扫描等。
    • WebAuthn 是支持浏览器直接调用生物识别硬件的标准。

7. API Token / JWT 登录

  • 特点:主要用于前后端分离架构和 API 调用。
  • 适用场景:API 调用或无状态认证。
    • 流程:登录后返回一个 JWT(JSON Web Token)或 API Token,后续请求使用该 Token 进行身份验证。
    • 特点:无状态、高效、支持分布式系统。
  • 应用示例:GitHub API、Twitter API、各类 RESTful API。

API Token / JWT 登录 是一种基于令牌的身份验证机制,广泛应用于前后端分离的 Web 应用、移动应用以及微服务架构中。下面是对该方式的详细说明:


1. 基本概念

  • API Token
    通常指在用户通过凭证(如用户名、密码)登录后,由服务器生成的令牌。这个令牌会作为身份凭证,附加在后续请求中,用于验证用户身份。

  • JWT(JSON Web Token)
    是一种开放标准(RFC 7519),用于在各方之间安全地传递 JSON 格式的声明。JWT 通常由三部分组成:

    1. Header(头部):描述令牌的类型及所使用的签名算法。
    2. Payload(载荷):包含用户信息和声明(例如用户 ID、角色、过期时间等)。
    3. Signature(签名):使用密钥对 Header 和 Payload 进行签名,以确保数据的完整性和来源可信。

2. 工作流程

  1. 登录请求

    • 用户向服务器发送包含用户名和密码的登录请求。
  2. 服务器验证

    • 服务器验证用户凭证后,生成一个 JWT。
    • 该 JWT 包含用户的身份信息和必要的声明(如过期时间)。
  3. Token 返回与存储

    • 服务器将 JWT 返回给客户端。
    • 客户端通常将该 Token 存储在 localStorage、sessionStorage 或 HTTP-only Cookie 中(建议使用 HTTP-only Cookie 以防 XSS)。
  4. 后续 API 请求

    • 客户端在访问受保护的 API 时,将 JWT 作为 Authorization 头的一部分发送:
http
 Authorization: Bearer <JWT>
  1. Token 验证

    • 后端在每个请求中验证 JWT 的签名和有效性(例如检查是否过期),并根据 Token 载荷中的信息授权访问。
  2. Token 刷新(可选)

    • 当 JWT 过期时,系统可采用 Refresh Token 机制,使用户无需重新登录即可获取新的 JWT。

3. 示例代码

后端(示例:Spring Boot)

在 Spring Boot 中,通过配置 spring-boot-starter-oauth2-resource-server 可轻松实现 JWT 验证:

yaml
# application.yml 示例
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://your-auth-server.com/issuer
          jwk-set-uri: https://your-auth-server.com/.well-known/jwks.json
java
// 示例 Controller
@RestController
public class HelloController {
    @GetMapping("/api/hello")
    public String hello(Principal principal) {
        return "Hello, " + principal.getName() + "!";
    }
}

前端(示例:JavaScript 使用 Axios)

javascript
// 获取 JWT Token 后调用 API 示例
async function fetchProtectedData() {
  const token = localStorage.getItem('jwtToken'); // 假设 Token 存储在 localStorage 中
  try {
    const response = await axios.get('https://your-api.com/api/hello', {
      headers: { Authorization: `Bearer ${token}` }
    });
    console.log('API 响应:', response.data);
  } catch (error) {
    console.error('访问 API 失败:', error);
  }
}

4. 优点与挑战

优点

  • 无状态认证
    服务器无需存储会话信息,令牌内含所有必要的用户信息,有助于分布式系统的扩展。

  • 灵活性
    JWT 可包含各种自定义声明,便于实现细粒度的访问控制。

  • 跨平台兼容
    广泛适用于 Web、移动端以及 API 服务,支持多种编程语言和框架。

挑战与注意事项

  • 安全存储
    JWT 应妥善存储,避免 XSS 攻击。建议将敏感 Token 存储在 HTTP-only Cookie 中。
  • Token 过期与刷新
    需要设计合理的过期时间和刷新机制,确保用户体验与安全性平衡。
  • Token 大小
    如果载荷中存储过多信息,JWT 可能会变得庞大,影响网络传输效率。

5. 总结

API Token / JWT 登录 是一种现代化、无状态的身份验证机制,通过在客户端和服务器之间传递 JWT,既简化了服务器端会话管理,又提高了系统的扩展性与灵活性。正确的实现需要在安全存储、有效期管理以及刷新机制上投入足够关注,以确保既满足业务需求又保障用户数据安全。

参考

8. 第三方身份验证服务

  • 常用身份验证平台
    • Auth0
    • Firebase Authentication
    • Okta
    • Amazon Cognito

这些服务通常提供多种认证方式(传统登录、社交登录、MFA 等)并简化实现过程。

  • 优点:集成简单,支持多种登录方式。
  • 应用示例:一些 SaaS 平台使用这些服务快速集成认证功能。

各登录方式对比

登录方式安全性用户体验适用场景
用户名+密码常规应用,简单场景
用户名+密码+MFA金融、企业级应用、高安全性要求场景
社交登录(OAuth 2.0)中-高B2C 应用,注重用户增长
单点登录(SSO)企业内网、教育系统、多系统协作环境
无密码登录B2C 应用、移动端、注重用户体验的产品
生物识别登录移动端、金融类 APP、安全敏感的应用
JWT / API TokenAPI 认证、微服务架构、前后端分离项目

选择登录方式的考虑因素

  1. 安全性:是否需要防止暴力破解?是否需要多重认证?
  2. 用户体验:用户是否愿意接受多步骤认证?是否倾向于便捷的登录方式?
  3. 业务需求:是否需要支持第三方登录?是否有分布式架构的需求?
  4. 开发复杂性:现有团队是否有能力实现安全的认证系统?是否考虑使用第三方认证服务?

登录状态保持

1. 传统登录状态保持的两种方式

  • 机制

    1. 用户登录后,后端生成一个 Session ID
    2. Session ID 存储在浏览器的 Cookie 中。
    3. 之后的每个请求都会自动携带该 Session ID,后端通过此 ID 识别用户。
  • 特性

    • 同域共享:在同一域名下(a.com),所有标签页和窗口都会共享 Session
    • 跨标签页生效:因为 Session ID 存在于 Cookie 中,打开新的 a.com 标签页仍然是登录状态。
    • Session 失效:关闭浏览器或达到会话超时时间后,Session 失效,需要重新登录。
  • 示例场景:大多数基于 Spring Boot + Session 的 Web 应用使用此机制。

  • 登录过程

    • 浏览器登录发送账号和密码,服务端查找数据库验证用户
    • 验证成功后,服务端把用户状态(登录状态,角色,权限等信息)存为Session,生成一个SessionId
    • 通过接口将SessionId返回到浏览器,表示用户验证成功,登录完成。浏览器将SessionId存在cookie中
    • 此后浏览器再请求业务接口,服务端发送http请求,并且会将cookie一起发送到服务端
    • 服务端从cookie中拿到SessionID(如果没有SessionID就是第一次登录),和session中的数据进行比对,比对成功,并且有访问权限,就允许访问,否则就访问失败。

Session Session的具体内容都是存储在服务端,只是给客户端一个SessionId存在cookie。Session的存储方式有:

  • Redis:推荐用Redis存储,以key-value形式存,刚好符号sessionId-sessionData的场景,访问更快
  • 内存:直接放到变量里,一旦服务重启就没有了
  • 数据库:存在磁盘里,性能不高

Session的分布式问题 通常服务端是集群,而用户请求会走负载均衡,不一定就能请求到登录时的服务端。一旦用户后续接口请求到的机器和之前登录请求的机器不一致,或者登录请求的机器宕机了,session就没用了

解决方式: 把所有用户的session集中存储,我们可以用独立的Redis或者普通数据库(推荐是Redis)或者消息队列 ,这就是分布式session:

  • 当用户第一次访问应用时,应用会为用户生成一个唯一的会话标识符(session ID),并将该标识符返回给用户
  • 当用户发送后续请求时,会将会话标识符作为请求的一部分发送回服务器。服务器可以利用该会话标识符来识别用户,并获取保存在分布式存储中的会话状态 。
  • 分布式存储可以是数据库、缓存、消息队列等,它们可以在多个应用服务器之间共享数据。当一个应用服务器接收到请求时,它可以根据会话标识符查询分布式存储,获取用户的会话状态,并进行相应的处理。

缺点:

  • 随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高
  • session存储在服务端,如果用户很多的话,服务端保存大量数据,增加服务端的压力
  • 服务从单服务到多服务会面临session共享问题,要考虑分布式的问题

b. 基于 Token(如 JWT)的登录状态

  • 机制

    1. 用户登录后,后端生成一个 JWT Token
    2. 前端将 JWT 存储在:
      • localStorage:跨标签页共享,但关闭浏览器仍然存在。
      • sessionStorage:仅当前标签页有效,关闭页面即失效。
      • Cookie(HTTP-only):自动随请求发送,提升安全性。
    3. 之后的请求携带 JWT Token 进行身份验证。
  • 特性

    • localStorage:跨标签页共享,关闭浏览器后仍然存在(除非手动清除)。
    • sessionStorage:仅在当前标签页有效,打开新标签页需要重新登录。
    • HTTP-only Cookie:与 Session 类似,跨标签页有效,但更安全(防止 XSS 攻击)。
  • 示例场景:前后端分离的应用(如 Vue + Spring Boot)通常使用 JWT

  • 流程

    • 用户登录:用户使用用户名和密码发送登录请求到服务器。服务器验证用户的身份信息,并验证成功后生成一个唯一的JWT令牌(token)。
    • JWT令牌生成:服务器生成一个包含用户身份验证信息的JWT令牌,并将其返回给客户端 。JWT令牌通常是一个加密的字符串,其中包含了用户的身份信息、授权信息和有效期 等。
    • JWT令牌存储:服务器将生成的JWT令牌存储在服务器端的缓存或数据库 中,以便后续验证和访问控制。
    • JWT令牌传递:客户端将接收到的JWT令牌存储在本地,通常可以使用Cookie或LocalStorage 等技术来存储。
    • 请求验证:当客户端发送后续请求到服务器时,需要将JWT令牌放在请求中的请求头、参数或Cookie中,以便服务器进行验证。
    • JWT令牌验证:服务器接收到请求后,从请求中获取JWT令牌,并与服务器存储的令牌进行比较和验证 。验证包括检查令牌的有效性、合法性以及是否过期等。
    • 授权访问:如果JWT令牌验证通过,服务器会根据JWT令牌中的身份信息进行授权验证,以确定用户是否有权限进行请求的操作。
    • 响应返回:服务器根据验证和授权结果返回相应的响应结果给客户端。

2. 状态共享的实际效果

存储方式是否跨标签页共享是否跨域共享关闭浏览器是否失效安全性
Session + Cookie✅(依赖配置)中等(易受 CSRF)
JWT + localStorage较低(易受 XSS)
JWT + sessionStorage✅(关闭标签页即失效)较高(但不跨标签页)
JWT + HTTP-only Cookie✅(依赖 Cookie 设置)高(防止 XSS 和 CSRF)

3. 如何避免跨标签页共享登录状态?

如果希望在打开新标签页时要求重新登录(例如在银行、支付等安全性高的场景),可以考虑以下方法:

  1. 使用 sessionStorage 存储 Token

    • 只在当前标签页有效,关闭页面后失效。
    • 缺点:无法在多个标签页间共享登录状态。
  2. 增加一次性 Token(如 CSRF Token)

    • 每次请求都需要额外的安全令牌,避免伪造请求。
  3. 短生命周期 Token + 刷新机制

    • Access Token 有较短的有效期(如 5-10 分钟)。
    • 配合 Refresh Token 实现自动续签。
  4. 多标签页间通信限制

    • 通过监听 localStorage 事件或使用 BroadcastChannel API 控制登录状态同步。
    • 可以实现“登出所有标签页”或“禁止多标签页同时使用”等功能。

refresh-token机制

Refresh Token 机制简介

Refresh Token 是一种用于延长用户登录状态的安全机制,常与 Access Token 一同使用,特别是在基于 JWT 的认证系统中。


1. 作用与意义

  • 短期 Access Token:为降低风险,Access Token 通常设置较短的有效期(如 15 分钟到 1 小时),即使被窃取,其使用时间也有限。
  • 长效 Refresh Token:Refresh Token 有较长的有效期,用于在 Access Token 过期后,帮助客户端向服务器请求新的 Access Token,从而无需用户重新登录,提升用户体验。

2. 工作流程

  1. 登录获取 Token
    用户成功登录后,服务器返回一对令牌:

    • Access Token:用于身份验证,短期有效
    • Refresh Token:用于刷新 Access Token,长期有效
  2. 访问受保护资源
    客户端在请求受保护的 API 时,携带 Access Token(通常放在 HTTP 头部)。

  3. Access Token 过期
    当 Access Token 过期时,客户端会检测到认证失败。

  4. 刷新 Access Token
    客户端使用 Refresh Token 发送请求给服务器,请求新的 Access Token。

    • 服务器验证 Refresh Token 的有效性。
    • 验证成功后,服务器返回新的 Access Token(有时也会返回新的 Refresh Token)。
  5. 继续访问
    客户端收到新 Access Token 后,更新本地存储,再继续正常发起 API 请求。

  6. 失效处理
    如果 Refresh Token 也失效或被撤销,客户端需要引导用户重新登录获取新的 Token 对。


3. 安全存储和管理

  • 存储建议
    由于 Refresh Token 生命周期长、权限高,建议将其存储在HTTP-only Cookie中,避免通过 JavaScript 访问,降低 XSS 攻击风险。

  • 生命周期管理
    需要设计合理的过期时间和刷新策略,以平衡用户体验与安全性。通常,Access Token 设置短期有效,而 Refresh Token 可设置较长的有效期,但在每次刷新时应进行严格验证。

  • 安全风险
    一旦 Refresh Token 泄露,攻击者可能长期获取新的 Access Token,因此需要额外的安全措施,如设备绑定、多因素认证以及检测异常刷新行为等。


4. 优点与挑战

  • 优点

    • 提升用户体验:用户无需频繁登录。
    • 降低风险:短期 Access Token 有助于减少被滥用的时间窗口。
  • 挑战

    • Token 管理复杂:需要处理 Token 刷新、撤销、失效检测等问题。
    • 安全性要求高:Refresh Token 的存储和传输必须非常谨慎,防止长期被恶意利用。

5. 示例流程

  1. 用户登录
    服务器返回:
json
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
  "refresh_token": "def50200accc6a3b9e9e3f8f...",
  "expires_in": 900, // 15 分钟
  "token_type": "Bearer"
}
  1. 访问 API
    客户端在请求头中携带 Authorization: Bearer <access_token>

  2. Token 刷新
    当 API 返回认证错误时,客户端发起刷新请求:

http
    POST /auth/refresh-token
    Content-Type: application/json
    
    {
      "refresh_token": "def50200accc6a3b9e9e3f8f..."
    }

服务器验证后返回新的 Access Token。


Refresh Token 机制是现代认证系统中保持用户长时间登录而不牺牲安全性的关键技术,正确实施可以在提供无缝用户体验的同时,有效降低因 Token 被窃取带来的风险。

6.安全性优点

Refresh Token 的安全性主要体现在以下几个方面,而不是简单地让它存在于客户端后直接用于获取新的 Access Token:

  1. 安全存储

    • Refresh Token 应存储在安全的位置(例如 HTTP-only Cookie),防止被 JavaScript 直接访问,从而降低 XSS 攻击的风险。
    • 如果直接暴露在 localStorage 中,恶意脚本一旦执行,就可能窃取 Refresh Token,从而危害整个认证系统。
  2. 用途受限

    • Refresh Token 仅用于在 Access Token 过期时获取新的 Access Token,不会直接访问受保护资源。
    • 因此,即便 Access Token 被短暂窃取,其使用时间窗口非常有限,而 Refresh Token 则是长期有效但在安全通道下使用。
  3. 额外安全机制

    • 绑定和轮换机制:很多系统会将 Refresh Token 绑定到特定的客户端或设备,并采用轮换(rotating refresh token)机制,即每次使用后刷新令牌都会失效旧的 Refresh Token,这样即使一个 Refresh Token 被窃取,也不能反复使用。
    • 服务端检测和撤销:服务器可以监控 Refresh Token 的使用模式,一旦检测到异常行为(如来自不同 IP 的频繁刷新),可以主动撤销 Refresh Token,从而防止滥用。
  4. 风险隔离

    • 由于 Access Token 的有效期很短,即使被窃取,其利用价值也受到限制。Refresh Token 则只在安全的环境下被用来续签新的 Access Token,所以在设计上形成了一个风险隔离的层次结构,进一步降低了整体风险。

评论区

欢迎留言、补充或勘误。

xiaoba.blog