Эх сурвалжийг харах

Merge branch '84-integrate' into 'develop'

develop-20220804

1. 新增 license 试用锁定功能
2. token 解析 username 至 request handler

See merge request !9

linwenhua 3 жил өмнө
parent
commit
dd935e73ad

+ 2 - 5
src/main/java/com/inspur/smsb/gateway/SmsbGatewayApplication.java

@@ -3,12 +3,8 @@ package com.inspur.smsb.gateway;
 import org.springframework.boot.SpringApplication;
 import org.springframework.boot.autoconfigure.SpringBootApplication;
 import org.springframework.boot.autoconfigure.security.reactive.ReactiveUserDetailsServiceAutoConfiguration;
-import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
-import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient;
-import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.server.WebSession;
-import reactor.core.publisher.Mono;
 
 /**
  * 网关服务
@@ -17,6 +13,7 @@ import reactor.core.publisher.Mono;
  */
 @SpringBootApplication(exclude = ReactiveUserDetailsServiceAutoConfiguration.class)
 @RestController
+@EnableScheduling
 public class SmsbGatewayApplication {
     public static void main(String[] args) {
         SpringApplication.run(SmsbGatewayApplication.class, args);

+ 21 - 0
src/main/java/com/inspur/smsb/gateway/dto/license/LicenseDto.java

@@ -0,0 +1,21 @@
+package com.inspur.smsb.gateway.dto.license;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @author zengweijie
+ * @version 1.0
+ * @date 2022/7/27 13:37
+ **/
+@Data
+public class LicenseDto implements Serializable {
+    private static final long serialVersionUID = 460738581763959036L;
+    Integer duration;
+    Integer account;
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
+    LocalDateTime lastUpdate;
+}

+ 146 - 0
src/main/java/com/inspur/smsb/gateway/filter/LicenseFilter.java

@@ -0,0 +1,146 @@
+package com.inspur.smsb.gateway.filter;
+
+import com.alibaba.cola.dto.Response;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.inspur.smsb.gateway.dto.license.LicenseDto;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.beans.factory.InitializingBean;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.cloud.gateway.filter.GatewayFilterChain;
+import org.springframework.cloud.gateway.filter.GlobalFilter;
+import org.springframework.core.Ordered;
+import org.springframework.core.io.buffer.DataBufferFactory;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
+import org.springframework.stereotype.Component;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import javax.annotation.Resource;
+import java.io.*;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * @author zengweijie
+ * @version 1.0
+ * @date 2022/7/27 11:20
+ **/
+@Slf4j
+@Component
+public class LicenseFilter implements GlobalFilter, Ordered, InitializingBean, DisposableBean {
+    @Resource
+    private ObjectMapper objectMapper;
+    private final AtomicBoolean sysLocked = new AtomicBoolean(false);
+    private final AtomicBoolean timeRollBack = new AtomicBoolean(false);
+    private static final int DEFAULT_DURATION = 60;
+    private static final int STEP = 5;
+    @Value("${license-dir}")
+    private String licenseDir;
+    private ThreadPoolTaskExecutor threadPoolTaskExecutor;
+
+    @Override
+    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
+        if (sysLocked.get() || timeRollBack.get()) {
+            ServerHttpResponse response = exchange.getResponse();
+            response.setStatusCode(HttpStatus.LOCKED);
+            response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
+            String msg = sysLocked.get() ? "系统已锁定" : "发现时间回退,系统已锁定";
+            return response.writeWith(Mono.fromSupplier(() -> {
+                DataBufferFactory bufferFactory = response.bufferFactory();
+                try {
+                    return bufferFactory.wrap(objectMapper.writeValueAsBytes(Response.buildFailure(String.valueOf(HttpStatus.LOCKED.value()), msg)));
+                } catch (JsonProcessingException e) {
+                    log.error("Error writing response", e);
+                    return bufferFactory.wrap(new byte[0]);
+                }
+            }));
+        }
+        return chain.filter(exchange);
+    }
+
+    public boolean checkSysLocked() {
+        if (sysLocked.get() || timeRollBack.get()) {
+            return false;
+        }
+        File directory = new File(licenseDir);
+        if (!directory.exists() && !directory.mkdirs()) {
+            return false;
+        }
+        File licenseFile = new File(directory.getAbsolutePath() + File.separator + "license.json");
+        if (!licenseFile.exists()) {
+            return false;
+        }
+        LicenseDto licenseDto = new LicenseDto();
+        try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(licenseFile))) {
+            if (inputStream.available() > 0) {
+                licenseDto = objectMapper.readValue(inputStream.readAllBytes(), LicenseDto.class);
+            }
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return true;
+        }
+        LocalDateTime now = LocalDateTime.now();
+        if (Objects.nonNull(licenseDto.getLastUpdate()) && licenseDto.getLastUpdate().isAfter(now)) {
+            timeRollBack.set(true);
+            return false;
+        }
+        int duration = Optional.ofNullable(licenseDto.getDuration()).orElse(DEFAULT_DURATION);
+        int account = Optional.ofNullable(licenseDto.getAccount()).orElse(0);
+        if (duration <= account) {
+            sysLocked.set(true);
+            return false;
+        }
+        licenseDto.setDuration(duration);
+        licenseDto.setAccount(account + STEP);
+        licenseDto.setLastUpdate(now);
+        try (BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(licenseFile))) {
+            outputStream.write(objectMapper.writeValueAsBytes(licenseDto));
+        } catch (IOException e) {
+            log.error(e.getMessage(), e);
+            return true;
+        }
+        return true;
+    }
+
+    @Override
+    public int getOrder() {
+        return HIGHEST_PRECEDENCE;
+    }
+
+    @Override
+    public void destroy() {
+        threadPoolTaskExecutor.shutdown();
+    }
+
+    @Override
+    public void afterPropertiesSet() {
+        threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
+        threadPoolTaskExecutor.setCorePoolSize(1);
+        threadPoolTaskExecutor.setMaxPoolSize(1);
+        threadPoolTaskExecutor.setQueueCapacity(0);
+        threadPoolTaskExecutor.setKeepAliveSeconds(60);
+        threadPoolTaskExecutor.setThreadNamePrefix("license-Async-Executor-");
+        threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(false);
+        threadPoolTaskExecutor.setAwaitTerminationSeconds(0);
+        threadPoolTaskExecutor.initialize();
+        threadPoolTaskExecutor.execute(() -> {
+            while (checkSysLocked()) {
+                try {
+                    Thread.sleep(Duration.ofMinutes(STEP).toMillis());
+                } catch (InterruptedException e) {
+                    log.error(e.getMessage(), e);
+                    Thread.currentThread().interrupt();
+                }
+            }
+            threadPoolTaskExecutor.shutdown();
+        });
+    }
+}

+ 2 - 2
src/main/java/com/inspur/smsb/gateway/filter/WebFluxUserRequestInfoFilter.java

@@ -24,7 +24,7 @@ import java.util.Map;
 import java.util.stream.Collectors;
 
 /**
- * 过滤器,在请求头中解析 JWT 字段,并解析出 userId 回填
+ * 过滤器,在请求头中解析 JWT 字段,并解析出 userId,userName 回填
  *
  * @author liangke
  */
@@ -91,10 +91,10 @@ public class WebFluxUserRequestInfoFilter implements GlobalFilter {
                     //just skip this adapter
                     return chain.filter(exchange);
                 }
-
                 ServerHttpRequest request = exchange.getRequest()
                     .mutate()
                     .header("userId", String.valueOf(jwsObject.getPayload().toJSONObject().get("sub")))
+                    .header("userName", String.valueOf(jwsObject.getPayload().toJSONObject().get("preferred_username")))
                     .build();
                 // 把新的 exchange 放回到过滤链
                 return chain.filter(exchange.mutate().request(request).build());