认证授权
本章节详细介绍 MootingBackend 的 JWT 认证实现。
认证架构
┌──────────────────────────────────────────────────────────────┐
│ 认证流程 │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 注册 │ → │发送验证码│ → │验证登录 │ → │获取Token│ │
│ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ 后续请求 │ │
│ │ Authorization: Bearer <JWT Token> │ │
│ │ ↓ │ │
│ │ JwtAuthFilter → 验证签名 → 设置SecurityContext │ │
│ └─────────────────────────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘JWT 配置
配置参数
properties
# JWT 密钥 (256位)
app.jwt.secret=your-256-bit-secret-key-for-jwt-signing-change-in-production
# Token 有效期 (毫秒)
app.jwt.expiration-ms=86400000JwtUtil 工具类
java
@Component
public class JwtUtil {
@Value("${app.jwt.secret}")
private String secret;
@Value("${app.jwt.expiration-ms}")
private long expirationMs;
// 生成 Token
public String generateToken(Long userId) {
return Jwts.builder()
.subject(String.valueOf(userId))
.issuedAt(new Date())
.expiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(getSigningKey(), Jwts.SIG.HS256)
.compact();
}
// 从 Token 获取用户 ID
public Long getUserIdFromToken(String token) {
Claims claims = Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token)
.getPayload();
return Long.parseLong(claims.getSubject());
}
// 验证 Token
public boolean validateToken(String token) {
try {
Jwts.parser()
.verifyWith(getSigningKey())
.build()
.parseSignedClaims(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
private SecretKey getSigningKey() {
return Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
}
}JWT 过滤器
JwtAuthFilter
java
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(
HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
String token = parseToken(request);
if (token != null && jwtUtil.validateToken(token)) {
Long userId = jwtUtil.getUserIdFromToken(token);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(
userId, null, Collections.emptyList());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String parseToken(HttpServletRequest request) {
String headerAuth = request.getHeader("Authorization");
if (headerAuth != null && headerAuth.startsWith("Bearer ")) {
return headerAuth.substring(7);
}
return null;
}
}Security 配置
SecurityConfig
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthFilter jwtAuthFilter;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// 禁用 CSRF (API 服务)
.csrf(csrf -> csrf.disable())
// 无状态会话
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 授权规则
.authorizeHttpRequests(auth -> auth
// 公开端点
.requestMatchers("/api/auth/register").permitAll()
.requestMatchers("/api/auth/login").permitAll()
.requestMatchers("/api/verify-code/**").permitAll()
// 其他需要认证
.anyRequest().authenticated()
)
// JWT 过滤器
.addFilterBefore(jwtAuthFilter,
UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("*"));
configuration.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
UrlBasedCorsConfigurationSource source =
new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
}认证流程
1. 发送验证码
http
POST /api/verify-code/send
Content-Type: application/json
{
"type": "email",
"target": "user@example.com"
}处理流程:
- 验证 target 格式 (邮箱/手机号)
- 检查发送频率 (60秒间隔)
- 生成 6 位随机验证码
- 存入内存 (5分钟有效期)
- 发送邮件/短信
2. 验证登录
http
POST /api/verify-code/verify
Content-Type: application/json
{
"target": "user@example.com",
"verifyCode": "123456"
}处理流程:
- 校验验证码正确性
- 标记验证码已使用
- 查找或创建用户
- 记录登录事件
- 生成 JWT Token
- 返回登录信息
响应示例:
json
{
"code": 0,
"message": "验证码校验成功",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"userId": 123,
"phone": null,
"email": "user@example.com"
}3. 密码登录 (可选)
http
POST /api/auth/login
Content-Type: application/json
{
"phone": "13800138000",
"password": "password123"
}处理流程:
- 查找用户
- BCrypt 密码验证
- 生成 JWT Token
- 记录登录事件
使用 Token
请求头格式
http
GET /api/users/me
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...获取当前用户
在 Controller 中获取当前用户 ID:
java
@GetMapping("/me")
public ResponseEntity<UserResponse> getMe(Authentication auth) {
Long userId = (Long) auth.getPrincipal();
return ResponseEntity.ok(userService.getMe(userId));
}端点权限
| 端点 | 权限 | 说明 |
|---|---|---|
POST /api/auth/register | 公开 | 用户注册 |
POST /api/auth/login | 公开 | 密码登录 |
POST /api/verify-code/send | 公开 | 发送验证码 |
POST /api/verify-code/verify | 公开 | 验证码登录 |
GET /api/users/me | 认证 | 获取当前用户 |
POST /api/devices | 认证 | 注册设备 |
GET /api/devices | 认证 | 设备列表 |
POST /api/transcriptions | 认证 | 提交转写 |
GET /api/transcriptions | 认证 | 转写列表 |
POST /api/behavior/* | 认证 | 行为记录 |
安全建议
生产环境检查清单
- [ ] 更换 JWT 密钥为随机生成的强密钥
- [ ] 使用环境变量管理敏感配置
- [ ] 启用 HTTPS
- [ ] 配置合理的 CORS 策略
- [ ] 实现 Token 刷新机制
- [ ] 添加请求频率限制