From a9691afe7cf0823b738f969baeef28ffe900cabe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E5=B0=8F=E4=BA=91?= Date: Thu, 4 Dec 2025 15:36:54 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8F=90=E4=BA=A4submodule?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- gateway/.drone.yml | 119 +++++++++++++++++ gateway/.gitignore | 1 + gateway/Dockerfile | 27 ++++ gateway/README.md | 48 +++++++ gateway/pom.xml | 122 ++++++++++++++++++ gateway/settings.xml | 14 ++ .../tuoheng/gateway/GatewayApplication.java | 13 ++ .../gateway/config/SecurityConfig.java | 22 ++++ .../gateway/filter/JwtPermissionFilter.java | 77 +++++++++++ .../src/main/resources/application.properties | 26 ++++ .../src/main/resources/bootstrap.properties | 9 ++ thingsboard-gateway-ws-demo | 2 +- 12 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 gateway/.drone.yml create mode 100644 gateway/.gitignore create mode 100644 gateway/Dockerfile create mode 100644 gateway/README.md create mode 100644 gateway/pom.xml create mode 100644 gateway/settings.xml create mode 100644 gateway/src/main/java/com/tuoheng/gateway/GatewayApplication.java create mode 100644 gateway/src/main/java/com/tuoheng/gateway/config/SecurityConfig.java create mode 100644 gateway/src/main/java/com/tuoheng/gateway/filter/JwtPermissionFilter.java create mode 100644 gateway/src/main/resources/application.properties create mode 100644 gateway/src/main/resources/bootstrap.properties 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/thingsboard-gateway-ws-demo b/thingsboard-gateway-ws-demo index 18c739a..9bab576 160000 --- a/thingsboard-gateway-ws-demo +++ b/thingsboard-gateway-ws-demo @@ -1 +1 @@ -Subproject commit 18c739a10b45f3ea3365a561181f92df40dd9cbb +Subproject commit 9bab576c5c96627aae9205a83282136d7ff00ab4