package com.baiyue.common.security; import com.baiyue.common.core.mybatis.AuditorProvider; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.boot.autoconfigure.AutoConfiguration; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; /** * Servlet 服务安全自动配置,建立无状态 JWT 认证链、统一异常响应和审计操作者解析。 */ @AutoConfiguration( after = {HmacJwtDecoderAutoConfiguration.class, JwksJwtDecoderAutoConfiguration.class}, before = SecurityAutoConfiguration.class) @EnableMethodSecurity public class BaiyueSecurityAutoConfiguration { @Bean @ConditionalOnMissingBean JwtClaimsMapper jwtClaimsMapper() { return new JwtClaimsMapper(); } @Bean @ConditionalOnMissingBean(AuthenticationEntryPoint.class) AuthenticationEntryPoint jsonAuthenticationEntryPoint(ObjectMapper objectMapper) { return new JsonAuthenticationEntryPoint(objectMapper); } @Bean @ConditionalOnMissingBean(AccessDeniedHandler.class) AccessDeniedHandler jsonAccessDeniedHandler(ObjectMapper objectMapper) { return new JsonAccessDeniedHandler(objectMapper); } @Bean @ConditionalOnMissingBean JwtAuthenticationFilter jwtAuthenticationFilter(JwtDecoder jwtDecoder, JwtClaimsMapper claimsMapper, AuthenticationEntryPoint entryPoint, org.springframework.beans.factory.ObjectProvider redis, ObjectMapper objectMapper, @Value("${baiyue.security.token-key-prefix:baiyue:auth}") String keyPrefix, @Value("${baiyue.security.gateway-internal-service-token:${baiyue.security.internal-service-token:${INTERNAL_SERVICE_TOKEN:}}}") String gatewayToken, @Value("${baiyue.security.session-mode:redis}") String sessionMode) { AccessTokenSessionValidator validator; if ("claims".equalsIgnoreCase(sessionMode)) validator = new ClaimsAccessTokenSessionValidator(); else if ("gateway".equalsIgnoreCase(sessionMode)) validator = new GatewayAccessTokenSessionValidator(gatewayToken); else { StringRedisTemplate template=redis.getIfAvailable(); if(template==null) throw new IllegalStateException("session-mode=redis requires StringRedisTemplate"); validator=new RedisAccessTokenSessionValidator(template,keyPrefix,objectMapper); } return new JwtAuthenticationFilter(jwtDecoder, claimsMapper, entryPoint, validator); } @Bean FilterRegistrationBean disableContainerRegistration( JwtAuthenticationFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean<>(filter); registration.setEnabled(false); return registration; } @Bean @ConditionalOnMissingBean(SecurityFilterChain.class) SecurityFilterChain baiyueSecurityFilterChain( HttpSecurity http, JwtAuthenticationFilter jwtFilter, AuthenticationEntryPoint entryPoint, AccessDeniedHandler deniedHandler) throws Exception { return http .csrf(csrf -> csrf.disable()) .httpBasic(httpBasic -> httpBasic.disable()) .formLogin(form -> form.disable()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .exceptionHandling(errors -> errors .authenticationEntryPoint(entryPoint).accessDeniedHandler(deniedHandler)) .authorizeHttpRequests(auth -> auth .requestMatchers("/actuator/health", "/v3/api-docs/**", "/swagger-ui/**").permitAll() .requestMatchers("/internal/**").permitAll() .requestMatchers("/openapi/**").permitAll() .requestMatchers("/api/merchant/**").hasAuthority("APP_MERCHANT") .requestMatchers("/api/platform/**").hasAuthority("APP_PLATFORM") .anyRequest().authenticated()) .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .build(); } @Bean @Primary AuditorProvider securityAuditorProvider() { return new SecurityAuditorProvider(); } }