Skip to content

认证授权

本章节详细介绍 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=86400000

JwtUtil 工具类

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"
}

处理流程:

  1. 验证 target 格式 (邮箱/手机号)
  2. 检查发送频率 (60秒间隔)
  3. 生成 6 位随机验证码
  4. 存入内存 (5分钟有效期)
  5. 发送邮件/短信

2. 验证登录

http
POST /api/verify-code/verify
Content-Type: application/json

{
  "target": "user@example.com",
  "verifyCode": "123456"
}

处理流程:

  1. 校验验证码正确性
  2. 标记验证码已使用
  3. 查找或创建用户
  4. 记录登录事件
  5. 生成 JWT Token
  6. 返回登录信息

响应示例:

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"
}

处理流程:

  1. 查找用户
  2. BCrypt 密码验证
  3. 生成 JWT Token
  4. 记录登录事件

使用 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 刷新机制
  • [ ] 添加请求频率限制

下一步

Mooting 开发者文档