# Backend Password Transfer Encryption Implementation Plan

> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** 为正式密码提交接口补上后端解密链路，让 `/login`、`/register`、`/system/user/profile/updatePwd`、`/system/user/resetPwd`、`/system/user` 在收到密文密码后先解密，再复用现有认证与 BCrypt 逻辑。

**Architecture:** 在 `ruoyi-framework` 新增统一的密码传输解密服务，固定密钥从配置读取，负责 AES/Base64 解密和失败抛错。`ruoyi-admin` 各控制器在进入现有业务逻辑前显式调用该服务对密码字段解密，`/login/test` 保持不变。

**Tech Stack:** Spring Boot 3.5、JUnit 5、MockMvc、JDK `javax.crypto`、Maven Surefire

---

### Task 1: 搭建后端密码解密基础设施

**Files:**
- Create: `ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PasswordTransferCryptoService.java`
- Create: `ruoyi-framework/src/test/java/com/ruoyi/framework/web/service/PasswordTransferCryptoServiceTest.java`
- Modify: `ruoyi-admin/src/main/resources/application.yml`
- Modify: `ruoyi-admin/src/main/resources/application-dev.yml`

- [ ] **Step 1: 写解密服务失败用例**

```java
class PasswordTransferCryptoServiceTest
{
    @Test
    void shouldDecryptValidCipherText()
    {
        String plain = service.decrypt("Base64密文");
        assertEquals("admin123", plain);
    }

    @Test
    void shouldRejectInvalidCipherText()
    {
        assertThrows(ServiceException.class, () -> service.decrypt("not-base64"));
    }
}
```

- [ ] **Step 2: 运行测试确认当前失败**

Run: `mvn -pl ruoyi-framework -am -Dtest=PasswordTransferCryptoServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: FAIL，提示测试类或 `PasswordTransferCryptoService` 不存在

- [ ] **Step 3: 补配置与最小实现**

```java
@Service
public class PasswordTransferCryptoService
{
    @Value("${security.password-transfer.key}")
    private String key;

    public String decrypt(String cipherText)
    {
        Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), "AES"));
        return new String(cipher.doFinal(Base64.getDecoder().decode(cipherText)), StandardCharsets.UTF_8);
    }
}
```

```yaml
security:
  password-transfer:
    key: "请替换为16位固定密钥"
```

- [ ] **Step 4: 重新运行基础测试**

Run: `mvn -pl ruoyi-framework -am -Dtest=PasswordTransferCryptoServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: PASS

- [ ] **Step 5: 提交本任务**

```bash
git add ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/PasswordTransferCryptoService.java ruoyi-framework/src/test/java/com/ruoyi/framework/web/service/PasswordTransferCryptoServiceTest.java ruoyi-admin/src/main/resources/application.yml ruoyi-admin/src/main/resources/application-dev.yml
git commit -m "新增密码传输解密服务"
```

### Task 2: 接入登录与注册接口解密

**Files:**
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java`
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysLoginControllerPasswordTransferTest.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysRegisterControllerPasswordTransferTest.java`

- [ ] **Step 1: 写登录与注册控制器失败测试**

```java
mockMvc.perform(post("/login")
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"username\":\"admin\",\"password\":\"cipher\",\"code\":\"1\",\"uuid\":\"u\"}"))
    .andExpect(status().isOk());

verify(passwordTransferCryptoService).decrypt("cipher");
verify(loginService).login("admin", "admin123", "1", "u");
```

```java
mockMvc.perform(post("/register")
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"username\":\"u1\",\"password\":\"cipher\",\"code\":\"1\",\"uuid\":\"u\"}"))
    .andExpect(status().isOk());

verify(passwordTransferCryptoService).decrypt("cipher");
verify(registerService).register(any(RegisterBody.class));
```

- [ ] **Step 2: 运行登录/注册测试确认失败**

Run: `mvn -pl ruoyi-admin -am -Dtest=SysLoginControllerPasswordTransferTest,SysRegisterControllerPasswordTransferTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: FAIL，控制器尚未调用解密服务

- [ ] **Step 3: 在正式接口入口补解密**

```java
loginBody.setPassword(passwordTransferCryptoService.decrypt(loginBody.getPassword()));
```

```java
user.setPassword(passwordTransferCryptoService.decrypt(user.getPassword()));
```

要求：
- 只改 `/login`
- 不改 `loginWithoutCaptcha`
- 解密失败直接抛错，不追加明文兼容分支

- [ ] **Step 4: 重新运行登录/注册测试**

Run: `mvn -pl ruoyi-admin -am -Dtest=SysLoginControllerPasswordTransferTest,SysRegisterControllerPasswordTransferTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: PASS

- [ ] **Step 5: 提交本任务**

```bash
git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysRegisterController.java ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysLoginControllerPasswordTransferTest.java ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysRegisterControllerPasswordTransferTest.java
git commit -m "接入登录注册密码解密"
```

### Task 3: 接入个人修改密码接口解密

**Files:**
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysProfileControllerPasswordTransferTest.java`

- [ ] **Step 1: 写修改密码失败测试**

```java
mockMvc.perform(put("/system/user/profile/updatePwd")
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"oldPassword\":\"oldCipher\",\"newPassword\":\"newCipher\"}"))
    .andExpect(status().isOk());

verify(passwordTransferCryptoService).decrypt("oldCipher");
verify(passwordTransferCryptoService).decrypt("newCipher");
```

- [ ] **Step 2: 运行测试确认失败**

Run: `mvn -pl ruoyi-admin -am -Dtest=SysProfileControllerPasswordTransferTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: FAIL，`updatePwd` 尚未解密 `oldPassword`、`newPassword`

- [ ] **Step 3: 在 `updatePwd` 开头显式解密两个字段**

```java
String oldPassword = passwordTransferCryptoService.decrypt(params.get("oldPassword"));
String newPassword = passwordTransferCryptoService.decrypt(params.get("newPassword"));
```

要求：
- 仅在解密成功后继续旧密码校验
- 不处理 `confirmPassword`
- 保持原有报错文案和 BCrypt 入库逻辑

- [ ] **Step 4: 重新运行测试**

Run: `mvn -pl ruoyi-admin -am -Dtest=SysProfileControllerPasswordTransferTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: PASS

- [ ] **Step 5: 提交本任务**

```bash
git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysProfileController.java ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysProfileControllerPasswordTransferTest.java
git commit -m "接入个人修改密码解密"
```

### Task 4: 接入管理员新增用户与重置密码接口解密

**Files:**
- Modify: `ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java`
- Create: `ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysUserControllerPasswordTransferTest.java`

- [ ] **Step 1: 写新增用户与重置密码失败测试**

```java
mockMvc.perform(post("/system/user")
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"userName\":\"u1\",\"nickName\":\"n1\",\"deptId\":1,\"password\":\"cipher\"}"));

verify(passwordTransferCryptoService).decrypt("cipher");
```

```java
mockMvc.perform(put("/system/user/resetPwd")
        .contentType(MediaType.APPLICATION_JSON)
        .content("{\"userId\":2,\"password\":\"cipher\"}"));

verify(passwordTransferCryptoService).decrypt("cipher");
```

- [ ] **Step 2: 运行测试确认失败**

Run: `mvn -pl ruoyi-admin -am -Dtest=SysUserControllerPasswordTransferTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: FAIL，新增用户和重置密码入口尚未调用解密

- [ ] **Step 3: 在 `add` 与 `resetPwd` 中先解密后继续原逻辑**

```java
user.setPassword(passwordTransferCryptoService.decrypt(user.getPassword()));
user.setPassword(SecurityUtils.encryptPassword(user.getPassword()));
```

要求：
- 仅对 `add`、`resetPwd` 补解密
- `edit` 不动
- 仍保留现有权限校验与数据范围校验

- [ ] **Step 4: 重新运行测试**

Run: `mvn -pl ruoyi-admin -am -Dtest=SysUserControllerPasswordTransferTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: PASS

- [ ] **Step 5: 提交本任务**

```bash
git add ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysUserController.java ruoyi-admin/src/test/java/com/ruoyi/web/controller/system/SysUserControllerPasswordTransferTest.java
git commit -m "接入用户密码接口解密"
```

### Task 5: 汇总验证与后端实施记录

**Files:**
- Create: `doc/implementation-report-2026-03-30-login-password-encryption-backend.md`

- [ ] **Step 1: 运行后端全部目标测试**

Run: `mvn -pl ruoyi-admin,ruoyi-framework -am -Dtest=PasswordTransferCryptoServiceTest,SysLoginControllerPasswordTransferTest,SysRegisterControllerPasswordTransferTest,SysProfileControllerPasswordTransferTest,SysUserControllerPasswordTransferTest -Dsurefire.failIfNoSpecifiedTests=false test`
Expected: PASS，所有新增后端测试通过

- [ ] **Step 2: 手工核对 `/login/test` 未被改动**

Run: `git diff -- ruoyi-admin/src/main/java/com/ruoyi/web/controller/system/SysLoginController.java`
Expected: 仅 `/login` 增加解密调用，`loginWithoutCaptcha` 无行为变化

- [ ] **Step 3: 写后端实施记录**

```markdown
# 密码加密传输后端实施记录
- 新增密码解密服务
- 接入 5 个正式接口中的后端入口
- 保持 `/login/test` 不变
- 补充控制器与服务测试
```

- [ ] **Step 4: 再次检查文档路径与 git 状态**

Run: `git status --short`
Expected: 仅包含后端实现文件与 `doc/implementation-report-2026-03-30-login-password-encryption-backend.md`

- [ ] **Step 5: 提交本任务**

```bash
git add doc/implementation-report-2026-03-30-login-password-encryption-backend.md
git commit -m "完成密码加密传输后端实现"
```
