PACS Online 系统支持三种登录方式:
所有登录成功后都会返回 JWT Token,用于后续接口的身份认证。
适用于所有普通用户的基础登录方式。
POST /auth/login请求参数:
{
"username": "用户名",
"password": "密码"
}
```
┌──────────┐ │ 前端提交 │ │ 用户名密码│ └────┬─────┘
│
▼
┌────────────────────────────┐ │ 1. 用户名密码认证 │ │ (Spring Security) │ └────┬───────────────────┬───┘
│ │
│ 认证失败 │ 认证成功
▼ ▼
┌─────────┐ ┌──────────────────┐ │返回错误 │ │2. 检查是否需要 │ └─────────┘ │ 短信二次验证 │
└────┬────────┬─────┘
│ │
需要二次验证│ │不需要二次验证
▼ ▼
┌──────────────┐ ┌────────────┐
│3. 生成并返回 │ │4. 生成JWT │
│ preAuthToken│ │ 返回token│
│ 和脱敏手机号│ └────────────┘
└──────────────┘
#### 响应说明
**情况1:不需要二次验证,直接登录成功**
```json
{
"code": 200,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"id": 1,
"username": "admin",
"phone": "13800138000",
...
}
}
}
情况2:需要短信二次验证
{
"code": 200,
"message": "需要短信验证",
"data": {
"requireSmsVerify": true,
"preAuthToken": "abc123def456...",
"phone": "138****8000",
"userId": 1
}
}
系统根据以下条件判断是否需要短信二次验证:
doctors 表中存在记录is_send_message 字段值为 1满足以上条件时,需要进行短信二次验证。
用户忘记密码或希望快速登录时使用。
┌──────────┐
│前端页面 │
└────┬─────┘
│
▼
┌─────────────────────┐
│ 1. 请求发送验证码 │
│ POST /auth/sendCode
│ 参数: phone │
└────┬────────────────┘
│
▼
┌─────────────────────┐
│ 2. 系统生成验证码 │
│ 保存到Redis和DB │
│ 有效期5分钟 │
└────┬────────────────┘
│
▼
┌─────────────────────┐
│ 3. 前端提交手机号和 │
│ 验证码进行登录 │
│ POST /auth/loginByPhone
└────┬────────────────┘
│
▼
┌─────────────────────┐
│ 4. 验证码校验成功 │
│ 生成JWT Token │
│ 返回登录结果 │
└─────────────────────┘
步骤1:发送验证码
POST /auth/sendCodephone=13800138000响应示例:
{
"code": 200,
"message": "验证码发送成功"
}
步骤2:验证码登录
POST /auth/loginByPhonejson
{
"phone": "13800138000",
"code": "123456"
}
响应示例:
{
"code": 200,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"id": 1,
"username": "user001",
...
}
}
}
针对需要高安全级别的医生用户,在用户名密码验证通过后,还需要通过手机短信验证码进行二次验证。
doctors 表中存在记录doctors.is_send_message = 1(开启短信二次验证)```
┌──────────────┐ │1. 用户名密码 │ │ 登录 │ │POST /auth/login └──────┬───────┘
│
▼
┌──────────────────────┐ │2. 认证成功,检查是否│ │ 需要二次验证 │ └──────┬───────────────┘
│
▼
┌──────────────────────┐ │3. 返回preAuthToken │ │ 和脱敏手机号 │ │ requireSmsVerify=true └──────┬───────────────┘
│
▼
┌──────────────────────┐ │4. 前端调用发送 │ │ 验证码接口 │ │POST /auth/sendLoginCode │参数: preAuthToken │ └──────┬───────────────┘
│
▼
┌──────────────────────┐ │5. 系统生成并发送验证码│ │ 保存记录到DB │ │ 返回脱敏手机号 │ └──────┬───────────────┘
│
▼
┌──────────────────────┐ │6. 用户收到验证码 │ │ 前端提交验证 │ │POST /auth/verifySmsLogin │参数: preAuthToken, code └──────┬───────────────┘
│
▼
┌──────────────────────┐ │7. 验证码校验成功 │ │ 生成JWT Token │ │ 删除preAuthToken │ │ 返回登录结果 │ └──────────────────────┘
#### 关键概念
**preAuthToken(预认证凭证)**
- 在用户名密码验证通过后生成
- 存储在 Redis 中,key 为 `preauth:{token}`,value 为 `userId`
- 有效期 5 分钟
- 用于关联后续的短信验证流程
**验证码生成和存储**
- 验证码为 6 位数字
- 同时存储在:
- Redis:`sms:code:{phone}`,有效期 5 分钟
- 数据库:`sys_sms_code` 表
- 数据库:`sys_login_sms_record` 表(登录专用记录)
---
## 3. 接口详细说明
### 3.1 用户名密码登录
**接口地址**:`POST /auth/login`
**请求参数**:
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| username | String | 是 | 用户名 |
| password | String | 是 | 密码(明文) |
**响应参数**:
根据不同情况,返回不同的数据结构:
1. **直接登录成功**(不需要二次验证):
```json
{
"code": 200,
"message": "登录成功",
"data": {
"token": "JWT令牌",
"userInfo": {用户信息对象}
}
}
需要短信二次验证:
{
"code": 200,
"message": "需要短信验证",
"data": {
"requireSmsVerify": true,
"preAuthToken": "预认证凭证",
"phone": "138****8000",
"userId": 1
}
}
接口地址:POST /auth/sendCode
请求参数: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | phone | String | 是 | 手机号码 |
响应示例:
{
"code": 200,
"message": "验证码发送成功"
}
接口地址:POST /auth/loginByPhone
请求参数: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | phone | String | 是 | 手机号码 | | code | String | 是 | 验证码 |
响应示例:
{
"code": 200,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"id": 1,
"username": "user001",
"phone": "13800138000",
...
}
}
}
接口地址:POST /auth/sendLoginCode
请求参数: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | preAuthToken | String | 是 | 预认证凭证(从/auth/login获取) |
响应示例:
{
"code": 200,
"message": "验证码发送成功",
"data": {
"phone": "138****8000"
}
}
错误响应:
{
"code": 400,
"message": "预认证已过期,请重新登录"
}
接口地址:POST /auth/verifySmsLogin
请求参数: | 参数名 | 类型 | 必填 | 说明 | |--------|------|------|------| | preAuthToken | String | 是 | 预认证凭证 | | code | String | 是 | 短信验证码 |
响应示例:
{
"code": 200,
"message": "登录成功",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userInfo": {
"id": 1,
"username": "doctor001",
...
}
}
}
错误响应:
{
"code": 400,
"message": "验证码错误或已过期"
}
接口地址:POST /auth/logout
请求头:
Authorization: Bearer {token}
响应示例:
{
"code": 200,
"message": "退出成功"
}
┌─────────┐ ┌──────────────┐ ┌──────────┐
│ 前端 │ │ 后端 │ │ 数据存储 │
└────┬────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 1.POST /auth/login │ │
│ {username,password} │ │
├─────────────────────►│ │
│ │ │
│ │ 2.查询用户 │
│ ├──────────────────────►│
│ │ sys_user
│ │◄──────────────────────┤
│ │ │
│ │ 3.验证密码(BCrypt) │
│ │ │
│ │ 4.查询医生信息 │
│ ├──────────────────────►│
│ │ doctors
│ │◄──────────────────────┤
│ │ │
│ │ 5.判断是否需要二次验证│
│ │ │
│ ┌─────────────┴─────────────┐ │
│ │ │ │
│ ▼ 不需要 ▼ 需要 │
│ 6.生成JWT Token 7.生成preAuthToken
│ 存储到Redis 存储到Redis │
│ key:token:{userId} key:preauth:{token}
│ │ │ │
│◄───────┤ ├────────►│
│ 返回token 返回preAuthToken
│ 和脱敏手机号
│ │
┌─────────┐ ┌──────────────┐ ┌──────────┐
│ 前端 │ │ 后端 │ │ 数据存储 │
└────┬────┘ └──────┬───────┘ └────┬─────┘
│ │ │
│ 1.POST /auth/sendLoginCode │
│ {preAuthToken} │ │
├─────────────────────►│ │
│ │ │
│ │ 2.从Redis验证token │
│ ├──────────────────────►│
│ │ get preauth:{token}│
│ │◄──────────────────────┤
│ │ 返回userId │
│ │ │
│ │ 3.生成6位验证码 │
│ │ │
│ │ 4.保存验证码 │
│ ├──────────────────────►│
│ │ Redis: sms:code:{phone}
│ │ DB: sys_sms_code │
│ │ DB: sys_login_sms_record
│ │ │
│ │ 5.发送短信(当前为模拟)│
│ │ │
│◄─────────────────────┤ │
│ 返回脱敏手机号 │ │
│ │ │
│ 6.POST /auth/verifySmsLogin │
│ {preAuthToken,code} │
├─────────────────────►│ │
│ │ │
│ │ 7.验证preAuthToken │
│ ├──────────────────────►│
│ │ │
│ │ 8.验证验证码 │
│ ├──────────────────────►│
│ │ get sms:code:{phone} │
│ │◄──────────────────────┤
│ │ │
│ │ 9.验证成功,生成JWT │
│ │ 删除preAuthToken │
│ │ 更新验证记录状态 │
│ ├──────────────────────►│
│ │ │
│◄─────────────────────┤ │
│ 返回JWT Token │ │
│ │ │
sys_log 表sys_login_sms_record 表对于医生等重要角色,涉及患者隐私数据的访问,需要更高的安全级别。短信二次验证可以有效防止密码泄露导致的账户被盗用。
preAuthToken 是一个临时凭证,用于关联用户名密码验证和短信验证两个步骤。它的存在是为了:
不可以。验证码是一次性的,验证成功后会立即从 Redis 中删除,并在数据库中标记为已使用。
在 doctors 表中设置 is_send_message 字段:
1:需要短信二次验证0 或 NULL:不需要短信二次验证如果需要短信验证的用户没有绑定手机号,系统会返回错误提示:"用户未绑定手机号,无法进行短信验证,请联系管理员"。
Token 过期后需要重新登录。系统会返回 401 未授权错误,前端应引导用户跳转到登录页面。
如果后续不需要此功能,代码中已添加明确的标记注释,搜索 【短信二次验证功能】 即可找到所有相关代码块,按注释提示删除即可。
存储用户基本信息,包括用户名、密码(加密)、手机号等。
存储医生信息,其中 is_send_message 字段控制是否启用短信二次验证。
存储所有发送的短信验证码记录,包括:
专门记录短信二次验证的发送和验证记录,包括:
记录所有登录操作,包括:
// 普通用户名密码登录
async function login(username, password) {
const response = await fetch('/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ username, password })
});
const result = await response.json();
if (result.code === 200) {
if (result.data.requireSmsVerify) {
// 需要短信二次验证
showSmsVerifyDialog(result.data.preAuthToken, result.data.phone);
} else {
// 登录成功,保存token
localStorage.setItem('token', result.data.token);
localStorage.setItem('userInfo', JSON.stringify(result.data.userInfo));
window.location.href = '/dashboard.html';
}
} else {
alert('登录失败:' + result.message);
}
}
// 发送验证码
async function sendLoginCode(preAuthToken) {
const response = await fetch('/auth/sendLoginCode?preAuthToken=' + preAuthToken, {
method: 'POST'
});
const result = await response.json();
if (result.code === 200) {
alert('验证码已发送至 ' + result.data.phone);
startCountdown(60); // 开始60秒倒计时
} else {
alert('发送失败:' + result.message);
}
}
// 验证验证码并完成登录
async function verifySmsLogin(preAuthToken, code) {
const response = await fetch('/auth/verifySmsLogin', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: `preAuthToken=${preAuthToken}&code=${code}`
});
const result = await response.json();
if (result.code === 200) {
// 登录成功,保存token
localStorage.setItem('token', result.data.token);
localStorage.setItem('userInfo', JSON.stringify(result.data.userInfo));
window.location.href = '/dashboard.html';
} else {
alert('验证失败:' + result.message);
}
}
// 发送验证码
async function sendPhoneCode(phone) {
const response = await fetch('/auth/sendCode?phone=' + phone, {
method: 'POST'
});
const result = await response.json();
if (result.code === 200) {
alert('验证码已发送');
startCountdown(60);
} else {
alert('发送失败:' + result.message);
}
}
// 手机号验证码登录
async function loginByPhone(phone, code) {
const response = await fetch('/auth/loginByPhone', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ phone, code })
});
const result = await response.json();
if (result.code === 200) {
localStorage.setItem('token', result.data.token);
localStorage.setItem('userInfo', JSON.stringify(result.data.userInfo));
window.location.href = '/dashboard.html';
} else {
alert('登录失败:' + result.message);
}
}
// 为所有请求自动添加 Token
fetch = (originalFetch => {
return (...args) => {
const token = localStorage.getItem('token');
if (token && args[1]) {
args[1].headers = args[1].headers || {};
args[1].headers['Authorization'] = 'Bearer ' + token;
}
return originalFetch(...args).then(response => {
// 如果返回401,跳转到登录页
if (response.status === 401) {
localStorage.removeItem('token');
localStorage.removeItem('userInfo');
window.location.href = '/login.html';
}
return response;
});
};
})(fetch);
登录相关接口已配置在白名单中(位于 SecurityConfig.java):
.antMatchers("/auth/login",
"/auth/logout",
"/auth/sendCode",
"/auth/loginByPhone",
"/auth/sendLoginCode",
"/auth/verifySmsLogin").permitAll()
需要确保 Redis 服务正常运行,系统使用 Redis 存储:
token:{userId},有效期 24 小时sms:code:{phone},有效期 5 分钟preauth:{token},有效期 5 分钟JWT 相关配置在 application.yml 或 JwtUtil.java 中,包括:
| 错误码 | 说明 |
|---|---|
| 200 | 成功 |
| 400 | 参数错误或业务逻辑错误 |
| 401 | 未授权(Token无效或过期) |
| 403 | 禁止访问(用户被禁用) |
| 404 | 资源不存在(用户不存在) |
| 500 | 服务器内部错误 |
Java 类文件:
SysUserController.java - 登录相关接口控制器SecurityConfig.java - Spring Security 配置JwtAuthenticationFilter.java - JWT 认证过滤器UserDetailsServiceImpl.java - 用户认证服务SysSmsCodeService.java - 短信验证码服务接口SysSmsCodeServiceImpl.java - 短信验证码服务实现SysLoginSmsRecordService.java - 登录短信记录服务JwtUtil.java - JWT 工具类数据库表:
sys_user - 用户表doctors - 医生表sys_sms_code - 短信验证码表sys_login_sms_record - 登录短信记录表sys_log - 系统日志表| 版本 | 日期 | 修改内容 | 修改人 |
|---|---|---|---|
| 1.0 | 2025-11-28 | 初始版本,包含三种登录方式的完整说明 | Claude |
如有问题或建议,请联系开发团队。