package com.xuqm.update.config; import com.xuqm.common.security.ApiKeyAuthFilter; import com.xuqm.common.security.AppSignatureAuthFilter; import com.xuqm.common.security.JwtAuthFilter; import com.xuqm.common.security.JwtUtil; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import jakarta.servlet.http.HttpServletResponse; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; import org.springframework.web.cors.CorsConfigurationSource; import org.springframework.web.cors.UrlBasedCorsConfigurationSource; import java.util.List; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtUtil jwtUtil; private final TenantApiKeyValidator apiKeyValidator; private final TenantAppSecretResolver appSecretResolver; public SecurityConfig(JwtUtil jwtUtil, TenantApiKeyValidator apiKeyValidator, TenantAppSecretResolver appSecretResolver) { this.jwtUtil = jwtUtil; this.apiKeyValidator = apiKeyValidator; this.appSecretResolver = appSecretResolver; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .cors(cors -> {}) .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .authorizeHttpRequests(auth -> auth .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() .requestMatchers( "/actuator/**", "/api/v1/updates/app/check", "/api/v1/updates/app/inspect", "/api/v1/updates/public/**", "/api/v1/rn/update/check", "/api/v1/rn/inspect", "/api/v1/rn/files/**", "/files/apk/**", "/ws/updates/**" ).permitAll() .anyRequest().authenticated() ) .exceptionHandling(ex -> ex .authenticationEntryPoint((req, res, e) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED)) ) // API Key 认证(通用过滤器 + tenant-service 验证) .addFilterBefore(new ApiKeyAuthFilter(apiKeyValidator), UsernamePasswordAuthenticationFilter.class) // HMAC 签名验证(SDK 请求) .addFilterBefore(new AppSignatureAuthFilter(appSecretResolver), UsernamePasswordAuthenticationFilter.class) // JWT 认证 .addFilterBefore(new JwtAuthFilter(jwtUtil), UsernamePasswordAuthenticationFilter.class) .httpBasic(AbstractHttpConfigurer::disable) .formLogin(AbstractHttpConfigurer::disable); return http.build(); } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); String deployMode = System.getenv("DEPLOYMENT_MODE"); if ("PRIVATE".equalsIgnoreCase(deployMode)) { config.setAllowedOriginPatterns(List.of("*")); } else { config.setAllowedOriginPatterns(List.of( "http://localhost:*", "http://127.0.0.1:*", "http://*.xuqinmin.com", "https://*.xuqinmin.com" )); } config.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS")); config.setAllowedHeaders(List.of("*")); config.setExposedHeaders(List.of("Location")); config.setMaxAge(3600L); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }