diff --git a/superpowers/specs/2026-06-07-multi-login-social-login-design.md b/superpowers/specs/2026-06-07-multi-login-social-login-design.md new file mode 100644 index 0000000..01afe19 --- /dev/null +++ b/superpowers/specs/2026-06-07-multi-login-social-login-design.md @@ -0,0 +1,852 @@ +# 多方式登录与第三方登录设计文档 + +> **日期**: 2026-06-07 +> **状态**: 已批准,待实现 +> **作者**: AI Assistant + +--- + +## 1. 背景与目标 + +### 1.1 现状 + +当前系统仅支持用户名密码登录(`grant_type=password`),且 `PasswordAuthenticationConverter` 只提取 `username` 参数,无法支持手机号、邮箱登录。微信、支付宝、短信登录的 `Converter` 和 `Provider` 均为空实现。 + +### 1.2 目标 + +1. **扩展密码登录**:支持用户名、手机号、邮箱三种账号类型登录 +2. **实现短信登录**:框架结构先行,验证码逻辑后续填充 +3. **实现微信登录**:支持微信授权码换取用户信息并自动创建账号 +4. **实现支付宝登录**:支持支付宝授权码换取用户信息并自动创建账号 +5. **第三方账号管理**:存储 openId/unionId,支持 unionId 优先查询 +6. **手机号为主键**:系统以手机号作为用户唯一标识,第三方登录自动创建新用户 +7. **字段迁移**:将 `email` 从 `uc_user_detail` 迁移到 `uc_user` 表 + +--- + +## 2. 核心设计原则 + +1. **独立授权模式**:每种登录方式使用独立的 `grant_type`,符合 OAuth2 扩展规范 +2. **手机号唯一性**:手机号是系统用户的唯一标识,第三方登录时优先用手机号创建/查找用户 +3. **自动创建用户**:第三方登录无手机号时,自动生成 `userNo` 作为用户名,后续用户可自行修改 +4. **unionId 优先**:查询第三方用户信息时,优先使用 unionId,其次使用 openId +5. **向后兼容**:保留现有 `password` 模式的 `username` 参数,同时新增 `account` + `accountType` 参数 + +--- + +## 3. 架构设计 + +### 3.1 整体架构 + +``` +前端调用 + │ + ▼ +POST /oauth2/token + │ + ▼ +┌─────────────────────────────────────┐ +│ DelegatingAuthenticationConverter │ +│ ┌─────────┐ ┌─────┐ ┌─────────┐ │ +│ │ Password│ │ Sms │ │ Wechat │ │ +│ │ Converter│ │Converter│ │Converter│ │ +│ └─────────┘ └─────┘ └─────────┘ │ +│ ┌─────────┐ │ +│ │ Alipay │ │ +│ │Converter│ │ +│ └─────────┘ │ +└─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ AuthenticationProvider 链 │ +│ ┌─────────┐ ┌─────┐ ┌─────────┐ │ +│ │ Password│ │ Sms │ │ Wechat │ │ +│ │ Provider│ │Provider│ │Provider│ │ +│ └─────────┘ └─────┘ └─────────┘ │ +│ ┌─────────┐ │ +│ │ Alipay │ │ +│ │Provider │ │ +│ └─────────┘ │ +└─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 用户查找 / 创建 / 绑定 │ +│ • 根据手机号/用户名/邮箱查找用户 │ +│ • 第三方登录:调平台API获取用户信息 │ +│ • 自动创建新用户(手机号或userNo) │ +│ • 记录第三方绑定关系 │ +└─────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────┐ +│ 生成 OAuth2 Token │ +│ Access Token + Refresh Token │ +└─────────────────────────────────────┘ +``` + +### 3.2 登录方式对照表 + +| 登录方式 | grant_type | 必填参数 | 可选参数 | 说明 | +|---------|-----------|---------|---------|------| +| 用户名密码 | `password` | `username`, `password` | - | 兼容现有方式 | +| 手机号密码 | `password` | `account`, `accountType=PHONE`, `password` | - | 扩展方式 | +| 邮箱密码 | `password` | `account`, `accountType=EMAIL`, `password` | - | 扩展方式 | +| 短信验证码 | `sms` | `phone`, `code` | - | 框架先行 | +| 微信登录 | `wechat` | `code` | `phone` | 授权码模式 | +| 支付宝登录 | `alipay` | `code` | `phone` | 授权码模式 | + +--- + +## 4. 数据库设计 + +### 4.1 新增表:`rui_uc_user_social` + +存储用户与第三方平台的绑定关系。 + +```sql +CREATE TABLE rui_uc_user_social ( + id BIGINT NOT NULL, + tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户ID', + user_id BIGINT NOT NULL COMMENT '用户ID', + provider VARCHAR(20) NOT NULL COMMENT '平台 wechat/alipay', + union_id VARCHAR(100) DEFAULT NULL COMMENT 'unionId(微信开放平台)', + open_id VARCHAR(100) NOT NULL COMMENT 'openId', + extra JSON DEFAULT NULL COMMENT '扩展信息(昵称、头像等)', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + PRIMARY KEY (id), + UNIQUE KEY uk_user_provider (user_id, provider), + UNIQUE KEY uk_provider_openid (provider, open_id), + INDEX idx_union_id (union_id), + INDEX idx_user_id (user_id) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户第三方账号关联'; +``` + +**字段说明**: +- `provider`: 平台标识,`wechat` 或 `alipay` +- `union_id`: 微信开放平台统一标识,同一主体下的不同应用 unionId 相同 +- `open_id`: 各应用内的唯一标识 +- `extra`: JSON 格式,存储第三方平台的额外信息(昵称、头像、性别等) + +**索引设计**: +- `uk_user_provider`: 一个用户在同一平台只能绑定一个账号 +- `uk_provider_openid`: 同一平台的 openId 唯一 +- `idx_union_id`: 支持 unionId 查询 + +### 4.2 修改表:`rui_uc_user` + +新增 `email` 字段: + +```sql +-- 在 rui_uc_user 表中添加 email 字段 +ALTER TABLE rui_uc_user ADD COLUMN email VARCHAR(100) DEFAULT NULL COMMENT '邮箱' AFTER phone; +ALTER TABLE rui_uc_user ADD UNIQUE KEY uk_email (tenant_id, email); +``` + +修改后的表结构: + +```sql +CREATE TABLE rui_uc_user ( + id BIGINT NOT NULL, + tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户ID 0:系统级', + username VARCHAR(100) NOT NULL COMMENT '用户名', + phone VARCHAR(20) DEFAULT NULL COMMENT '手机号', + email VARCHAR(100) DEFAULT NULL COMMENT '邮箱', + user_no VARCHAR(50) DEFAULT NULL COMMENT '用户编号(短编码,前端展示用)', + password VARCHAR(255) NOT NULL COMMENT '密码(BCrypt加密)', + user_type TINYINT NOT NULL DEFAULT 1 COMMENT '用户类型 1:普通用户 2:管理员 3:系统用户', + status TINYINT NOT NULL DEFAULT 1 COMMENT '状态 0:禁用 1:启用', + deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除 0:正常 1:已删', + created_by BIGINT DEFAULT NULL COMMENT '创建者ID', + created_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), + updated_by BIGINT DEFAULT NULL COMMENT '更新者ID', + updated_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3), + PRIMARY KEY (id), + UNIQUE KEY uk_username (tenant_id, username), + UNIQUE KEY uk_phone (tenant_id, phone), + UNIQUE KEY uk_email (tenant_id, email), + INDEX idx_tenant (tenant_id), + INDEX idx_status (status) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户'; +``` + +### 4.3 修改表:`rui_uc_user_detail` + +删除 `email` 字段: + +```sql +-- 从 rui_uc_user_detail 表中删除 email 字段 +ALTER TABLE rui_uc_user_detail DROP COLUMN email; +``` + +### 4.4 登录日志扩展 + +`rui_sys_login_log` 表的 `login_type` 字段已有定义: +- `1`: 密码登录 +- `2`: 短信登录 +- `3`: 微信登录 +- `4`: 支付宝登录 + +**无需修改**,但需要在代码中确保所有登录方式都正确记录类型。 + +--- + +## 5. 核心流程设计 + +### 5.1 密码登录流程(扩展) + +``` +前端请求 + │ + ▼ +POST /oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic {client_credentials} + +# 方式1:用户名密码(兼容现有) +grant_type=password +&username=admin +&password=123456 + +# 方式2:手机号密码(新增) +grant_type=password +&account=13800138000 +&accountType=PHONE +&password=123456 + +# 方式3:邮箱密码(新增) +grant_type=password +&account=user@example.com +&accountType=EMAIL +&password=123456 + │ + ▼ +PasswordAuthenticationConverter + ├─ 提取 grant_type=password + ├─ 如果有 username → 走兼容模式 + └─ 如果有 account + accountType → 走扩展模式 + │ + ▼ +PasswordAuthenticationProvider + ├─ 校验客户端支持 password 授权 + ├─ 构建 UsernamePasswordAuthenticationToken + │ ├─ 兼容模式: username 作为 principal + │ └─ 扩展模式: account 作为 principal + │ + ▼ +AuthenticationManager + │ + ▼ +DaoAuthenticationProvider + ├─ 调用 RemoteUserDetailsService.loadUserByUsername(username) + │ 或 RemoteUserDetailsService.loadUserByAccount(account, accountType) + │ + ▼ +RemoteUserDetailsService + ├─ USERNAME → userAuthFeign.loadUser(account) + ├─ PHONE → userAuthFeign.loadUser({account, PHONE}) + └─ EMAIL → userAuthFeign.loadUser({account, EMAIL}) + │ + ▼ +UserInnerController.loadUser(LoginAccountDTO) + ├─ 根据 accountType 查询用户 + ├─ PHONE → lambdaQuery().eq(User::getPhone, account) + ├─ EMAIL → lambdaQuery().eq(User::getEmail, account) + └─ USERNAME → lambdaQuery().eq(User::getUsername, account) + │ + ▼ +返回 UserDetails → 生成 Token +``` + +### 5.2 短信登录流程 + +``` +前端请求 + │ + ▼ +POST /oauth2/token +grant_type=sms +&phone=13800138000 +&code=123456 + │ + ▼ +SmsAuthenticationConverter + ├─ 校验 grant_type=sms + ├─ 校验 phone 必填 + └─ 校验 code 必填 + │ + ▼ +SmsAuthenticationProvider + ├─ 校验客户端支持 sms 授权 + ├─ 从 Redis 获取验证码(key: sms:code:{phone}) + ├─ 比对验证码 + ├─ 验证码错误 → 抛出异常 + └─ 验证码正确 → 继续 + │ + ▼ +根据 phone 查询用户 + ├─ 找到 → 生成 Token + └─ 未找到 → 创建新用户 + ├─ username = phone + ├─ phone = phone + ├─ password = 随机生成(BCrypt加密) + └─ user_no = 自动生成 + │ + ▼ +生成 OAuth2 Token +``` + +**注意**:短信验证码发送接口(`POST /sms/send`)本次不实现,只预留框架结构。Redis 中的验证码需要前端开发时手动设置或通过其他方式注入。 + +### 5.3 微信登录流程 + +``` +前端请求 + │ + ▼ +POST /oauth2/token +grant_type=wechat +&code=wx_auth_code +&phone=13800138000 ← 可选 + │ + ▼ +WechatAuthenticationConverter + ├─ 校验 grant_type=wechat + ├─ 校验 code 必填 + └─ 提取 phone(可选) + │ + ▼ +WechatAuthenticationProvider + ├─ 校验客户端支持 wechat 授权 + ├─ 调用微信 API 换取 access_token + │ GET https://api.weixin.qq.com/sns/oauth2/access_token + │ ?appid={appid}&secret={secret}&code={code}&grant_type=authorization_code + │ + ├─ 获取 openId, unionId, access_token + │ + ├─ 根据 unionId 查询 rui_uc_user_social + │ ├─ 找到 → 获取 user_id → 查询用户 → 生成 Token + │ └─ 未找到 → 根据 openId 查询 + │ ├─ 找到 → 获取 user_id → 查询用户 → 生成 Token + │ └─ 未找到 → 创建新用户 + │ + ▼ +创建新用户流程 + ├─ 有 phone 参数 + │ ├─ 查询 phone 是否已存在 + │ ├─ 存在 → 使用该用户,记录绑定关系 + │ └─ 不存在 → 创建新用户 + │ ├─ username = phone + │ ├─ phone = phone + │ └─ password = 随机生成 + │ + └─ 无 phone 参数 + ├─ username = 随机生成(如 WX_ + 时间戳) + ├─ phone = null + └─ password = 随机生成 + │ + ▼ +记录绑定关系 + INSERT INTO rui_uc_user_social + (user_id, provider, union_id, open_id, extra) + VALUES (?, 'wechat', ?, ?, ?) + │ + ▼ +生成 OAuth2 Token +``` + +### 5.4 支付宝登录流程 + +与微信登录类似,区别: +1. 调用支付宝 API:`alipay.system.oauth.token` 换取 access_token +2. 调用 `alipay.user.info.share` 获取用户信息 +3. 支付宝没有 unionId,使用 userId 作为唯一标识 +4. 存储到 `rui_uc_user_social` 时,`union_id` 为 null + +--- + +## 6. 代码结构 + +### 6.1 新增/修改文件清单 + +#### rui-common-oauth2 模块 + +``` +rui-common-oauth2/src/main/java/com/rui/common/oauth2/ +├── authentication/ +│ ├── BaseAuthenticationConverter.java # 已有,无需修改 +│ ├── BaseAuthenticationProvider.java # 已有,无需修改 +│ ├── password/ +│ │ ├── PasswordAuthenticationConverter.java # 修改:支持 accountType +│ │ └── PasswordAuthenticationProvider.java # 已有,无需修改 +│ ├── sms/ +│ │ ├── SmsAuthenticationConverter.java # 重写:实现短信参数提取 +│ │ ├── SmsAuthenticationProvider.java # 重写:实现短信认证逻辑 +│ │ └── SmsAuthenticationToken.java # 新增:短信认证令牌 +│ ├── weixin/ +│ │ ├── WeixinAuthenticationConverter.java # 重写:实现微信参数提取 +│ │ ├── WeixinAuthenticationProvider.java # 重写:实现微信认证逻辑 +│ │ └── WeixinAuthenticationToken.java # 新增:微信认证令牌 +│ └── alipay/ +│ ├── AlipayAuthenticationConverter.java # 重写:实现支付宝参数提取 +│ ├── AlipayAuthenticationProvider.java # 重写:实现支付宝认证逻辑 +│ └── AlipayAuthenticationToken.java # 新增:支付宝认证令牌 +├── config/ +│ └── OAuth2ServerConfig.java # 修改:注册新的 Converter 和 Provider +└── service/ + └── RemoteUserDetailsService.java # 修改:支持 EMAIL 类型 +``` + +#### rui-service-user 模块 + +``` +rui-service-user/src/main/java/com/rui/service/user/ +├── entity/ +│ ├── User.java # 修改:新增 email 字段 +│ ├── UserDetail.java # 修改:删除 email 字段 +│ └── UserSocial.java # 新增:第三方账号关联实体 +├── mapper/ +│ └── UserSocialMapper.java # 新增 +├── service/ +│ ├── IUserSocialService.java # 新增 +│ └── impl/ +│ └── UserSocialServiceImpl.java # 新增 +├── controller/ +│ └── inner/ +│ └── UserInnerController.java # 修改:支持 EMAIL 查询 +└── dto/ + └── LoginAccountDTO.java # 已有,无需修改 +``` + +### 6.2 关键类设计 + +#### 6.2.1 PasswordAuthenticationConverter(修改) + +```java +public class PasswordAuthenticationConverter extends BaseAuthenticationConverter { + + @Override + public void checkParams(HttpServletRequest request) { + MultiValueMap parameters = OAuth2EndpointUtils.getParameters(request); + + // 兼容模式:使用 username + String username = parameters.getFirst("username"); + if (StringUtils.hasText(username)) { + // 校验 password + String password = parameters.getFirst("password"); + if (!StringUtils.hasText(password)) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "password", ...); + } + return; + } + + // 扩展模式:使用 account + accountType + String account = parameters.getFirst("account"); + if (!StringUtils.hasText(account)) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "account", ...); + } + + String accountType = parameters.getFirst("accountType"); + if (!StringUtils.hasText(accountType)) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "accountType", ...); + } + + // 校验 password + String password = parameters.getFirst("password"); + if (!StringUtils.hasText(password)) { + OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, "password", ...); + } + } + + @Override + public PasswordAuthenticationToken buildToken(...) { + // 将 accountType 放入 additionalParameters + // 供 Provider 使用 + return new PasswordAuthenticationToken(...); + } +} +``` + +#### 6.2.2 WechatAuthenticationProvider(重写) + +```java +@Slf4j +public class WechatAuthenticationProvider extends BaseAuthenticationProvider { + + private final WechatApiClient wechatApiClient; + private final UserSocialService userSocialService; + private final UserService userService; + + @Override + public UsernamePasswordAuthenticationToken buildToken(Map reqParameters) { + String code = (String) reqParameters.get("code"); + String phone = (String) reqParameters.get("phone"); + + // 调用微信 API 获取 openId, unionId + WechatTokenResponse wxResponse = wechatApiClient.getAccessToken(code); + String openId = wxResponse.getOpenid(); + String unionId = wxResponse.getUnionid(); + + // 查找或创建用户 + User user = findOrCreateUser(openId, unionId, phone); + + // 构建认证令牌 + return new UsernamePasswordAuthenticationToken(user.getUsername(), null); + } + + private User findOrCreateUser(String openId, String unionId, String phone) { + // 1. 根据 unionId 查找 + if (StringUtils.hasText(unionId)) { + UserSocial social = userSocialService.findByUnionId(unionId); + if (social != null) { + return userService.getById(social.getUserId()); + } + } + + // 2. 根据 openId 查找 + UserSocial social = userSocialService.findByOpenId("wechat", openId); + if (social != null) { + return userService.getById(social.getUserId()); + } + + // 3. 创建新用户 + User user = new User(); + if (StringUtils.hasText(phone)) { + // 检查手机号是否已存在 + User existUser = userService.findByPhone(phone); + if (existUser != null) { + user = existUser; + } else { + user.setUsername(phone); + user.setPhone(phone); + user.setPassword(generateRandomPassword()); + userService.save(user); + } + } else { + // 无手机号,生成随机用户名 + user.setUsername(generateRandomUsername()); + user.setPassword(generateRandomPassword()); + userService.save(user); + } + + // 4. 记录绑定关系 + UserSocial newSocial = new UserSocial(); + newSocial.setUserId(user.getId()); + newSocial.setProvider("wechat"); + newSocial.setUnionId(unionId); + newSocial.setOpenId(openId); + userSocialService.save(newSocial); + + return user; + } +} +``` + +#### 6.2.3 UserSocial 实体 + +```java +@Data +@TableName(value = "uc_user_social", keepGlobalPrefix = true) +public class UserSocial extends BaseEntity { + + @Schema(description = "用户ID") + private Long userId; + + @Schema(description = "平台 wechat/alipay") + private String provider; + + @Schema(description = "unionId") + private String unionId; + + @Schema(description = "openId") + private String openId; + + @Schema(description = "扩展信息") + private String extra; +} +``` + +--- + +## 7. API 接口设计 + +### 7.1 密码登录 + +```http +### 用户名密码登录(兼容现有) +POST /oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic c3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Q6 + +grant_type=password +&username=admin +&password=123456 +&scope=server + +### 手机号密码登录(新增) +POST /oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic c3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Q6 + +grant_type=password +&account=13800138000 +&accountType=PHONE +&password=123456 +&scope=server + +### 邮箱密码登录(新增) +POST /oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic c3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Q6 + +grant_type=password +&account=user@example.com +&accountType=EMAIL +&password=123456 +&scope=server +``` + +### 7.2 短信登录 + +```http +### 短信验证码登录 +POST /oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic c3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Q6 + +grant_type=sms +&phone=13800138000 +&code=123456 +&scope=server +``` + +### 7.3 微信登录 + +```http +### 微信登录 +POST /oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic c3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Q6 + +grant_type=wechat +&code=wx_auth_code_xxx +&phone=13800138000 +&scope=server +``` + +### 7.4 支付宝登录 + +```http +### 支付宝登录 +POST /oauth2/token +Content-Type: application/x-www-form-urlencoded +Authorization: Basic c3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Qtc3ByaW5nLWJvb3Q6 + +grant_type=alipay +&code=alipay_auth_code_xxx +&phone=13800138000 +&scope=server +``` + +### 7.5 响应格式 + +所有登录方式返回统一的 OAuth2 Token 响应: + +```json +{ + "access_token": "abc123...", + "token_type": "Bearer", + "expires_in": 7200, + "refresh_token": "def456...", + "scope": "server" +} +``` + +--- + +## 8. 配置设计 + +### 8.1 微信配置 + +```yaml +# Nacos 配置:rui-service-auth.yaml 或 rui-common.yaml +social: + wechat: + app-id: wx1234567890abcdef + app-secret: your-app-secret + # 可选:token 刷新地址 + token-url: https://api.weixin.qq.com/sns/oauth2/access_token + # 可选:用户信息地址 + user-info-url: https://api.weixin.qq.com/sns/userinfo +``` + +### 8.2 支付宝配置 + +```yaml +social: + alipay: + app-id: 2024XXXXXXXXXXXX + private-key: your-private-key + public-key: alipay-public-key + # 可选:网关地址 + gateway-url: https://openapi.alipay.com/gateway.do +``` + +### 8.3 客户端授权类型配置 + +修改 `sys_oauth_client` 表,为客户端添加新的授权类型: + +```sql +-- 更新默认客户端,支持所有登录方式 +UPDATE sys_oauth_client +SET grant_types = 'password,refresh_token,client_credentials,sms,wechat,alipay' +WHERE client_id = 'rui-client'; +``` + +--- + +## 9. 安全设计 + +### 9.1 验证码安全 + +- 短信验证码有效期:5 分钟 +- 验证码错误次数限制:5 次/小时 +- 验证码存储:Redis,key = `sms:code:{phone}` + +### 9.2 第三方登录安全 + +- 微信/支付宝授权码只能使用一次 +- 授权码有效期:5 分钟(由微信/支付宝平台控制) +- 后端必须校验授权码的真实性(调平台 API) + +### 9.3 密码安全 + +- 第三方登录自动创建的用户,生成随机密码(32 位随机字符串) +- 用户首次设置密码时,要求提供原密码或通过手机验证码验证 + +--- + +## 10. 错误码设计 + +| 错误码 | 描述 | 场景 | +|-------|------|------| +| `invalid_request` | 请求参数错误 | 缺少必填参数、参数格式错误 | +| `invalid_grant` | 授权失败 | 验证码错误、授权码无效 | +| `invalid_client` | 客户端认证失败 | 客户端不存在、授权类型不支持 | +| `unauthorized_client` | 客户端未授权 | 客户端不支持该授权类型 | +| `server_error` | 服务器内部错误 | 调用第三方 API 失败 | + +--- + +## 11. 测试策略 + +### 11.1 单元测试 + +- `PasswordAuthenticationConverterTest`: 测试参数提取和校验 +- `SmsAuthenticationProviderTest`: 测试验证码校验逻辑 +- `WechatAuthenticationProviderTest`: Mock 微信 API,测试用户创建流程 + +### 11.2 集成测试 + +- 使用 H2 内存数据库测试完整登录流程 +- 使用 WireMock 模拟微信/支付宝 API + +### 11.3 手动测试清单 + +- [ ] 用户名密码登录(兼容测试) +- [ ] 手机号密码登录 +- [ ] 邮箱密码登录 +- [ ] 短信验证码登录(使用 Redis 手动设置验证码) +- [ ] 微信登录(使用测试授权码) +- [ ] 支付宝登录(使用测试授权码) +- [ ] 第三方登录后绑定手机号 +- [ ] 同一微信不同手机号创建不同用户 +- [ ] unionId 优先查询验证 + +--- + +## 12. 风险与回滚 + +### 12.1 风险 + +| 风险 | 影响 | 缓解措施 | +|------|------|---------| +| 微信/支付宝 API 变更 | 登录失败 | 封装 API 调用,便于快速适配 | +| 手机号重复 | 数据不一致 | 数据库唯一索引 + 代码校验 | +| 性能问题 | 登录慢 | Redis 缓存 + 异步记录日志 | + +### 12.2 回滚方案 + +- 数据库变更:保留原字段,新增字段不影响现有数据 +- 代码回滚:新授权模式独立实现,不影响现有 `password` 模式 +- 配置回滚:移除新 grant_type 即可禁用 + +--- + +## 13. 后续优化 + +1. **短信服务商接入**:实现真实的短信发送功能 +2. **社交账号解绑**:提供 API 解除第三方绑定 +3. **多账号合并**:支持将多个第三方账号合并到同一用户 +4. **登录设备管理**:记录登录设备,支持远程登出 +5. **扫码登录**:支持微信扫码登录 PC 端 + +--- + +## 14. 附录 + +### 14.1 登录类型枚举 + +```java +public enum LoginType { + PASSWORD(1, "密码登录"), + SMS(2, "短信登录"), + WECHAT(3, "微信登录"), + ALIPAY(4, "支付宝登录"); + + private final int code; + private final String description; + + LoginType(int code, String description) { + this.code = code; + this.description = description; + } +} +``` + +### 14.2 账号类型枚举 + +```java +public enum AccountType { + USERNAME("用户名"), + PHONE("手机号"), + EMAIL("邮箱"); + + private final String description; + + AccountType(String description) { + this.description = description; + } +} +``` + +### 14.3 第三方平台枚举 + +```java +public enum SocialProvider { + WECHAT("微信"), + ALIPAY("支付宝"); + + private final String description; + + SocialProvider(String description) { + this.description = description; + } +} +``` + +--- + +**文档结束**