修改样例配置
This commit is contained in:
parent
18c739a10b
commit
9bab576c5c
|
|
@ -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
|
||||
|
|
@ -0,0 +1 @@
|
|||
target/
|
||||
|
|
@ -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"]
|
||||
|
|
@ -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
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; // 优先级高
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
55
pom.xml
|
|
@ -1,12 +1,53 @@
|
|||
<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>
|
||||
<parent>
|
||||
<groupId>com.tuoheng</groupId>
|
||||
<artifactId>hyf</artifactId>
|
||||
<version>1.0-SNAPSHOT</version>
|
||||
<groupId>com.tuoheng.hxf</groupId>
|
||||
<artifactId>parent</artifactId>
|
||||
<version>0.0.1-SNAPSHOT</version>
|
||||
</parent>
|
||||
<artifactId>thingsboard-gateway-ws</artifactId>
|
||||
<name>Archetype - thingsboard-gateway-ws</name>
|
||||
<url>http://maven.apache.org</url>
|
||||
|
||||
<artifactId>thingsboard-gateway-ws-demo</artifactId>
|
||||
<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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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; // 优先级高,确保在其他过滤器之前执行
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue