diff --git a/gateway/.drone.yml b/gateway/.drone.yml new file mode 100644 index 0000000..94bcee9 --- /dev/null +++ b/gateway/.drone.yml @@ -0,0 +1,119 @@ +clone: + image: registry.t-aaron.com/drone/git:latest + +kind: pipeline +type: kubernetes +name: gateway + +volumes: + - name: maven-cache + host: + path: /opt/maven-cache-default + - name: sonar-cache + host: + path: /opt/sonar-cache-default + +steps: + - name: download-dependencies + image: registry.t-aaron.com/maven:3.8.6-openjdk-11-slim + volumes: + - name: maven-cache + path: /root/.m2 + commands: + - echo "配置 Maven 镜像源..." + - mkdir -p /root/.m2 + - cp settings.xml /root/.m2/settings.xml + - echo "开始下载 Maven 依赖..." + - mvn dependency:go-offline -B + - echo "依赖下载完成!" + - echo "将本地 Maven 缓存同步到工作区用于后续构建..." + - mkdir -p /drone/src/.m2 + - cp -a /root/.m2/. /drone/src/.m2/ + + - name: package + image: registry.t-aaron.com/maven:3.8.6-openjdk-11-slim + volumes: + - name: maven-cache + path: /root/.m2 + commands: + - echo "配置 Maven 镜像源..." + - mkdir -p /root/.m2 + - cp settings.xml /root/.m2/settings.xml + - echo "开始构建 JAR 包..." + - mvn clean package -DskipTests -B + - echo "JAR 包构建完成!" + - ls -la target/*.jar + when: + event: [ push, pull_request ] + depends_on: + - download-dependencies + + - name: sonar-scan + image: registry.t-aaron.com/maven:3.8.6-openjdk-11-slim + volumes: + - name: maven-cache + path: /root/.m2 + - name: sonar-cache + path: /root/.sonar/cache + commands: + - echo "配置 Maven 镜像源..." + - mkdir -p /root/.m2 + - cp settings.xml /root/.m2/settings.xml + - echo "开始 SonarQube 代码质量检查..." + - echo "清理之前的构建文件..." + - rm -rf target/ .mvn/ .classpath .project .settings/ + - echo "编译代码..." + - mvn clean compile + - echo "执行 SonarQube 扫描..." + - mvn sonar:sonar -Dsonar.projectKey=gateway -Dsonar.host.url=https://sonar-ops.t-aaron.com/sonar -Dsonar.login=$SONAR_TOKEN -Dsonar.projectName="Gateway" -Dsonar.projectVersion=${DRONE_COMMIT_SHA:0:8} -Dsonar.sources=src/main/java -Dsonar.java.binaries=target/classes + - echo "SonarQube 代码质量检查完成!" + environment: + SONAR_TOKEN: + from_secret: SONAR_TOKEN + when: + event: [ push, pull_request ] + depends_on: + - download-dependencies + + - name: build-and-push + image: registry.t-aaron.com/plugins/kaniko + settings: + registry: registry.t-aaron.com + repo: registry.t-aaron.com/tuoheng/gateway + cache: true + cache_repo: registry.t-aaron.com/kaniko/cache-gateway + build_args: + - MAVEN_MIRROR_URL=https://maven.aliyun.com/repository/public + username: + from_secret: REGISTRY_USERNAME + password: + from_secret: REGISTRY_PASSWORD + tags: + - latest + - ${DRONE_COMMIT_SHA:0:8} + dockerfile: Dockerfile + context: . + when: + event: [ push, tag ] + depends_on: + - package + + - name: deploy-to-k8s + image: registry.t-aaron.com/alpine/k8s:1.25.9 + commands: + - echo "部署/更新 gateway 到 default 命名空间" + - | + kubectl create deployment gateway \ + --image=registry.t-aaron.com/tuoheng/gateway:${DRONE_COMMIT_SHA:0:8} \ + --port=8080 -n default --dry-run=client -o yaml | kubectl apply -f - + - kubectl set image deployment/gateway gateway=registry.t-aaron.com/tuoheng/gateway:${DRONE_COMMIT_SHA:0:8} -n default --record=true || true + - kubectl create service clusterip gateway --tcp=8080:8080 -n default --dry-run=client -o yaml | kubectl apply -f - + - echo "等待 Deployment 就绪..." + - kubectl rollout status deployment/gateway -n default --timeout=300s + - echo "查看服务与Pod状态" + - kubectl get deploy,svc -n default | grep -i gateway || true + - kubectl get pods -n default -l app=gateway || kubectl get pods -n default | grep gateway || true + when: + event: [ push ] + depends_on: + - build-and-push \ No newline at end of file diff --git a/gateway/.gitignore b/gateway/.gitignore new file mode 100644 index 0000000..2f7896d --- /dev/null +++ b/gateway/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/gateway/Dockerfile b/gateway/Dockerfile new file mode 100644 index 0000000..184a471 --- /dev/null +++ b/gateway/Dockerfile @@ -0,0 +1,27 @@ +# 生产阶段 - 仅复制预构建的 JAR 文件 +FROM registry.t-aaron.com/openjdk:11-jre-slim + +# 创建应用用户 +RUN groupadd -r appuser && useradd -r -g appuser appuser + +# 设置工作目录 +WORKDIR /app + +# 复制预构建的 JAR 文件 +COPY target/*.jar app.jar + +# 更改文件所有者 +RUN chown -R appuser:appuser /app + +# 切换到应用用户 +USER appuser + +# 暴露端口 +EXPOSE 8080 + +# 健康检查 +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD netstat -an | grep :8080 | grep LISTEN || exit 1 + +# 启动应用 +ENTRYPOINT ["java", "-jar", "app.jar"] diff --git a/gateway/README.md b/gateway/README.md new file mode 100644 index 0000000..13a2d63 --- /dev/null +++ b/gateway/README.md @@ -0,0 +1,48 @@ +# Test pipeline trigger +# Test pipeline with secrets permission +# Test with image mirroring +# Test with local images - drone/git and drone/placeholder +# Test with updated RBAC permissions +# Test with uploaded alpine image +# Test host volume mount +# Test host volume mount again +# Test after server config update +# Test with emptyDir volume +Trigger new build +Trigger Kaniko build +Retrigger build with correct image +Trigger build test +Test build +Test registry mirror +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +xx +a +a +a +xx +xx +xx diff --git a/gateway/pom.xml b/gateway/pom.xml new file mode 100644 index 0000000..0caeac4 --- /dev/null +++ b/gateway/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 2.7.18 + + + com.tuoheng + gateway + 0.0.1-SNAPSHOT + gateway + Spring Boot 2.7.x Servlet Gateway + + 11 + 2021.0.8 + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-discovery + 2021.0.5.0 + + + + + com.alibaba.cloud + spring-cloud-starter-alibaba-nacos-config + 2021.0.5.0 + + + + + org.springframework.cloud + spring-cloud-starter-bootstrap + 3.1.5 + + + + + org.springframework.cloud + spring-cloud-starter-loadbalancer + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.jacoco + jacoco-maven-plugin + 0.8.8 + + + + prepare-agent + + + + report + compile + + report + + + + + + + + org.sonarsource.scanner.maven + sonar-maven-plugin + 3.9.1.2184 + + + + \ No newline at end of file diff --git a/gateway/settings.xml b/gateway/settings.xml new file mode 100644 index 0000000..7749838 --- /dev/null +++ b/gateway/settings.xml @@ -0,0 +1,14 @@ + + + + + aliyun-all + Aliyun Maven (all) + https://maven.aliyun.com/repository/public + * + + + diff --git a/gateway/src/main/java/com/tuoheng/gateway/GatewayApplication.java b/gateway/src/main/java/com/tuoheng/gateway/GatewayApplication.java new file mode 100644 index 0000000..186d9ac --- /dev/null +++ b/gateway/src/main/java/com/tuoheng/gateway/GatewayApplication.java @@ -0,0 +1,13 @@ +package com.tuoheng.gateway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; + +@SpringBootApplication +@EnableDiscoveryClient +public class GatewayApplication { + public static void main(String[] args) { + SpringApplication.run(GatewayApplication.class, args); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java b/gateway/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java new file mode 100644 index 0000000..b65652c --- /dev/null +++ b/gateway/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java @@ -0,0 +1,22 @@ +package com.tuoheng.gateway.config; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.web.server.SecurityWebFilterChain; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig { + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + http + .authorizeExchange(exchanges -> exchanges + .pathMatchers("/a/**").authenticated() + .pathMatchers("/b/**").authenticated() + .anyExchange().permitAll() + ) + .oauth2ResourceServer(oauth2 -> oauth2.jwt()); + return http.build(); + } +} \ No newline at end of file diff --git a/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java b/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java new file mode 100644 index 0000000..ff17242 --- /dev/null +++ b/gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java @@ -0,0 +1,77 @@ +package com.tuoheng.gateway.filter; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpHeaders; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; + +@Component +public class JwtPermissionFilter implements GlobalFilter, Ordered { + + + /** + * 这边能获取到Token里面的值 + * @param exchange + * @param chain + * @return + */ + @Override + public Mono filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) { + + // 获取完整的请求URL + String fullUrl = exchange.getRequest().getURI().toString(); + System.out.println("用户访问的完整URL: " + fullUrl); + + // 获取请求的path + String path = exchange.getRequest().getPath().toString(); + System.out.println("用户访问的path: " + path); + + // 获取请求的host + String host = exchange.getRequest().getHeaders().getHost().toString(); + System.out.println("用户访问的host: " + host); + + String hostName = exchange.getRequest().getHeaders().getHost().getHostName(); + System.out.println("用户访问的域名: " + hostName); + + // 获取Referer(如果有) + String referer = exchange.getRequest().getHeaders().getFirst("Referer"); + System.out.println("Referer: " + referer); +// 从Spring Security上下文获取JWT + return ReactiveSecurityContextHolder.getContext() + .flatMap(securityContext -> { + if (securityContext.getAuthentication() instanceof JwtAuthenticationToken) { + JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) securityContext.getAuthentication(); + Jwt jwt = jwtAuth.getToken(); + String username = jwt.getClaimAsString("username"); + String clientId = jwt.getClaimAsString("client_id"); + String tenantCode = jwt.getClaimAsString("tenant_code"); + String authorities = jwt.getClaimAsString("clientIds"); + + // 你可以在这里做权限判断 + System.out.println("网关解析到token字段:"); + System.out.println("用户名: " + username); + System.out.println("客户端ID: " + clientId); + System.out.println("租户代码: " + tenantCode); + System.out.println("用户权限: " + authorities); + + //该域名没有权限 + if(!authorities.contains(hostName)){ + exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.FORBIDDEN); + return exchange.getResponse().setComplete(); + } + } + return chain.filter(exchange); + }); + + } + + @Override + public int getOrder() { + return -1; // 优先级高 + } +} \ No newline at end of file diff --git a/gateway/src/main/resources/application.properties b/gateway/src/main/resources/application.properties new file mode 100644 index 0000000..b5475db --- /dev/null +++ b/gateway/src/main/resources/application.properties @@ -0,0 +1,26 @@ +server.port=8080 + +# 应用名称 +spring.application.name=gateway + +# Nacos 服务发现配置 +spring.cloud.nacos.discovery.server-addr=nacos:8848 +spring.cloud.nacos.discovery.namespace=public +spring.cloud.nacos.discovery.group=DEFAULT_GROUP +spring.cloud.nacos.discovery.enabled=true + +# Gateway 路由配置 - 使用服务发现 +spring.cloud.gateway.routes[0].id=resource-server-a +spring.cloud.gateway.routes[0].uri=lb://aserver +spring.cloud.gateway.routes[0].predicates[0]=Path=/a/** +spring.cloud.gateway.routes[0].filters[0]=RewritePath=/a/(?.*), /api/${segment} +spring.cloud.gateway.routes[0].filters[1]=TokenRelay + +spring.cloud.gateway.routes[1].id=resource-server-b +spring.cloud.gateway.routes[1].uri=lb://bserver +spring.cloud.gateway.routes[1].predicates[0]=Path=/b/** +spring.cloud.gateway.routes[1].filters[0]=RewritePath=/b/(?.*), /api/${segment} +spring.cloud.gateway.routes[1].filters[1]=TokenRelay + +# OAuth2 配置 +spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://oidc:8080/oauth2/jwks \ No newline at end of file diff --git a/gateway/src/main/resources/bootstrap.properties b/gateway/src/main/resources/bootstrap.properties new file mode 100644 index 0000000..4bc2f0c --- /dev/null +++ b/gateway/src/main/resources/bootstrap.properties @@ -0,0 +1,9 @@ +# Bootstrap configuration for Nacos config center +spring.application.name=gateway + +# Nacos config center configuration +spring.cloud.nacos.config.server-addr=nacos:8848 +spring.cloud.nacos.config.namespace=public +spring.cloud.nacos.config.group=DEFAULT_GROUP +spring.cloud.nacos.config.file-extension=properties +spring.cloud.nacos.config.enabled=true diff --git a/pom.xml b/pom.xml index 0fe87b6..4b08ada 100644 --- a/pom.xml +++ b/pom.xml @@ -1,12 +1,53 @@ + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - com.tuoheng - hyf - 1.0-SNAPSHOT + com.tuoheng.hxf + parent + 0.0.1-SNAPSHOT - thingsboard-gateway-ws - Archetype - thingsboard-gateway-ws - http://maven.apache.org + + thingsboard-gateway-ws-demo + jar + + thingsboard-gateway-ws + WebSocket Gateway for ThingsBoard + + + UTF-8 + + + + + + org.springframework.cloud + spring-cloud-starter-gateway + + + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + + + org.springframework.security + spring-security-oauth2-jose + + + + + org.projectlombok + lombok + true + + + + + org.springframework.boot + spring-boot-starter-test + test + + diff --git a/src/main/java/com/tuoheng/gateway/GatewayApplication.java b/src/main/java/com/tuoheng/gateway/GatewayApplication.java new file mode 100644 index 0000000..71fb3da --- /dev/null +++ b/src/main/java/com/tuoheng/gateway/GatewayApplication.java @@ -0,0 +1,12 @@ +package com.tuoheng.gateway; + +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); + } +} diff --git a/src/main/java/com/tuoheng/gateway/config/GatewayConfig.java b/src/main/java/com/tuoheng/gateway/config/GatewayConfig.java new file mode 100644 index 0000000..a641ed2 --- /dev/null +++ b/src/main/java/com/tuoheng/gateway/config/GatewayConfig.java @@ -0,0 +1,35 @@ +package com.tuoheng.gateway.config; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.route.RouteLocator; +import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Gateway 路由配置 + * 配置 WebSocket 转发规则 + */ +@Slf4j +@Configuration +public class GatewayConfig { + + /** + * 配置路由规则 + * 将 /ws/api/** 转发到 ws://iot.t-aaron.com:18080/api/** + */ + @Bean + public RouteLocator customRouteLocator(RouteLocatorBuilder builder) { + return builder.routes() + .route("websocket-route", r -> r + .path("/ws/api/**") + .filters(f -> f + .stripPrefix(1) // 移除 /ws 前缀 + .filter(new com.tuoheng.gateway.filter.WebSocketFilter().apply( + new com.tuoheng.gateway.filter.WebSocketFilter.Config())) + ) + .uri("ws://iot.t-aaron.com:18080") + ) + .build(); + } +} diff --git a/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java b/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java new file mode 100644 index 0000000..dbced4f --- /dev/null +++ b/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java @@ -0,0 +1,52 @@ +package com.tuoheng.gateway.config; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; +import org.springframework.security.web.server.SecurityWebFilterChain; + +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; +import java.nio.charset.StandardCharsets; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig { + + @Bean + @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.enabled", havingValue = "true", matchIfMissing = false) + public SecurityWebFilterChain securityWebFilterChainWithJwt(ServerHttpSecurity http) { + http + .authorizeExchange(exchanges -> exchanges + .pathMatchers("/ws/api/**").authenticated() + .anyExchange().permitAll() + ) + .oauth2ResourceServer(oauth2 -> oauth2.jwt()) + .csrf().disable(); + return http.build(); + } + + @Bean + @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.enabled", havingValue = "false", matchIfMissing = true) + public SecurityWebFilterChain securityWebFilterChainWithoutJwt(ServerHttpSecurity http) { + http + .authorizeExchange(exchanges -> exchanges + .anyExchange().permitAll() + ) + .csrf().disable(); + return http.build(); + } + + @Bean + @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.enabled", havingValue = "true") + public ReactiveJwtDecoder jwtDecoder() { + // 这是一个示例密钥,生产环境中应该使用配置文件中的密钥或从认证服务器获取 + String secret = "your-256-bit-secret-key-here-must-be-at-least-32-characters-long"; + SecretKey key = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); + return NimbusReactiveJwtDecoder.withSecretKey(key).build(); + } +} diff --git a/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java b/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java new file mode 100644 index 0000000..e980412 --- /dev/null +++ b/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java @@ -0,0 +1,74 @@ +package com.tuoheng.gateway.filter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.Ordered; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.oauth2.jwt.Jwt; +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; + +/** + * JWT 权限过滤器 + * 用于验证用户的 JWT token 并检查域名访问权限 + */ +@Slf4j +@Component +public class JwtPermissionFilter implements GlobalFilter, Ordered { + + @Override + public Mono filter(ServerWebExchange exchange, org.springframework.cloud.gateway.filter.GatewayFilterChain chain) { + // 获取请求信息 + String fullUrl = exchange.getRequest().getURI().toString(); + String path = exchange.getRequest().getPath().toString(); + String host = exchange.getRequest().getHeaders().getHost().toString(); + String hostName = exchange.getRequest().getHeaders().getHost().getHostName(); + String referer = exchange.getRequest().getHeaders().getFirst("Referer"); + + log.info("用户访问的完整URL: {}", fullUrl); + log.info("用户访问的path: {}", path); + log.info("用户访问的host: {}", host); + log.info("用户访问的域名: {}", hostName); + log.info("Referer: {}", referer); + + // 从 Spring Security 上下文获取 JWT + return ReactiveSecurityContextHolder.getContext() + .flatMap(securityContext -> { + if (securityContext.getAuthentication() instanceof JwtAuthenticationToken) { + JwtAuthenticationToken jwtAuth = (JwtAuthenticationToken) securityContext.getAuthentication(); + Jwt jwt = jwtAuth.getToken(); + + // 提取 JWT 中的字段 + String username = jwt.getClaimAsString("username"); + String clientId = jwt.getClaimAsString("client_id"); + String tenantCode = jwt.getClaimAsString("tenant_code"); + String authorities = jwt.getClaimAsString("clientIds"); + + log.info("网关解析到token字段:"); + log.info("用户名: ", username); + log.info("客户端ID: {}", clientId); + log.info("租户代码: {}", tenantCode); + log.info("用户权限: {}", authorities); + + // 检查该域名是否有权限访问 + if (authorities == null || !authorities.contains(hostName)) { + log.warn("用户 {} 无权访问域名: {}", username, hostName); + exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN); + return exchange.getResponse().setComplete(); + } + + log.info("用户 {} 通过权限验证,允许访问域名: {}", username, hostName); + } + return chain.filter(exchange); + }) + .switchIfEmpty(chain.filter(exchange)); + } + + @Override + public int getOrder() { + return -1; // 优先级高,确保在其他过滤器之前执行 + } +} diff --git a/src/main/java/com/tuoheng/gateway/filter/WebSocketFilter.java b/src/main/java/com/tuoheng/gateway/filter/WebSocketFilter.java new file mode 100644 index 0000000..dbbeb57 --- /dev/null +++ b/src/main/java/com/tuoheng/gateway/filter/WebSocketFilter.java @@ -0,0 +1,59 @@ +package com.tuoheng.gateway.filter; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.cloud.gateway.filter.GatewayFilter; +import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +/** + * WebSocket 过滤器 + * 用于处理 WebSocket 连接的转发和日志记录 + */ +@Slf4j +@Component +public class WebSocketFilter extends AbstractGatewayFilterFactory { + + public WebSocketFilter() { + super(Config.class); + } + + @Override + public GatewayFilter apply(Config config) { + return (exchange, chain) -> { + logRequest(exchange); + + return chain.filter(exchange).then(Mono.fromRunnable(() -> { + logResponse(exchange); + })); + }; + } + + /** + * 记录 WebSocket 请求信息 + */ + private void logRequest(ServerWebExchange exchange) { + HttpHeaders headers = exchange.getRequest().getHeaders(); + String path = exchange.getRequest().getURI().getPath(); + String upgrade = headers.getFirst(HttpHeaders.UPGRADE); + String connection = headers.getFirst(HttpHeaders.CONNECTION); + + log.info("WebSocket 请求 - Path: {}, Upgrade: {}, Connection: {}", path, upgrade, connection); + log.debug("请求头: {}", headers); + } + + /** + * 记录 WebSocket 响应信息 + */ + private void logResponse(ServerWebExchange exchange) { + log.info("WebSocket 响应 - Status: {}", exchange.getResponse().getStatusCode()); + } + + /** + * 配置类 + */ + public static class Config { + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..c49e34b --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,37 @@ +server: + port: 8080 + +spring: + application: + name: thingsboard-gateway-ws + cloud: + gateway: + routes: + - id: websocket-route + uri: ws://iot.t-aaron.com:18080 + predicates: + - Path=/ws/api/** + filters: + - StripPrefix=1 + - name: WebSocketFilter + +# 启用 JWT 认证(默认为 false,即不启用) +# spring.security.oauth2.resourceserver.jwt.enabled: false + +# 如果需要启用 JWT 认证,请配置以下内容: +# spring: +# security: +# oauth2: +# resourceserver: +# jwt: +# enabled: true +# issuer-uri: https://your-auth-server.com +# # 或者使用 jwk-set-uri: +# # jwk-set-uri: https://your-auth-server.com/.well-known/jwks.json + +logging: + level: + org.springframework.cloud.gateway: DEBUG + org.springframework.web.reactive: DEBUG + org.springframework.security: DEBUG + com.tuoheng.gateway: DEBUG