认证与授权#
认证方式#
系统支持两种认证方式:
- 邮箱密码认证
- Google OAuth 2.0 认证
JWT 认证流程#
sequenceDiagram
participant C as Client
participant API as Backend API
participant DB as Database
C->>API: POST /api/auth/login {email, password}
API->>DB: 查询用户
DB-->>API: 返回用户信息
API->>API: 验证密码(BCrypt)
API->>API: 生成 JWT Token
API-->>C: 返回 {token, user}
Note over C: 保存 Token 到 localStorage
C->>API: GET /api/users/me<br/>Authorization: Bearer {token}
API->>API: 验证 JWT Token
API->>DB: 查询用户信息
DB-->>API: 返回用户数据
API-->>C: 返回用户信息JWT Token 结构#
{
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"sub": "user@example.com",
"userId": 1,
"iat": 1234567890,
"exp": 1234654290
},
"signature": "..."
}字段说明:
sub: 用户邮箱(Subject)userId: 用户 IDiat: 签发时间(Issued At)exp: 过期时间(Expiration,24 小时后)
Google OAuth 2.0 流程#
sequenceDiagram
participant U as User
participant F as Frontend
participant G as Google
participant B as Backend
U->>F: 点击「使用 Google 登入」
F->>G: 打开 Google OAuth 授权页面
U->>G: 授权应用
G-->>F: 返回 Access Token
F->>B: POST /api/auth/google {token}
B->>G: 验证 Token 并获取用户信息
G-->>B: 返回用户资料
B->>B: 创建/更新用户
B->>B: 生成 JWT Token
B-->>F: 返回 {token, user}
F->>F: 保存 Token
F-->>U: 登录成功,跳转安全配置#
密码加密#
使用 BCrypt 加密密码:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}特点:
- 自动加盐(Salt)
- 计算成本可调
- 防止彩虹表攻击
JWT Secret#
要求:
- 长度 >= 32 字符
- 使用强随机字符串
- 通过环境变量配置
# application.properties
jwt.secret=${JWT_SECRET}
jwt.expiration=86400000 # 24 hours in milliseconds生成安全的 Secret:
openssl rand -base64 32CORS 配置#
@Configuration
public class SecurityConfig {
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(
Arrays.asList(corsOrigins.split(","))
);
configuration.setAllowedMethods(
Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")
);
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}权限控制#
公开端点#
无需认证即可访问:
"/api/auth/**", // 认证相关
"/api/leaderboard/**", // 排行榜
"/api/courses/**", // 课程列表(基础信息)
"/actuator/health" // 健康检查受保护端点#
需要 JWT Token:
"/api/users/**", // 用户信息
"/api/enrollments/**", // 课程报名
"/api/missions/*/progress", // 学习进度
"/api/missions/*/claim" // 领取奖励自定义权限注解#
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequireEnrollment {
// 检查用户是否已报名课程
}前端集成#
保存 Token#
// 登录成功后
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
});
const data = await response.json();
// 保存到 localStorage
localStorage.setItem('token', data.token);
// 更新 Zustand store
useUserStore.setState({
user: data.user,
isAuthenticated: true
});携带 Token 发送请求#
const token = localStorage.getItem('token');
const response = await fetch('/api/users/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});自动注销(Token 过期)#
const response = await fetch('/api/users/me', {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.status === 401) {
// Token 无效或过期
localStorage.removeItem('token');
useUserStore.setState({
user: null,
isAuthenticated: false
});
window.location.href = '/sign-in';
}安全最佳实践#
1. Token 存储#
✅ 推荐: localStorage(简单场景) ❌ 避免: Cookie(除非 HttpOnly + Secure)
2. HTTPS#
生产环境必须使用 HTTPS:
- 防止 Token 被拦截
- 防止中间人攻击
3. Token 刷新#
当前实现:固定过期时间(24 小时)
改进方案(可选):
- Refresh Token 机制
- 滑动过期时间
4. 防止 CSRF#
JWT 存储在 localStorage 时自动防止 CSRF(不使用 Cookie)。
5. 输入验证#
@Valid
public ResponseEntity<?> login(@RequestBody @Valid LoginRequest request) {
// Spring Validation 自动验证
}
public class LoginRequest {
@Email
@NotBlank
private String email;
@NotBlank
@Size(min = 6)
private String password;
}故障排查#
Token 无效#
症状: 401 Unauthorized
检查:
- Token 是否过期
- JWT_SECRET 是否一致
- Token 格式是否正确(
Bearer {token})
CORS 错误#
症状: Access-Control-Allow-Origin 错误
解决:
- 确认
CORS_ORIGINS包含前端域名 - 检查请求方法是否允许
- 确认
Access-Control-Allow-Credentials设置
Google OAuth 失败#
检查:
GOOGLE_CLIENT_ID和GOOGLE_CLIENT_SECRET是否正确- 回调 URL 是否在 Google Console 中配置
- Google Token 是否有效