У меня есть две цепочки фильтров: первая — для пользовательского интерфейса с keycloak, а вторая — для публичного доступа для «клиентских» конечных точек с помощью apiKey, а некоторые из них должны быть доступны всем, например, для проверки электронной почты.
Я сижу над этой проблемой несколько дней и не могу' Я действительно не нашел решения и надеюсь, что кто-нибудь сможет мне помочь.
Версия Spring Boot: 3.0.5
Зависимости Spring Boot Security:
Код: Выделить всё
org.springframework.security
spring-security-core
6.1.3
org.springframework.boot
spring-boot-starter-security
org.springframework.boot
spring-boot-starter-oauth2-resource-server
ApiFilter:< /p>
Код: Выделить всё
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.info("ApiKeyFilter invoked for request: " + request.getRequestURI());
if (ApiContext.isApi()) {
String requestApiKey = request.getHeader("X-API-KEY");
String requestApiSecret = request.getHeader("X-SECRET-KEY");
if (requestApiKey == null || requestApiSecret == null) {
throw new BadCredentialsException("BadCredentials");
}
Optional apiInformationOptional = this.apiInformationRepository.findByApiKey(requestApiKey);
if (!apiInformationOptional.isPresent()) {
throw new BadCredentialsException("BadCredentials");
}
ApiInformation apiInformation = apiInformationOptional.get();
if (!apiInformation.getApiKey().equals(requestApiKey) || !apiInformation.getSecretKey().equals(requestApiSecret)) {
throw new BadCredentialsException("BadCredentials");
}
Optional tenantInformationOptional = this.tenantInformationRepository.findByOrganization(apiInformation.getOrganization());
if (!tenantInformationOptional.isPresent()) {
throw new BadCredentialsException("BadCredentials");
}
TenantInformation tenantInformation = tenantInformationOptional.get();
Authentication authentication = new UsernamePasswordAuthenticationToken(apiInformation.getApiKey(), null, new ArrayList());
SecurityContextHolder.getContext().setAuthentication(authentication);
TenantContext.setCurrentTenant(tenantInformation.getTenantId());
} else if (ApiContext.isActuator()) {
Authentication authentication = new UsernamePasswordAuthenticationToken("GenericUser", null, new ArrayList());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
Код: Выделить всё
@Bean
@Order(1)
@DependsOn("corsConfigurationSource")
public SecurityFilterChain apiServerFilterChain(
HttpSecurity http,
@Qualifier("corsConfigurationSource") CorsConfigurationSource corsConfigurationSource
) throws Exception {
http.authorizeHttpRequests((authorize) ->
authorize.requestMatchers(
GenericAbstractControllerInterface.PUBLIC_API_DOC_BASE_URI + "/**",
GenericAbstractControllerInterface.API_PUBLIC_URI + "/**",
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(apiFilter, ChannelProcessingFilter.class)
.addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.cors(cors -> {
cors.configurationSource(corsConfigurationSource);
})
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
@Bean
@Order(0)
public SecurityFilterChain resourceServerFilterChain(
HttpSecurity http,
@Qualifier("corsConfigurationSource") CorsConfigurationSource corsConfigurationSource
) throws Exception {
List subscriptions = subscriptionRepository.findAll();
List allRoles = subscriptions.stream()
.map(subscription -> subscription.getRoles().split(","))
.flatMap(Arrays::stream)
.collect(Collectors.toList());
allRoles.add("ORGANIZATION_ADMIN");
allRoles.add("ORGANIZATION_USER");
http.authorizeHttpRequests(
authorizeRequests -> {
try {
authorizeRequests.requestMatchers(
GenericAbstractControllerInterface.API_PUBLIC_URI + "/**"
).permitAll()
.anyRequest().authenticated().and().oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthConverter);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
);
http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()).disable());
http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.httpBasic(withDefaults());
http.cors(cors -> cors.configurationSource(corsConfigurationSource));
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.addAllowedOriginPattern("*");
configuration.setAllowedMethods(Arrays.asList(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.OPTIONS.name()
));
configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization", "Access-Control-Allow-Methods", "X-TENANT-ID", "X-API-KEY", "X-ACTUATOR", "X-GUI"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Он устанавливает, например. если запрос поступает к общедоступному API к PUBLIC_API, GUI или API
Мои конечные точки, которые проверяются через apiKey, работают как положено, но я не знаю, является ли это лучшим решением.
ApiFilter:
Код: Выделить всё
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.info("ApiKeyFilter invoked for request: " + request.getRequestURI());
if (ApiContext.isApi()) {
String requestApiKey = request.getHeader("X-API-KEY");
String requestApiSecret = request.getHeader("X-SECRET-KEY");
if (requestApiKey == null || requestApiSecret == null) {
throw new BadCredentialsException("BadCredentials");
}
Optional apiInformationOptional = this.apiInformationRepository.findByApiKey(requestApiKey);
if (!apiInformationOptional.isPresent()) {
throw new BadCredentialsException("BadCredentials");
}
ApiInformation apiInformation = apiInformationOptional.get();
if (!apiInformation.getApiKey().equals(requestApiKey) || !apiInformation.getSecretKey().equals(requestApiSecret)) {
throw new BadCredentialsException("BadCredentials");
}
Optional tenantInformationOptional = this.tenantInformationRepository.findByOrganization(apiInformation.getOrganization());
if (!tenantInformationOptional.isPresent()) {
throw new BadCredentialsException("BadCredentials");
}
TenantInformation tenantInformation = tenantInformationOptional.get();
Authentication authentication = new UsernamePasswordAuthenticationToken(apiInformation.getApiKey(), null, new ArrayList());
SecurityContextHolder.getContext().setAuthentication(authentication);
TenantContext.setCurrentTenant(tenantInformation.getTenantId());
} else if (ApiContext.isActuator()) {
Authentication authentication = new UsernamePasswordAuthenticationToken("GenericUser", null, new ArrayList());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
Код: Выделить всё
@Bean
@Order(1)
@DependsOn("corsConfigurationSource")
public SecurityFilterChain apiServerFilterChain(
HttpSecurity http,
@Qualifier("corsConfigurationSource") CorsConfigurationSource corsConfigurationSource
) throws Exception {
http.authorizeHttpRequests((authorize) ->
authorize.requestMatchers(
GenericAbstractControllerInterface.PUBLIC_API_DOC_BASE_URI + "/**",
GenericAbstractControllerInterface.API_PUBLIC_URI + "/**",
).permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(apiFilter, ChannelProcessingFilter.class)
.addFilterBefore(apiKeyFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.cors(cors -> {
cors.configurationSource(corsConfigurationSource);
})
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
@Bean
@Order(0)
public SecurityFilterChain resourceServerFilterChain(
HttpSecurity http,
@Qualifier("corsConfigurationSource") CorsConfigurationSource corsConfigurationSource
) throws Exception {
List subscriptions = subscriptionRepository.findAll();
List allRoles = subscriptions.stream()
.map(subscription -> subscription.getRoles().split(","))
.flatMap(Arrays::stream)
.collect(Collectors.toList());
allRoles.add("ORGANIZATION_ADMIN");
allRoles.add("ORGANIZATION_USER");
http.authorizeHttpRequests(
authorizeRequests -> {
try {
authorizeRequests.requestMatchers(
GenericAbstractControllerInterface.API_PUBLIC_URI + "/**"
).permitAll()
.anyRequest().authenticated().and().oauth2ResourceServer().jwt().jwtAuthenticationConverter(jwtAuthConverter);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
);
http.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()).csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()).disable());
http.sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
http.httpBasic(withDefaults());
http.cors(cors -> cors.configurationSource(corsConfigurationSource));
return http.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowCredentials(true);
configuration.addAllowedOriginPattern("*");
configuration.setAllowedMethods(Arrays.asList(
HttpMethod.GET.name(),
HttpMethod.POST.name(),
HttpMethod.PUT.name(),
HttpMethod.DELETE.name(),
HttpMethod.OPTIONS.name()
));
configuration.setAllowedHeaders(Arrays.asList("Content-Type", "Authorization", "Access-Control-Allow-Methods", "X-TENANT-ID", "X-API-KEY", "X-ACTUATOR", "X-GUI"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
Он устанавливает, например. если запрос поступает от общедоступного API к PUBLIC_API, GUI или API
Подробнее здесь: https://stackoverflow.com/questions/786 ... s-expected
Мобильная версия