修改样例配置

This commit is contained in:
孙小云 2025-12-04 15:36:02 +08:00
parent 18c739a10b
commit 9bab576c5c
18 changed files with 795 additions and 7 deletions

119
gateway/.drone.yml Normal file
View File

@ -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

1
gateway/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target/

27
gateway/Dockerfile Normal file
View File

@ -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"]

48
gateway/README.md Normal file
View File

@ -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

122
gateway/pom.xml Normal file
View File

@ -0,0 +1,122 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.18</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.tuoheng</groupId>
<artifactId>gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>gateway</name>
<description>Spring Boot 2.7.x Servlet Gateway</description>
<properties>
<java.version>11</java.version>
<spring-cloud.version>2021.0.8</spring-cloud.version>
</properties>
<dependencies>
<!-- Spring MVC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<!-- Spring Cloud Gateway (Servlet/MVC模式) -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- OAuth2 Resource Server (JWT校验) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- OAuth2 Client (TokenRelay等) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<!-- Nacos Discovery -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2021.0.5.0</version>
</dependency>
<!-- Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
<version>2021.0.5.0</version>
</dependency>
<!-- Spring Cloud Bootstrap -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-bootstrap</artifactId>
<version>3.1.5</version>
</dependency>
<!-- Spring Cloud LoadBalancer -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<!-- JaCoCo 代码覆盖率插件 -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>0.8.8</version>
<executions>
<execution>
<goals>
<goal>prepare-agent</goal>
</goals>
</execution>
<execution>
<id>report</id>
<phase>compile</phase>
<goals>
<goal>report</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- SonarQube 插件 -->
<plugin>
<groupId>org.sonarsource.scanner.maven</groupId>
<artifactId>sonar-maven-plugin</artifactId>
<version>3.9.1.2184</version>
</plugin>
</plugins>
</build>
</project>

14
gateway/settings.xml Normal file
View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
http://maven.apache.org/xsd/settings-1.0.0.xsd">
<mirrors>
<mirror>
<id>aliyun-all</id>
<name>Aliyun Maven (all)</name>
<url>https://maven.aliyun.com/repository/public</url>
<mirrorOf>*</mirrorOf>
</mirror>
</mirrors>
</settings>

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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<Void> 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; // 优先级高
}
}

View File

@ -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/(?<segment>.*), /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/(?<segment>.*), /api/${segment}
spring.cloud.gateway.routes[1].filters[1]=TokenRelay
# OAuth2 配置
spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://oidc:8080/oauth2/jwks

View File

@ -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

55
pom.xml
View File

@ -1,12 +1,53 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>com.tuoheng</groupId> <groupId>com.tuoheng.hxf</groupId>
<artifactId>hyf</artifactId> <artifactId>parent</artifactId>
<version>1.0-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
</parent> </parent>
<artifactId>thingsboard-gateway-ws</artifactId>
<name>Archetype - thingsboard-gateway-ws</name> <artifactId>thingsboard-gateway-ws-demo</artifactId>
<url>http://maven.apache.org</url> <packaging>jar</packaging>
<name>thingsboard-gateway-ws</name>
<description>WebSocket Gateway for ThingsBoard</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Security OAuth2 Resource Server -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- Spring Security OAuth2 Jose (for JWT) -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project> </project>

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<Void> 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; // 优先级高确保在其他过滤器之前执行
}
}

View File

@ -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<WebSocketFilter.Config> {
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 {
}
}

View File

@ -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