Explorar el Código

Merge branch 'master' of https://gitee.com/li_xueqiang1/kxs

* 'master' of https://gitee.com/li_xueqiang1/kxs:
  update README.md.
  update README.md.
  update README.md.
  update README.md.
  update README.md.
  update README.md.
  update README.md.
  gateway网关解密,只支持get和post
  gateway网关解密,只支持get和post
  gateway网关解密,只支持get和post

# Conflicts:
#	kxs-gateway/src/main/java/com/kxs/gateway/api/filter/RequestDecoderFilter.java
#	kxs-gateway/src/main/resources/application.yml
lixueqiang hace 2 años
padre
commit
1db455e745

BIN
1700399839501.jpg


+ 11 - 2
README.md

@@ -1,9 +1,12 @@
 ### 系统说明
-- 定位多服务多模块架构;功能分离;服务分离;轻量级快速开发框架
+- 定位JAVA开发多模块架构;功能聚合;服务分离;轻量级快速开发框架
 - 内置基于OAuth2 的 RBAC 权限管理系统
-- 基于网关统一调度所有模块
+- 基于网关统一路由所有服务,可实现动态负载均衡
+- 前置网关加解密、请求限流、header头清洗
+- 内置定时任务统一调度
 - 支持容器化一键部署架构
 - 管理后台基于vue3的element-plus开发
+- 可无缝对接nacos、dubbo、spring-cloud
 ### 后端版本说明
 | Jdk                         | 17    |
 |-----------------------------|-------|
@@ -134,4 +137,10 @@ BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserte
 			}));
 ```
 
+### 认证授权
 
+ **登陆授权流程图** 
+
+![流程图](1700399839501.jpg)
+
+ **后续文档编写中...** 

+ 21 - 46
kxs-common/kxs-common-security/src/main/java/com/kxs/common/security/util/OAuth2EndpointUtils.java

@@ -15,9 +15,9 @@ import org.springframework.util.LinkedMultiValueMap;
 import org.springframework.util.MultiValueMap;
 import org.springframework.util.StreamUtils;
 
-import java.io.*;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.lang.reflect.Field;
-import java.nio.charset.StandardCharsets;
 import java.time.temporal.ChronoUnit;
 import java.util.HashMap;
 import java.util.Map;
@@ -35,55 +35,30 @@ public class OAuth2EndpointUtils {
 
 	public MultiValueMap<String, String> getParameters(HttpServletRequest request) {
 
-		String bodyString = getBodyString(request);
-		JSONObject jsonObject = JSON.parseObject(bodyString);
+		try(ByteArrayOutputStream baos = new ByteArrayOutputStream()){
+			StreamUtils.copy(request.getInputStream(), baos);
+			String bodyStr = baos.toString();
+			Map body = JSON.parseObject(bodyStr, Map.class);
 
-		Map<String, String[]> parameterMap = request.getParameterMap();
-		MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
-		parameterMap.forEach((key, values) -> {
-			for (String value : values) {
-				parameters.add(key, value);
-			}
-		});
-		if(jsonObject != null && !jsonObject.isEmpty()){
-			for(Object key:jsonObject.keySet()){
-				String value = jsonObject.get(key).toString();
-				parameters.add(key.toString(), value);
-			}
-		}
-
-		return parameters;
-	}
-	private String getBodyString(HttpServletRequest request) {
-		StringBuilder sb = new StringBuilder();
-		InputStream inputStream = null;
-		BufferedReader reader = null;
-		try {
-			inputStream = request.getInputStream();
-			reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
-			String line = "";
-			while ((line = reader.readLine()) != null) {
-				sb.append(line);
-			}
-		} catch (IOException e) {
-			e.printStackTrace();
-		} finally {
-			if (inputStream != null) {
-				try {
-					inputStream.close();
-				} catch (IOException e) {
-					e.printStackTrace();
+			Map<String, String[]> parameterMap = request.getParameterMap();
+			MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size());
+			parameterMap.forEach((key, values) -> {
+				for (String value : values) {
+					parameters.add(key, value);
 				}
-			}
-			if (reader != null) {
-				try {
-					reader.close();
-				} catch (IOException e) {
-					e.printStackTrace();
+			});
+			if(body != null && !body.isEmpty()){
+				for(Object key:body.keySet()){
+					String value = body.get(key).toString();
+					parameters.add(key.toString(), value);
 				}
 			}
+
+			return parameters;
+		}catch (IOException e){
+			e.printStackTrace();
+			throw new OAuth2AuthenticationException("获取参数异常");
 		}
-		return sb.toString();
 	}
 
 	public void throwError(String errorCode, String parameterName, String errorUri) {

+ 10 - 0
kxs-gateway/pom.xml

@@ -56,6 +56,16 @@
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-http</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.alibaba</groupId>
+            <artifactId>fastjson</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.jetbrains</groupId>
+            <artifactId>annotations</artifactId>
+            <version>13.0</version>
+            <scope>compile</scope>
+        </dependency>
     </dependencies>
 
 

+ 3 - 3
kxs-gateway/src/main/java/com/kxs/gateway/api/config/GatewayConfiguration.java

@@ -2,7 +2,7 @@ package com.kxs.gateway.api.config;
 
 import cn.hutool.crypto.symmetric.AES;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.kxs.gateway.api.filter.KxsRequestGlobalFilter;
+import com.kxs.gateway.api.filter.RequestGlobalFilter;
 import com.kxs.gateway.api.filter.RequestDecoderFilter;
 import com.kxs.gateway.api.filter.ValidateCodeGatewayFilter;
 import com.kxs.gateway.api.handler.GlobalExceptionHandler;
@@ -23,8 +23,8 @@ import org.springframework.data.redis.core.RedisTemplate;
 public class GatewayConfiguration {
 
 	@Bean
-	public KxsRequestGlobalFilter skyRequestGlobalFilter() {
-		return new KxsRequestGlobalFilter();
+	public RequestGlobalFilter requestGlobalFilter() {
+		return new RequestGlobalFilter();
 	}
 
 	@Bean

+ 108 - 71
kxs-gateway/src/main/java/com/kxs/gateway/api/filter/RequestDecoderFilter.java

@@ -1,33 +1,35 @@
 package com.kxs.gateway.api.filter;
 
-import cn.hutool.core.text.CharSequenceUtil;
 import cn.hutool.crypto.symmetric.AES;
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
 import com.kxs.common.core.constant.enums.ErrorTypeEnum;
 import com.kxs.common.core.exception.AesDecodeException;
-import com.kxs.common.core.exception.CheckedException;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.jetbrains.annotations.NotNull;
 import org.springframework.cloud.gateway.filter.GatewayFilter;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
 import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
-import org.springframework.cloud.gateway.filter.factory.rewrite.CachedBodyOutputMessage;
-import org.springframework.cloud.gateway.support.BodyInserterContext;
 import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DataBufferFactory;
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.HttpMethod;
-import org.springframework.http.ReactiveHttpOutputMessage;
+import org.springframework.http.MediaType;
 import org.springframework.http.server.reactive.ServerHttpRequest;
 import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
-import org.springframework.web.reactive.function.BodyInserter;
-import org.springframework.web.reactive.function.BodyInserters;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
 import org.springframework.web.reactive.function.server.HandlerStrategies;
 import org.springframework.web.reactive.function.server.ServerRequest;
 import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.UriComponentsBuilder;
 import reactor.core.publisher.Flux;
 import reactor.core.publisher.Mono;
 
-import java.lang.reflect.Field;
 import java.net.URI;
-import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.atomic.AtomicInteger;
+
 
 
 /**
@@ -53,82 +55,117 @@ public class RequestDecoderFilter extends AbstractGatewayFilterFactory<Object> {
 
 			if (method == HttpMethod.GET) {
 				// 1 修改请求参数,并获取请求参数
-				try {
-					updateRequestParam(exchange);
-				}
-				catch (Exception e) {
-					throw new CheckedException("解密异常");
-				}
+				return decryptGetRequestParam(exchange, chain);
 			}
 
-			if (method != HttpMethod.POST) {
-				return chain.filter(exchange);
+			if (method == HttpMethod.POST) {
+				return decryptPostBodyParam(exchange, chain);
 			}
 
+			return chain.filter(exchange);
 
+		};
 
-			// 2 获取请求体,修改请求体
-			ServerRequest serverRequest = ServerRequest.create(exchange,
-					HandlerStrategies.withDefaults().messageReaders());
-
-			Mono<String> modifiedBody = serverRequest.bodyToMono(String.class).flatMap(body -> {
-				// 解密请求体
-				try {
-					log.info("Post请求:{},待解密请求参数:{}", request.getURI().getPath(), body);
-					String encrypt = cryptoAes.decryptStr(body);
-					log.info("Post请求:{},解密后参数:{}", request.getURI().getPath(), encrypt);
-					return Mono.just(encrypt);
+	}
 
-				}
-				catch (Exception e) {
-					log.error(ErrorTypeEnum.DECRYPT_ERROR.getDescription(), e);
-					return Mono.error(new AesDecodeException(ErrorTypeEnum.DECRYPT_ERROR.getDescription()));
-				}
-			});
+	/**
+	 * 解密post-body参数
+	 *
+	 * @param exchange exchange
+	 * @param chain    chain
+	 * @return mono<void>
+	 */
+	private Mono<Void> decryptPostBodyParam(ServerWebExchange exchange, GatewayFilterChain chain) {
+		//获取请求体,修改请求体
+		ServerRequest serverRequest = ServerRequest.create(exchange,
+				HandlerStrategies.withDefaults().messageReaders());
+
+		//请求长度
+		AtomicInteger setContentLength = new AtomicInteger();
+		//获取请求头
+		MediaType contentType = exchange.getRequest().getHeaders().getContentType();
+		Flux<DataBuffer> modifiedBody = serverRequest.bodyToFlux(String.class).flatMap(body -> {
+			// 解密请求体
+			try {
+				log.info("待解密请求参数:{}", body);
+				String decryptStr = cryptoAes.decryptStr(body);
+				log.info("解密后参数:{}", decryptStr);
+
+				DataBufferFactory dataBufferFactory = exchange.getResponse().bufferFactory();
+				DataBuffer bodyDataBuffer = dataBufferFactory.wrap(decryptStr.getBytes());
+				//获取参数长度
+//					setContentLength.set(encrypt.getBytes().length);
+				//添加到请求体
+				return Flux.just(bodyDataBuffer);
+			} catch (Exception e) {
+				log.error(ErrorTypeEnum.DECRYPT_ERROR.getDescription(), e);
+				return Flux.error(new AesDecodeException(ErrorTypeEnum.DECRYPT_ERROR.getDescription()));
+			}
+		});
+		ServerHttpRequest mutatedRequest = new ServerHttpRequestDecorator(
+				exchange.getRequest()) {
+			@NotNull
+			@Override
+			public HttpHeaders getHeaders() {
+				//重新设置请求头
+				HttpHeaders httpHeaders = new HttpHeaders();
+				httpHeaders.putAll(super.getHeaders());
+				httpHeaders.remove(HttpHeaders.CONTENT_TYPE);
+				httpHeaders.remove(HttpHeaders.CONTENT_LENGTH);
+				assert contentType != null;
+				httpHeaders.set(HttpHeaders.CONTENT_TYPE, contentType.toString());
+
+				return httpHeaders;
+			}
 
-			// 3 创建BodyInserter修改请求体
-			BodyInserter<Mono<String>, ReactiveHttpOutputMessage> bodyInserter = BodyInserters
-				.fromPublisher(modifiedBody, String.class);
-			HttpHeaders headers = new HttpHeaders();
-			headers.putAll(exchange.getRequest().getHeaders());
-			headers.remove(HttpHeaders.CONTENT_LENGTH);
-			// 4 创建CachedBodyOutputMessage并且把请求param加入
-			CachedBodyOutputMessage outputMessage = new CachedBodyOutputMessage(exchange, headers);
-			return bodyInserter.insert(outputMessage, new BodyInserterContext()).then(Mono.defer(() -> {
-				ServerHttpRequestDecorator decorator = new ServerHttpRequestDecorator(exchange.getRequest()) {
-					@Override
-					public Flux<DataBuffer> getBody() {
-						return outputMessage.getBody();
-					}
-				};
-				return chain.filter(exchange.mutate().request(decorator).build());
-			}));
+			@NotNull
+			@Override
+			public Flux<DataBuffer> getBody() {
+				return modifiedBody;
+			}
 		};
+		return chain.filter(exchange.mutate().request(mutatedRequest).build());
 	}
 
+
 	/**
-	 * 修改前端传的参数
+	 * 解密GET请求参数
+	 *
+	 * @param exchange exchange
+	 * @param chain    chain
+	 * @return mono<void>
 	 */
-	private void updateRequestParam(ServerWebExchange exchange) throws NoSuchFieldException, IllegalAccessException {
-		ServerHttpRequest request = exchange.getRequest();
-		// 请求链接
-		URI uri = request.getURI();
-		// 请求参数
-		String value = request.getQueryParams().getFirst(PARAM_NAME);
-		log.info("Get请求:{},待解密请求参数:{}", uri.getPath(), value);
-		// 判断是否有加密的参数 这里的约定是 param
-		if (CharSequenceUtil.isNotBlank(value)) {
-			// 解密请求参数
-			String decryPtoData = cryptoAes.decryptStr(value);
-			log.info("Get请求:{},解密之后参数:{}", uri.getPath(), decryPtoData);
-			// 使用反射强行拿出 URI 的 query
-			Field targetQuery = uri.getClass().getDeclaredField(PARAM_NAME);
-			// 授权
-			targetQuery.setAccessible(true);
-			// 重新设置参数
-			targetQuery.set(uri, decryPtoData);
+	private Mono<Void> decryptGetRequestParam(ServerWebExchange exchange, GatewayFilterChain chain) {
+
+		try {
+			MultiValueMap<String, String> queryParams = exchange.getRequest().getQueryParams();
+			String decryptedParam = queryParams.getFirst(PARAM_NAME);
+			log.info("GET请求待解密数据 :{}", decryptedParam);
+			String decryptStr = cryptoAes.decryptStr(decryptedParam);
+			log.info("GET解密后数据 :{}", decryptStr);
+
+			JSONObject decryptJson = JSON.parseObject(decryptStr);
+			//构建新的请求参数webflux
+			MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(decryptJson.size());
+			decryptJson.forEach((key, value) -> {
+				parameters.add(key, (String) value);
+			});
+			// 构建修改后的URI
+			URI modifiedUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())
+					.replaceQueryParams(parameters)
+					.build(true)
+					.toUri();
+			// 修改请求,设置新的URI
+			ServerHttpRequest modifiedRequest = exchange.getRequest().mutate()
+					.uri(modifiedUri)
+					.build();
+			// 用修改后的请求继续处理请求链
+			return chain.filter(exchange.mutate().request(modifiedRequest).build());
+		} catch (Exception e) {
+			log.error(ErrorTypeEnum.DECRYPT_ERROR.getDescription(), e);
+			return Mono.error(new AesDecodeException(ErrorTypeEnum.DECRYPT_ERROR.getDescription()));
 		}
+
 	}
 
 }
-

+ 2 - 10
kxs-gateway/src/main/java/com/kxs/gateway/api/filter/KxsRequestGlobalFilter.java → kxs-gateway/src/main/java/com/kxs/gateway/api/filter/RequestGlobalFilter.java

@@ -8,16 +8,10 @@ import org.springframework.cloud.gateway.filter.GlobalFilter;
 import org.springframework.core.Ordered;
 import org.springframework.core.env.Environment;
 import org.springframework.http.server.reactive.ServerHttpRequest;
-import org.springframework.util.StringUtils;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 
-import java.util.Arrays;
 import java.util.Collections;
-import java.util.stream.Collectors;
-
-import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR;
-import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.addOriginalRequestUrl;
 
 /**
  * @author LXQ
@@ -25,11 +19,11 @@ import static org.springframework.cloud.gateway.support.ServerWebExchangeUtils.a
  * <p>
  * 全局拦截器,作用所有的微服务
  * <p>
- * 1. 对请求头中参数进行处理 from 参数进行清洗 2. 重写StripPrefix = 1,支持全局
+ * 1. 对请求头中参数进行处理 from 参数进行清洗
  * <p>
  * 支持swagger添加X-Forwarded-Prefix header (F SR2 已经支持,不需要自己维护)
  */
-public class KxsRequestGlobalFilter implements GlobalFilter, Ordered {
+public class RequestGlobalFilter implements GlobalFilter, Ordered {
 
 	/**
 	 * Process the Web request and (optionally) delegate to the next {@code WebFilter}
@@ -41,8 +35,6 @@ public class KxsRequestGlobalFilter implements GlobalFilter, Ordered {
 	@Override
 	public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
 
-		Environment environment = SpringContextHolder.getApplicationContext().getEnvironment();
-
 		// 1. 清洗请求头中from 参数
 		ServerHttpRequest request = exchange.getRequest().mutate().headers(httpHeaders -> {
 			httpHeaders.remove(SecurityConstants.FROM);

+ 4 - 3
kxs-gateway/src/main/java/com/kxs/gateway/api/handler/GlobalExceptionHandler.java

@@ -16,11 +16,12 @@ import org.springframework.web.server.ServerWebExchange;
 import org.springframework.web.server.handler.ResponseStatusExceptionHandler;
 import reactor.core.publisher.Mono;
 
+
 /**
  * 网关异常通用处理器,只作用在webflux 环境下 , 优先级低于 {@link ResponseStatusExceptionHandler} 执行
  *
- * @author 冷酱
- * @date 2021/5/26
+ * @author 没秃顶的码农
+ * @date 2023/11/19
  */
 @Slf4j
 @Order(-1)
@@ -46,7 +47,7 @@ public class GlobalExceptionHandler implements ErrorWebExceptionHandler {
 		return response.writeWith(Mono.fromSupplier(() -> {
 			DataBufferFactory bufferFactory = response.bufferFactory();
 			try {
-				log.warn("Error Spring Cloud Gateway : {} {}", exchange.getRequest().getPath(), ex.getMessage());
+				log.warn("Error Spring Gateway : {} {}", exchange.getRequest().getPath(), ex.getMessage());
 				return bufferFactory.wrap(objectMapper.writeValueAsBytes(R.failed(ex.getMessage())));
 			}
 			catch (JsonProcessingException e) {

+ 23 - 23
kxs-gateway/src/main/resources/application.yml

@@ -2,40 +2,50 @@
 server:
   port: 9999
 spring:
+  profiles:
+    active: @profiles.active@
+  application:
+    name: kxs-gateway
   cloud:
     gateway:
       routes:
         #ADMIN 模块
-        - id: kxs-system-biz
-          uri: lb://kxs-system-biz
+        - id: kxs-system-biz  # 唯一的服务ID
+          uri: lb://kxs-system-biz # instances下的服务名称,实现负载均衡
           predicates:
-            - Path=/v1/kxs/admin/**
+            - Path=/v1/kxs/admin/** #当前服务的请求前缀,
           filters:
             # 过滤前缀
             - StripPrefix=3
             # 限流配置
             - name: RequestRateLimiter
               args:
-                key-resolver: '#{@remoteAddrKeyResolver}'
-                redis-rate-limiter.replenishRate: 100
-                redis-rate-limiter.burstCapacity: 200
+                key-resolver: '#{@remoteAddrKeyResolver}' #SPEL表达式去的对应的bean
+                redis-rate-limiter.replenishRate: 100 # 令牌桶每秒填充平均速率
+                redis-rate-limiter.burstCapacity: 200 # 令牌桶总容量
             # 验证码处理
-#            - ValidateCodeGatewayFilter
+            - ValidateCodeGatewayFilter #如果该服务需要验证码则开启此过滤器
             # 解密过滤器
-#            - RequestDecoderFilter
+            - RequestDecoderFilter
+
         # 代码生成模块
         - id: kxs-codegen
-          uri: lb://kxs-codegen:5002
+          uri: lb://kxs-codegen
           predicates:
             - Path=/v1/kxs/gen/**
           filters:
             # 过滤前缀
             - StripPrefix=3
+
         # 定时任务成模块
-        - id: sky-quartz
-          uri: lb://sky-quartz
+        - id: kxs-quartz
+          uri: lb://kxs-quartz
           predicates:
-            - Path=/job/**
+            - Path=/v1/kxs/job/**
+          filters:
+            # 过滤前缀
+            - StripPrefix=3
+
         # 固定路由转发配置 无修改
         - id: openapi
           uri: lb://sky-gateway
@@ -44,16 +54,6 @@ spring:
           filters:
             - RewritePath=/v3/api-docs/(?<path>.*), /$\{path}/$\{path}/v3/api-docs
 
-
-    discovery:
-      client:
-        simple:
-          instances:
-            kxs-system-biz: #负载均衡
-              - uri: http://127.0.0.1:8080
-            kxs-codegen:
-              - uri: http://127.0.0.1:5002
-
 gateway:
   aes:
     encode-key: 'CBTU1dD4Kd5pyiGWTsI10jRQ3SvKusSV'
@@ -62,4 +62,4 @@ gateway:
     encode-padding: 'PKCS7Padding'
   #不校验验证码的客户端
   ignore-clients:
-    - sky
+    - kxs

+ 16 - 6
kxs-system/kxs-system-biz/src/main/java/com/kxs/system/biz/controller/LoginController.java

@@ -3,12 +3,14 @@ package com.kxs.system.biz.controller;
 import cn.hutool.core.util.StrUtil;
 import com.kxs.common.core.constant.SecurityConstants;
 import com.kxs.common.core.util.R;
+import com.kxs.common.security.annotation.Inner;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.AllArgsConstructor;
+import org.springdoc.core.annotations.ParameterObject;
 import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.Map;
 
 /**
  * 登录控制器
@@ -28,8 +30,16 @@ public class LoginController {
 
 	private static final Integer DEFAULT_IMAGE_HEIGHT = 40;
 
-	// public R getCode(@RequestParam String randomStr){
-	//
-	// }
+	@GetMapping("/test")
+	@Inner(value = false)
+	 public R getCode(@RequestParam Map randomStr){
+		return R.ok(randomStr);
+	 }
+
+	@PostMapping("/testPost")
+	@Inner(value = false)
+	 public R testPost(@RequestBody Map randomStr){
+		return R.ok(randomStr);
+	 }
 
 }