101 lines
5.5 KiB
Java
101 lines
5.5 KiB
Java
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 = 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<StringRedisTemplate> redis,
|
|
ObjectMapper objectMapper,
|
|
@Value("${baiyue.security.token-key-prefix:baiyue:auth}") String keyPrefix,
|
|
@Value("${baiyue.security.session-mode:redis}") String sessionMode,
|
|
@Value("${baiyue.security.session-validation-uri:http://auth:9100/internal/auth/accounts/sessions/validate}") String validationUri,
|
|
@Value("${baiyue.security.internal-service-token}") String internalToken) {
|
|
if (!"redis".equalsIgnoreCase(sessionMode)) {
|
|
throw new IllegalStateException("Only shared Redis access sessions are supported");
|
|
}
|
|
StringRedisTemplate template = redis.getIfAvailable();
|
|
if (template == null) throw new IllegalStateException("Redis access sessions require StringRedisTemplate");
|
|
AccessTokenSessionValidator validator = new RedisAccessTokenSessionValidator(template, keyPrefix, objectMapper,
|
|
new HttpSessionStateVerifier(validationUri, internalToken, objectMapper));
|
|
return new JwtAuthenticationFilter(jwtDecoder, claimsMapper, entryPoint, validator);
|
|
}
|
|
|
|
@Bean
|
|
FilterRegistrationBean<JwtAuthenticationFilter> disableContainerRegistration(
|
|
JwtAuthenticationFilter filter) {
|
|
FilterRegistrationBean<JwtAuthenticationFilter> 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/**", "/api/pda-updates/**").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(); }
|
|
}
|