Skip to content

认证与系统集成:登录保护、单点登录与跨公司对接 ​

登录失败几次该锁定?锁多久?多个站点怎么共享登录状态?和外部公司交换数据用什么协议、怎么保证不被重放?这些问题的答案都不在框架文档里,而在业务的风险偏好里。

本文讨论三件事:登录的失败保护、跨站点单点登录的方案选择、与外部系统对接时的安全设计。

一、先说结论 ​

  • 登录失败保护要分层:按账号、按 IP、按设备分别计数,用滑动窗口而不是固定窗口,锁定时间递增。
  • 锁定策略要防「拒绝服务」:攻击者用错误密码反复尝试就能锁死别人的账号。所以账号锁定要配合 IP 维度判断,或者改用验证码而非直接锁定。
  • 单点登录的选择取决于域名关系:同主域用 Cookie 共享最简单;跨主域用标准协议(OIDC / CAS);App 与前后端分离场景用 Token。
  • 不要自己实现 OAuth/OIDC 的服务端,用成熟实现(Keycloak、Spring Authorization Server 等),自研的最大风险是安全细节漏项。
  • 跨公司对接的核心是三件事:身份认证(谁在调用)、防重放(同一请求不能生效两次)、防篡改(签名)。
  • 对外接口一定要有限流和配额,并且能按调用方精确定位问题。

二、登录失败保护 ​

2.1 计数窗口:为什么用滑动窗口 ​

固定窗口(每分钟计数清零)有明显的边界问题:攻击者可以在窗口末尾和下一个窗口开头各试 5 次,实际一瞬间试了 10 次。滑动窗口统计「最近 N 分钟」,没有这个缺口。

用 Redis 实现的滑动窗口计数:

java
// key 为 login:fail:{account},member 为唯一标识,score 为时间戳
public long recordFailure(String account) {
    String key = "login:fail:" + account;
    long now = System.currentTimeMillis();
    long windowStart = now - Duration.ofMinutes(15).toMillis();

    redis.opsForZSet().add(key, UUID.randomUUID().toString(), now);
    redis.opsForZSet().removeRangeByScore(key, 0, windowStart);   // 清掉窗口外的记录
    redis.expire(key, Duration.ofMinutes(30));                    // 兜底过期,防止 key 泄漏
    return redis.opsForZSet().zCard(key);                         // 窗口内的失败次数
}

三个细节:removeRangeByScore 保证窗口滑动;expire 防止长期不登录的账号留下垃圾 key;成功登录后要删除这个 key。

2.2 锁定策略与它的副作用 ​

常见的策略是「15 分钟内失败 5 次,锁定 30 分钟」。这里有一个必须正视的问题:攻击者只要知道账号,就能用错误密码把它锁死——锁定机制本身成了拒绝服务的手段。

更稳妥的组合:

触发条件动作理由
同一账号连续失败 3 次要求验证码拦住脚本,不影响真实用户
同一账号连续失败 10 次锁定,但允许通过邮箱或短信解锁给真实用户自助恢复的通道
同一 IP 短时间内失败多个账号按 IP 限流或封禁这才是典型的撞库特征
异地、异常设备登录二次验证密码泄漏的主要防线

锁定时长递增(1 分钟、5 分钟、30 分钟)比固定时长更合理:对误操作的真实用户友好,对持续攻击的成本递增。

2.3 其他要点 ​

  • 失败提示不要区分「用户不存在」和「密码错误」,否则等于提供了账号枚举接口。
  • 密码存储用 bcrypt、scrypt 或 Argon2,不要用 MD5、SHA 加盐——它们太快,适合被暴力破解。
  • 登录日志要完整:时间、IP、设备、结果、失败原因,这是事后排查和风控建模的基础。
  • 成功登录后重置计数,并考虑让旧会话失效(可配置)。

三、跨站点单点登录 ​

3.1 先看域名关系 ​

场景方案说明
同一主域的多个子域(a.corp.com、b.corp.com)Cookie 设置 Domain=.corp.com最简单,会话集中存储在 Redis
不同主域(shop.com、pay.com)标准协议:OIDC / OAuth 2.0 / CAS通过认证中心跳转换取令牌
App、小程序、前后端分离Token(JWT 或不透明令牌)客户端存储,随请求携带

3.2 跨主域的流程 ​

以 OIDC 为例(省略细节):

浏览器shop.com应用sso.corp.com认证中心1访问,未登录重定向到认证中心2已有会话则直接签发授权码3带授权码回跳4后端用授权码换 ID Token5 · 校验签名、iss、aud、exp、nonce
图 1 · 浏览器只经手授权码,令牌由应用后端直接向认证中心换取;最后一步的令牌校验最容易被省略,也最关键
  1. 用户访问 shop.com,未登录,被重定向到认证中心 sso.corp.com;
  2. 认证中心检查自己的会话 Cookie:已登录则直接签发授权码,未登录则展示登录页;
  3. 带授权码回跳到 shop.com 的回调地址;
  4. shop.com 的后端用授权码换取 ID Token 和 Access Token(这一步是服务端之间的调用);
  5. 校验 ID Token 的签名、iss、aud、exp、nonce,建立本地会话。

这一套流程里最容易出错的是第 5 步的校验。不要跳过任何一项,尤其是签名和 aud——不校验 aud 意味着别的应用的令牌也能登录你的系统。

3.3 令牌怎么选 ​

JWT(自包含)不透明令牌(随机串)
校验方式本地验签,无需查询每次调用认证中心校验(或查 Redis)
撤销困难,需要黑名单直接删除即可
体积较大,每次请求都带小
适合内部服务间、短有效期面向用户的会话

实践中常见的组合是:用户会话用不透明令牌(便于随时踢下线),服务间调用用短有效期的 JWT。如果一定要用 JWT 做用户会话,就把有效期设短(几分钟到几十分钟),配合刷新令牌,并准备好黑名单机制应对紧急撤销。

3.4 退出登录 ​

单点登录的反面是单点退出,它比登录更难:

  • 认证中心的会话要销毁;
  • 各个已登录的应用要收到通知并销毁本地会话(OIDC 的后端通道退出规范);
  • 已签发的 JWT 在过期前仍然有效,需要黑名单;
  • 移动端可能离线,要在下次请求时失效。

设计时先问清楚:业务要求的是「彻底退出」还是「当前设备退出」,两者的复杂度差很多。

四、跨公司数据对接 ​

和外部公司交换数据时,双方的信任边界很清楚:任何请求都可能是伪造的、重放的、被篡改的。

4.1 三件必须做的事 ​

1. 身份认证。 给每个合作方分配独立的 appId 和密钥,不要共用。密钥要能单独轮换和吊销。

2. 防篡改:请求签名。

text
签名串 = appId + timestamp + nonce + body 的哈希
signature = HMAC-SHA256(签名串, appSecret)

请求头携带 appId、timestamp、nonce、signature。服务端用同样的方式计算并比对。注意:签名要覆盖 body,否则内容可以被改;比较签名要用常量时间比较,避免时序侧信道。

3. 防重放。

  • timestamp 与服务器时间相差超过 5 分钟的请求直接拒绝;
  • nonce 存入 Redis(TTL 略大于时间窗口),重复出现就拒绝。

两者必须同时使用:只有时间戳,攻击者可以在 5 分钟内重放;只有 nonce,服务端要永久保存所有 nonce。

4.2 接口设计 ​

  • 必须 HTTPS,敏感字段(身份证号、银行卡号)在传输之外再单独加密。
  • 幂等:对方会重试,同一个业务请求 ID 只能生效一次,做法见 订单、库存与数据一致性。
  • 限流与配额:按 appId 限流,超出返回明确的错误码,不要让一个合作方影响其他人。
  • 版本化:接口路径或请求头带版本号,避免升级时互相绑架。
  • 错误码稳定:错误码是对外契约,不能随意改动含义。

4.3 数据推送与拉取 ​

模式适合注意
对方主动拉取数据量大、实时性要求不高分页、增量标记(时间戳或游标)、限流
我方推送回调状态变更通知要重试、要幂等、要能查询补偿
文件交换日终对账、批量数据校验和、文件命名规范、重复投递处理

推送类接口必须配一个查询接口:对方漏收或处理失败时可以主动查询补偿,这比无限重试更可靠。

五、常见误区 ​

  • 「登录失败就锁账号最安全」:这给了攻击者锁死任意账号的能力,要结合 IP 维度和验证码。
  • 「提示『用户不存在』更友好」:等于开放账号枚举。
  • 「JWT 是无状态的,所以更好」:无状态意味着难以撤销,用户会话场景要谨慎。
  • 「自己实现一套 SSO 更可控」:安全细节(PKCE、state、nonce、aud 校验)极易遗漏。
  • 「内网调用不需要签名」:内网也会被横向渗透,关键接口同样要认证。

小结 ​

这三个话题的共同点是:默认不信任。登录保护假设有人在撞库,单点登录假设令牌会泄漏,跨公司对接假设请求会被伪造和重放。对应的手段也很固定——分层计数与递增惩罚、标准协议与完整校验、签名加时间戳加 nonce。剩下的工作是把它们做全,而不是发明新方案。


参考资料

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权