1
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
package com.example.geteway;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
@SpringBootApplication
|
||||
public class GatewayApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(GatewayApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.example.geteway.config;
|
||||
|
||||
import org.jspecify.annotations.NonNull;
|
||||
import org.springframework.http.HttpCookie;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
public class CookieBearerTokenResolver extends ServerBearerTokenAuthenticationConverter {
|
||||
|
||||
|
||||
private final String cookieName;
|
||||
|
||||
public CookieBearerTokenResolver(String cookieName) {
|
||||
this.cookieName = cookieName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Mono<Authentication> convert(ServerWebExchange exchange) {
|
||||
HttpCookie cookie = exchange.getRequest().getCookies().getFirst(cookieName);
|
||||
if (cookie == null || !StringUtils.hasText(cookie.getValue())) {
|
||||
// No token → return empty → triggers 401 Unauthorized
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
String token = cookie.getValue().trim();
|
||||
if (token.isEmpty()) {
|
||||
return Mono.empty();
|
||||
}
|
||||
|
||||
// Create authentication token with the extracted JWT
|
||||
return Mono.just(new BearerTokenAuthenticationToken(token));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.geteway.config;
|
||||
|
||||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
class InstallOpenTelemetryAppender implements InitializingBean {
|
||||
|
||||
private final OpenTelemetry openTelemetry;
|
||||
|
||||
InstallOpenTelemetryAppender(OpenTelemetry openTelemetry) {
|
||||
this.openTelemetry = openTelemetry;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
OpenTelemetryAppender.install(this.openTelemetry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package com.example.geteway.config;
|
||||
|
||||
import io.micrometer.core.instrument.Tags;
|
||||
import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics;
|
||||
import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics;
|
||||
import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics;
|
||||
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmClassLoadingMeterConventions;
|
||||
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmCpuMeterConventions;
|
||||
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmMemoryMeterConventions;
|
||||
import io.micrometer.core.instrument.binder.jvm.convention.otel.OpenTelemetryJvmThreadMeterConventions;
|
||||
import io.micrometer.core.instrument.binder.system.ProcessorMetrics;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.observation.OpenTelemetryServerRequestObservationConvention;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 指标配置,选择需要的指标
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public class OpenTelemetryConfiguration {
|
||||
|
||||
@Bean
|
||||
OpenTelemetryServerRequestObservationConvention openTelemetryServerRequestObservationConvention() {
|
||||
return new OpenTelemetryServerRequestObservationConvention();
|
||||
}
|
||||
|
||||
@Bean
|
||||
OpenTelemetryJvmCpuMeterConventions openTelemetryJvmCpuMeterConventions() {
|
||||
return new OpenTelemetryJvmCpuMeterConventions(Tags.empty());
|
||||
}
|
||||
|
||||
@Bean
|
||||
ProcessorMetrics processorMetrics() {
|
||||
return new ProcessorMetrics(List.of(), new OpenTelemetryJvmCpuMeterConventions(Tags.empty()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
JvmMemoryMetrics jvmMemoryMetrics() {
|
||||
return new JvmMemoryMetrics(List.of(), new OpenTelemetryJvmMemoryMeterConventions(Tags.empty()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
JvmThreadMetrics jvmThreadMetrics() {
|
||||
return new JvmThreadMetrics(List.of(), new OpenTelemetryJvmThreadMeterConventions(Tags.empty()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
ClassLoaderMetrics classLoaderMetrics() {
|
||||
return new ClassLoaderMetrics(new OpenTelemetryJvmClassLoadingMeterConventions());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.example.geteway.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
|
||||
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
|
||||
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
|
||||
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
|
||||
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
|
||||
import org.springframework.security.web.server.SecurityWebFilterChain;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.reactive.CorsConfigurationSource;
|
||||
import org.springframework.web.cors.reactive.CorsWebFilter;
|
||||
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.reactive.config.CorsRegistry;
|
||||
import org.springframework.web.reactive.config.WebFluxConfigurer;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Configuration
|
||||
@EnableWebFluxSecurity
|
||||
public class SecurityConfig implements WebFluxConfigurer {
|
||||
|
||||
@Bean
|
||||
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
|
||||
http
|
||||
.csrf(ServerHttpSecurity.CsrfSpec::disable)
|
||||
.authorizeExchange(exchanges ->
|
||||
exchanges
|
||||
.pathMatchers("/api/public/**", "/api/auth/**").permitAll() // 公共API路径
|
||||
.pathMatchers("/api/**").authenticated() // 保护你的 API 路由
|
||||
.anyExchange().permitAll()
|
||||
)
|
||||
.oauth2ResourceServer(oauth2 ->
|
||||
oauth2.bearerTokenConverter(new CookieBearerTokenResolver("access_token"))
|
||||
.jwt(Customizer.withDefaults())
|
||||
)
|
||||
.cors(cors -> cors.configurationSource(corsConfigurationSource())); // 使用自定义CORS配置
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsConfigurationSource corsConfigurationSource() {
|
||||
CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOriginPatterns(List.of("*"));
|
||||
configuration.setAllowedHeaders(List.of("*"));
|
||||
configuration.setAllowedMethods(List.of("*"));
|
||||
configuration.setAllowCredentials(true);
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
|
||||
@Bean
|
||||
CorsWebFilter corsWebFilter() {
|
||||
return new CorsWebFilter(corsConfigurationSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addCorsMappings(CorsRegistry registry) {
|
||||
registry.addMapping("/**")
|
||||
.allowedOriginPatterns("*")
|
||||
.allowedHeaders("*")
|
||||
.allowedMethods("*")
|
||||
.allowCredentials(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.example.geteway.config;
|
||||
|
||||
import io.micrometer.tracing.TraceContext;
|
||||
import io.micrometer.tracing.Tracer;
|
||||
import jakarta.annotation.Nullable;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import org.springframework.web.server.WebFilter;
|
||||
import org.springframework.web.server.WebFilterChain;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
|
||||
@Component
|
||||
class TraceIdFilter implements WebFilter {
|
||||
|
||||
private final Tracer tracer;
|
||||
|
||||
TraceIdFilter(Tracer tracer) {
|
||||
this.tracer = tracer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
|
||||
String traceId = getTraceId();
|
||||
|
||||
if (traceId != null) {
|
||||
exchange.getRequest().mutate()
|
||||
.header("X-Trace-Id", traceId)
|
||||
.build();
|
||||
}
|
||||
return chain.filter(exchange);
|
||||
}
|
||||
|
||||
private @Nullable String getTraceId() {
|
||||
TraceContext context = this.tracer.currentTraceContext().context();
|
||||
return context != null ? context.traceId() : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.example.geteway.config;
|
||||
|
||||
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
|
||||
import org.springframework.cloud.gateway.filter.GlobalFilter;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Component
|
||||
@Order(1000)
|
||||
public class UserIdMappingFilter implements GlobalFilter {
|
||||
|
||||
@Override
|
||||
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
|
||||
System.out.println(exchange.getRequest().getHeaders());
|
||||
|
||||
|
||||
return ReactiveSecurityContextHolder.getContext()
|
||||
.mapNotNull(SecurityContext::getAuthentication)
|
||||
.cast(JwtAuthenticationToken.class) // ✅ 先转为 JwtAuthenticationToken
|
||||
.map(AbstractOAuth2TokenAuthenticationToken::getToken) // ✅ 再获取 Jwt
|
||||
.map(jwt -> {
|
||||
String userId = jwt.getSubject(); // ✅ 现在可以安全调用
|
||||
if (userId == null || userId.isBlank()) {
|
||||
throw new IllegalArgumentException("JWT missing 'sub' claim");
|
||||
}
|
||||
ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
|
||||
.header("X-User-Id", userId)
|
||||
.build();
|
||||
return exchange.mutate().request(modifiedRequest).build();
|
||||
})
|
||||
.switchIfEmpty(Mono.just(exchange))
|
||||
.flatMap(chain::filter);
|
||||
}
|
||||
}
|
||||
70
gateway/src/main/resources/application.yml
Normal file
70
gateway/src/main/resources/application.yml
Normal file
@@ -0,0 +1,70 @@
|
||||
server:
|
||||
port: 8083
|
||||
#spring:
|
||||
# application:
|
||||
# name: api-gateway
|
||||
# cloud:
|
||||
# gateway:
|
||||
# server:
|
||||
# webflux:
|
||||
# default-filters:
|
||||
# - RewritePath=/api/(?<service>.*)/(?<path>.*), /$\{path}
|
||||
# enabled: true
|
||||
# routes:
|
||||
# - id: user-service
|
||||
# uri: http://a-service:8091
|
||||
# predicates:
|
||||
# - Path=/api/auth/**
|
||||
# security:
|
||||
# oauth2:
|
||||
# resourceserver:
|
||||
# jwt:
|
||||
# jwk-set-uri: http://auth-service:9000/oauth2/jwks
|
||||
# client:
|
||||
# provider:
|
||||
# spring:
|
||||
# issuer-uri: http://auth-service:9000
|
||||
|
||||
spring:
|
||||
cloud:
|
||||
gateway:
|
||||
server:
|
||||
webflux:
|
||||
default-filters:
|
||||
- RewritePath=/api/(?<service>.*)/(?<path>.*), /$\{path}
|
||||
enabled: true
|
||||
routes:
|
||||
- id: user-service
|
||||
uri: http://localhost:8091
|
||||
predicates:
|
||||
- Path=/api/auth/**
|
||||
|
||||
security:
|
||||
oauth2:
|
||||
resourceserver:
|
||||
jwt:
|
||||
jwk-set-uri: http://localhost:9000/oauth2/jwks
|
||||
client:
|
||||
provider:
|
||||
spring:
|
||||
issuer-uri: http://localhost:9000
|
||||
management:
|
||||
otlp:
|
||||
metrics:
|
||||
export:
|
||||
url: http://192.168.1.14:9090/api/v1/otlp/v1/metrics #Prometheus otlp协议 http地址
|
||||
step: 30s
|
||||
opentelemetry:
|
||||
tracing:
|
||||
export:
|
||||
otlp:
|
||||
endpoint: http://192.168.1.14:4317/v1/traces #Jaeger otlp协议 grpc地址
|
||||
transport: grpc
|
||||
# endpoint: http://localhost:4318/v1/traces
|
||||
# transport: http
|
||||
|
||||
logging:
|
||||
export:
|
||||
otlp:
|
||||
endpoint: http://192.168.1.14:32664/otlp/v1/logs #Loki otlp协议 grpc地址
|
||||
# transport: grpc
|
||||
12
gateway/src/main/resources/logback-spring.xml
Normal file
12
gateway/src/main/resources/logback-spring.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration>
|
||||
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||
|
||||
<appender name="OTEL" class="io.opentelemetry.instrumentation.logback.appender.v1_0.OpenTelemetryAppender">
|
||||
</appender>
|
||||
|
||||
<root level="INFO">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
<appender-ref ref="OTEL"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.example.geteway;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
||||
@SpringBootTest
|
||||
class GatewayApplicationTests {
|
||||
|
||||
@Test
|
||||
void contextLoads() {
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user