高可用 高扩展 高安全 的 登陆与注册的设计 one

设计并实现了 完整的 高安全性,高可用,高扩展的 登陆与注册 服务

脱讲稿

简介 : 设计并实现了 完整的 高安全性,高可用,高扩展的 登陆与注册 服务

自述 :

我给系统设计了一个支持 手机号码、微信登陆、邮箱注册 的登陆与注册服务。 使用 长短JWT + Session 控制用户的登陆登出。 处理了跨域和限流问题,并且对于手机登陆支持切换云服务商 支持 failover 策略。功能拥有完整的链路控制,并且使用 wrk 进行压测。

高安全性 :

  1. 做了限流, 手机验证, 设备验证

高可用 :

  1. 支持切换多个云服务商

高扩展的 :

  1. 基于面向接口编程. 并且采用 DDD 设计 支持 repo 层切换多个存储方案,支持 SMS 和 微信登陆切换各种服务

问题 1

  1. 什么是 Gin 的 middle-ware ? 能解决什么问题 ?

Gin 框架提供的一种 AOP 编程机制, 支持实际业务逻辑前后进行插入额外处理 。 能够解决以下几类问题

Web治理 : 实现 http 层面的熔断、限流、降级

可观测性 : 可以聚合日志,metrics,tracing

身份的认证与鉴权


  1. 什么是跨域问题,怎么解决 ?

跨域的问题本质是 请求发送方和接收方 host 和 端口不匹配的问题 。 对于跨域问题,浏览器会预先发送一个 preflight option方法,询问允许访问的范围 。

Gin 框架支持使用 Cros 中间件进行处理跨域问题,设置对应的 allow-origins,allow-headers,allow-method即可


  1. 跨域问题需要设置哪些头部 ? 上面的已经回答

  1. 什么是 cookie,什么是 session ?

http 本身是无状态协议,为了保护用户的登陆状态,浏览器支持本地存储一个 kv 数据, 这个数据可以理解为 cookie

由于 cookie 的安全性问题,隐私信息一般不存放在 cookie中,因此引入 session 机制

session 机制允许 绑定一个 session_id 给前端,具体身份校验逻辑可以存放在后端避免隐私信息泄漏


  1. cookie 和 session 比起来有什么缺点 ?

cookie 不依赖后端存储,并且本身可以设置一些安全性配置 例如 domain,expires,secure

缺点的话就是 不允许存放敏感信息,容易丢失

–

  1. Session ID 可以放在哪里? 如果Cookie禁用

cookie, header, redis,本地缓存,sql 等各种地方


  1. 用户密码加密算法的选取

尽可能选择高安全系数的,不宜破解的 。 技术选型的时候考虑过 md5,pbkdf2,bcrypt

因为 md5 需要在数据库中额外存储盐值的原因最终选择 bcrypt

使用 wrk 压测的时候,发现 密码加密和不加密的情况下 性能相差10倍, 但是这是不可避免的


  1. 怎么做登陆校验 ?

正确性校验 :

  1. 在登陆的时候 获取对应的账号和密码,去数据库中查找,并且使用 bcrypt 进行比对 正确的话 则返回对应的 jwt 用于保护用户状体啊

状态维护:

  1. 每次获取对应的 短token进行 jwt解析,解析失败则登陆失败

安全性处理:

  1. 在 JWT 中维护一个 user-agent字段,每次比对该字段是否相同

  2. 使用 Redis 限流控制 每次的登陆


  1. 刷新 Session 过期时间的几种方案

  2. 快要过期的时候刷新 ; 这个可能会有 gap

  3. 每次访问都刷新 。会频繁的读redis

  4. 固定时间刷新 , 增加 updateAt字段


  • 怎么保护 session_id ?主要还是启用 https协议,设置 cookie 的 secure

  • 怎么做到 在session_id 和 jwt-token泄漏之后保护着客户 ? 记录登陆的额外信息

  • 如何保护 Web 服务 ? 针对 IP限流、整个集群限流

这几个问题主要在问题 8 会引申

问题 2

  1. 你用 Redis 解决过什么问题?

业务上 :

  1. 用来标记某些 handle 是否完成。 例如某些需要在全环境修复的任务

  2. 分布式锁 控制针对于 IP 限流的一些账户创建

  3. Stream 实现的轻量化 mq 队列 用于实现异步的消费

  4. 存储一些实时性不高的 例如快照的信息

  5. 使用 Zset 进行热榜功能的排序

  6. 使用 Redis 单机 线程安全的特性 实现限流


  1. 你知道 Redis 支持哪些数据结构,用过哪些 ? 用来解决什么问题?

string,list,hash,set,zset . 问题 1 的扩展


  1. 各个数据结构的底层实现 ?

不知道 ,有对应的 memo 。 预计会单独开辟一个文章


  1. 当你更新数数据的时候 先更新数据库还是先更新缓存,有没有一致性问题

先更新数据库,然后删除缓存。 对于更新操作 采用 Cahce-Aside 的方式 会有一致性问题

我们只有在 Get 操作的时候才会重新写入缓存

(这里说实话,忘记了开发的时候的想法了,创建一个memo处理一下)

  1. 如何解决一致性问题

这里主要是 check-do thing 的模型吧 。 尽可能使用原子操作 ,例如 lua 或者是 atomic

(同样单独使用 memo 进行处理)

问题 3

  1. 什么是依赖注入,如何在 Go 实现 依赖注入

依赖注入,就是 A 要调用 B 上面的方法, A 在构造的时候要求传入构造好的 B 而不是自己初始化一个 B 。

通过引入 wire包进行依赖注入


  1. 什么是面向接口编程 ? 为什么要面向接口编程 ?

业务之间 组件和组件的通信必须依赖接口 。 即 如果 A 调用 B,那么 B 必须是一个接口 。

面向接口编程最主要的一点就是提升扩展性,避免代码堆积 。


  1. 什么是长短 token ? 为什么要用长短两个 token ?

长 token 用于控制 短 token 的刷新

短 token 用于进行登陆的校验

长短token的设计是为了安全性, 如果只有一个 token 有效期设置的很短 用户频繁登陆,很长的话会有安全问题


  1. 长短 token 的过期时间应该怎么设置 ?

根据业务需求和产品经理来判断,长 token 一般会很长 例如半年,一个月 。 短token 可以是 2小时 或者是 1天


  1. 怎么保证长token的安全性,万一泄漏了怎么办 ?

携带一些设备信息,例如 user-agent 手机设备号,在对于敏感操作的时候,增加手机号验证


  1. 使用 JWT token 怎么退出登陆,使用 长短 token 之后怎么退出登陆

增加一个 session_id 进行维护,在 Redis 中维护一个黑名单

扩展问题

Q : 对于系统支持 微信、手机号、邮箱注册,数据库表是怎么设计的

A : 对于这三个字段单独设计了唯一索引,我们认为任意一个渠道注册进来就是单独的一个账号

Q : 那么如果我后续想要实现 手机号和微信和邮箱 是一个账号怎么办

A :

看一下业务层面是否要迁移历史数据

如果不迁移历史数据的话 :

  1. 创建部分联合索引 加上 _create 限制,保证就新的数据需要遵守该索引

  2. 业务层面强制 这三类业务注册的时候 就一起绑定 手机号 或者是 邮箱

如果要迁移历史数据的话

  1. 优先保证内部数据迁移跑完

  2. 并且合并多个账户之间的数据。 这里可能复杂点 预计需要强制用户绑定手机号才可以进行功能操作 xxx

  3. 创建索引

  4. 改善业务层面的登陆与注册服务


Q : Gin 实现了哪些限流 降级 熔断


遗留

  1. 尝试把 SMS 服务单独在拆一个场景出来,增加异步的内容

    可以设置一个 同步转异步的 容错机制 。 当第三方不可用的时候,将当前请求存储到数据库中,后续再单独启动一个 gouroutine 异步的发送出去

AI Judge

1
你现在是 Golang 面试官,下面是一个拥有2年经验的求职者的一个面试准备 。 请你结合现有系统实现 和 xxx.md 给其中的内容进行打分 满分100分,给出 优缺点和建议 。 

对照 webook 实现后,这份准备材料按 2 年经验后端面试 标准评分如下。

总分:62 / 100

维度 分数 说明
项目叙事与亮点提炼 14/20 有多通道登录、长短 Token、限流、failover,但「三高」略夸大
与代码一致性 10/15 大方向对,细节与代码不一致,易被追问穿帮
安全与鉴权深度 12/20 知道 bcrypt / JWT / ssid,但机制说不清、有概念混用
Go 工程能力(DI/接口/分层) 12/15 这块相对扎实,和代码匹配
Redis / 中间件深度 7/15 用过什么能说,底层与一致性大量空白
表达与完整度 7/15 错别字多、多题空答、遗留太多

优点

  1. 项目选型对得上代码:邮箱密码、短信、微信;长短 JWT + ssid 黑名单;User-Agent 校验;SMS failover;Wire + 接口;Redis 滑动窗口限流;CORS;wrk 压测——这些在仓库里都能找到对应实现。
  2. 能讲清楚「为什么做」:长短 Token 的安全/体验权衡、接口便于换 SMS/Repo、bcrypt 有压测体感——比只会背概念强。
  3. 扩展题有业务意识:多渠道账号合并、历史数据迁移,这是面试官爱听的「产品 + 工程」题。
  4. 工程分层叙事清晰:依赖注入、面向接口,和 wire.go、UserCache 注释里的约束一致。

缺点(面试里会扣分的点)

1. 概念混用,容易被打穿

  • 实际是 JWT 放 Header(x-jwt-token / x-refresh-token)+ Redis users:ssid 黑名单,不是经典 Cookie Session。
  • Cookie / Session 答得偏教材,和项目实现脱节;面试官一问「你们项目 Session 存在哪」,容易答乱。
  • 「Session ID 可放 redis/本地缓存/sql」把 服务端存储 和 客户端携带位置 混在一起了。

2. 与代码不一致

讲稿说法 代码现实
短 Token 2h / 1 天,长 Token 月/半年 短 30 分钟,长 7 天(redis_jwt.go)
设备验证 仅比对 User-Agent,不是设备指纹
完整高可用 SMS 有 failover,但 SendV1 有逻辑问题;真实多厂商未必接好
DDD 更接近分层 + 接口,不是完整 DDD

短信登录流程本身也不完整:LoginSMS 校验成功后没发 Token,Token 却在 SendLoginSMSCode 里设置——若被问「短信登录时序」,会很难圆。

3. 关键答案有硬伤

  • bcrypt vs md5:选 bcrypt 主要因为自适应哈希/抗暴力破解,不是「md5 要额外存盐」。bcrypt 自带盐,理由说偏了。
  • 跨域:不只是 host/port,还有 scheme;还缺 Credentials、ExposeHeaders(你们代码里刚好有 ExposeHeaders 暴露 JWT Header)。
  • 缓存一致性:写了「忘了」——2 年经验被问 Cache-Aside / 双写,这是硬伤。
  • Gin 限流/降级/熔断:空题;项目有限流,没有完整熔断降级,却标题写了「高可用」。

4. 准备完整度不够

Redis 底层、一致性、Gin 治理、遗留 SMS 异步都标了 memo/空答。面试不是写博客进度,空题 = 扣分。

5. 自我包装过满

「完整的高安全性、高可用、高扩展」对学习项目偏重。2 年经验更稳妥的说法是:

练手项目,覆盖多登录方式、长短 Token、限流与 SMS 容错,重点验证接口化扩展。


建议(按优先级)

立刻改讲稿

  1. 用代码里的真实数字讲:AT 30min、RT 7 天;退出时 ssid 写入 Redis,TTL 与 RT 对齐。
  2. 统一术语:说「JWT + 会话标识(ssid)黑名单」,少说「Session 存在 Cookie」。
  3. 删掉或降级「设备验证」:改成「绑定 User-Agent,防简单 Token 盗用,不能防 UA 伪造」。
  4. 补齐空题,至少能答 2 分钟:
    • Cache-Aside:先写 DB 再删缓存;并发下的短暂不一致
    • Redis String/Hash/ZSet 各举一个本项目例子(验证码、用户信息、热榜)
    • 限流:滑动窗口;熔断降级诚实说「项目未做,可讲思路」

对着代码能画一张时序图

  • 邮箱登录 → 发 AT/RT → 中间件校验 AT + UA + ssid
  • Refresh:校验 RT → 查黑名单 → 发新 AT
  • Logout:ssid 入黑名单

面试官最爱顺着这条链路追问。

主动暴露已知坑(加分)

  • 短信登录时序问题
  • Failover 主备不均 / SendV1 错误处理
  • FindOrCreate 主从延迟注释

「我知道问题在哪、怎么改」比「系统很完美」更像有 2 年经验。

表达

错别字(Cros、状体啊、Cahce)和口语化「不知道有 memo」直接改掉;面试可以诚实说「这块我回去补过」,但讲稿里不该留白。


面试官视角的一句话

项目素材够用(约 70 分的料),讲解与掌握大约 55–60 分;合在一起 62。
能过初筛讲项目,但深挖安全、缓存一致性、短信登录细节时风险大。把术语对齐代码、补空题、主动讲缺陷,冲到 75+ 比较现实。

使用 Golang 构建