Skip to content

Commit

Permalink
Merge pull request #52 from xezzon/feature/issue-49
Browse files Browse the repository at this point in the history
第三方应用调用与校验
  • Loading branch information
komunisto authored Dec 8, 2024
2 parents 66285d8 + 63e8bef commit b957ca6
Show file tree
Hide file tree
Showing 50 changed files with 1,176 additions and 114 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ buildNumber.properties
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf
.vscode

# AWS User-specific
.idea/**/aws.xml
Expand Down
2 changes: 2 additions & 0 deletions .vscode/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*
!.gitignore
49 changes: 49 additions & 0 deletions geom-open-sdk/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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>
<artifactId>geom-open-sdk</artifactId>
<description>构造与geom开放平台交互的HTTP接口的SDK</description>
<name>${project.groupId}:${project.artifactId}</name>

<packaging>jar</packaging>
<parent>
<groupId>io.github.xezzon</groupId>
<artifactId>geom</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<dependencies>
<dependency>
<artifactId>feign-core</artifactId>
<groupId>io.github.openfeign</groupId>
</dependency>
<dependency>
<artifactId>bcpkix-jdk18on</artifactId>
<groupId>org.bouncycastle</groupId>
<version>1.78.1</version>
</dependency>
<dependency>
<artifactId>feign-jackson</artifactId>
<groupId>io.github.openfeign</groupId>
<scope>test</scope>
</dependency>
<dependency>
<artifactId>spring-boot-starter-webflux</artifactId>
<groupId>org.springframework.boot</groupId>
<scope>test</scope>
</dependency>
<dependency>
<artifactId>spring-boot-starter-test</artifactId>
<groupId>org.springframework.boot</groupId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.xezzon.geom;

/**
* @author xezzon
*/
public class GeomOpenConstant {

public static final String DIGEST_ALGORITHM = "HmacSHA256";
public static final String ACCESS_KEY_HEADER = "X-Access-Key";
public static final String TIMESTAMP_HEADER = "X-Timestamp";
public static final String SIGNATURE_HEADER = "X-Signature";

private GeomOpenConstant() {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.github.xezzon.geom;

/**
* @author xezzon
*/
public class GeomOpenException extends RuntimeException {

public GeomOpenException(Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.github.xezzon.geom;

import feign.Feign;
import feign.RequestInterceptor;
import feign.RequestTemplate;
import java.security.Security;
import java.time.Instant;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.bouncycastle.jce.provider.BouncyCastleProvider;

/**
* @author xezzon
*/
public class GeomOpenRequestBuilder {

static {
Security.addProvider(new BouncyCastleProvider());
}

/**
* 应用标识
*/
private final String accessKey;
/**
* 应用密钥(AES)
*/
private final byte[] secretKey;

public GeomOpenRequestBuilder(String accessKey, String secretKey) {
this.accessKey = accessKey;
this.secretKey = Base64.getDecoder().decode(secretKey);
}

public Feign.Builder builder() {
return Feign.builder()
.requestInterceptor(new GeomOpenRequestInterceptor());
}

class GeomOpenRequestInterceptor implements RequestInterceptor {

@Override
public void apply(RequestTemplate requestTemplate) {
// 应用标识
requestTemplate.header(GeomOpenConstant.ACCESS_KEY_HEADER, accessKey);
// 时间戳
long timestamp = Instant.now().toEpochMilli();
requestTemplate.header(GeomOpenConstant.TIMESTAMP_HEADER, String.valueOf(timestamp));
// 摘要
byte[] body = requestTemplate.body();
try {
Mac mac = Mac.getInstance(GeomOpenConstant.DIGEST_ALGORITHM);
mac.init(new SecretKeySpec(secretKey, GeomOpenConstant.DIGEST_ALGORITHM));
mac.update(body);
String signature = Base64.getEncoder().encodeToString(mac.doFinal());
requestTemplate.header(GeomOpenConstant.SIGNATURE_HEADER, signature);
} catch (Exception e) {
throw new GeomOpenException(e);
}
}
}
}
8 changes: 8 additions & 0 deletions geom-open-sdk/src/test/java/io/github/xezzon/geom/Entity.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.github.xezzon.geom;

/**
* @author xezzon
*/
public record Entity(String name) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.github.xezzon.geom;

import static io.github.xezzon.geom.TestApplication.SECRET_KEY;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.boot.test.web.server.LocalServerPort;

/**
* @author xezzon
*/
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class GeomOpenRequestBuilderTest {

@LocalServerPort
private int port;

@Test
void test() {
TestApi testApi = new RequestBuilder("hello", SECRET_KEY).builder()
.target(TestApi.class, "http://localhost:" + this.port);
Entity entity = new Entity("Alice");
String response = testApi.test(entity);
Assertions.assertEquals("Hello, Alice", response);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.xezzon.geom;

import feign.Feign.Builder;
import feign.jackson.JacksonEncoder;

/**
* @author xezzon
*/
public class RequestBuilder extends GeomOpenRequestBuilder {

public RequestBuilder(String accessKey, String secretKey) {
super(accessKey, secretKey);
}

@Override
public Builder builder() {
return super.builder()
.encoder(new JacksonEncoder())
;
}
}
14 changes: 14 additions & 0 deletions geom-open-sdk/src/test/java/io/github/xezzon/geom/TestApi.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package io.github.xezzon.geom;

import feign.Headers;
import feign.RequestLine;

/**
* @author xezzon
*/
public interface TestApi {

@RequestLine("POST /test")
@Headers("Content-Type: application/json")
String test(Entity entity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package io.github.xezzon.geom;

import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
* @author xezzon
*/
@SpringBootApplication
public class TestApplication {

public static final String SECRET_KEY;

static {
try {
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
SecretKey key = keyGenerator.generateKey();
SECRET_KEY = Base64.getEncoder().encodeToString(key.getEncoded());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}

public static void main(String[] args) {
SpringApplication.run(TestApplication.class, args);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.github.xezzon.geom;

import static io.github.xezzon.geom.GeomOpenConstant.DIGEST_ALGORITHM;
import static io.github.xezzon.geom.TestApplication.SECRET_KEY;

import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.Resource;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Duration;
import java.time.Instant;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.junit.jupiter.api.Assertions;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;

/**
* @author xezzon
*/
@RestController
public class TestController {

@Resource
private ObjectMapper objectMapper;

@PostMapping("/test")
public String test(
@RequestBody byte[] body,
@RequestHeader(GeomOpenConstant.ACCESS_KEY_HEADER) String accessKey,
@RequestHeader(GeomOpenConstant.TIMESTAMP_HEADER) Instant timestamp,
@RequestHeader(GeomOpenConstant.SIGNATURE_HEADER) String signature
) throws NoSuchAlgorithmException, InvalidKeyException, IOException {
Assertions.assertEquals("hello", accessKey);
Assertions.assertTrue(timestamp.isBefore(Instant.now()));
Assertions.assertTrue(Duration.between(timestamp, Instant.now()).toMinutes() < 2);
Mac mac = Mac.getInstance(DIGEST_ALGORITHM);
mac.init(new SecretKeySpec(Base64.getDecoder().decode(SECRET_KEY), DIGEST_ALGORITHM));
mac.update(body);
Assertions.assertArrayEquals(Base64.getDecoder().decode(signature), mac.doFinal());
Entity entity = objectMapper.readValue(body, Entity.class);
return "Hello, " + entity.name();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public void loadPrivateKey() {
/* 获取不到文件或解析不了 则生成一对密钥 */
KeyPairGenerator keyPairGenerator;
try {
keyPairGenerator = KeyPairGenerator.getInstance("EC");
keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
log.error("Cannot create key pair.", e);
return;
Expand Down
5 changes: 5 additions & 0 deletions geom-service/geom-service-open/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ service:
## 配置清单
| 变量 | 描述 | 默认值 |
|------------------|------------------|------------------|
| GEOM_JWT_ISSUER | JWT签发机构。建议设置为域名。 | xezzon.github.io |
| GEOM_JWT_TIMEOUT | JWT 有效时长。单位 秒。 | 120 |
其他配置请查看[公共配置清单](../../geom-spring-boot-starter/README.md)。
## 接口描述文档
Expand Down
8 changes: 8 additions & 0 deletions geom-service/geom-service-open/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,12 @@
<app.port>9011</app.port>
<grpc.port>9012</grpc.port>
</properties>

<dependencies>
<dependency>
<artifactId>geom-open-sdk</artifactId>
<groupId>io.github.xezzon</groupId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.github.xezzon.geom.call;

import static com.google.auth.http.AuthHttpConstants.BEARER;

import io.github.xezzon.geom.GeomOpenConstant;
import jakarta.servlet.http.HttpServletResponse;
import java.time.Instant;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

/**
* 订阅服务调用记录
* @author xezzon
*/
@RestController
@RequestMapping("/subscription-call")
public class SubscriptionCallController {

private final SubscriptionCallService subscriptionCallService;

public SubscriptionCallController(SubscriptionCallService subscriptionCallService) {
this.subscriptionCallService = subscriptionCallService;
}

@PostMapping("/validate")
public void validateCall(
@RequestBody byte[] body,
@RequestParam("path") String path,
@RequestHeader(GeomOpenConstant.ACCESS_KEY_HEADER) String accessKey,
@RequestHeader(GeomOpenConstant.TIMESTAMP_HEADER) Instant timestamp,
@RequestHeader(GeomOpenConstant.SIGNATURE_HEADER) String signature,
HttpServletResponse response
) {
/* 所有校验通过后发放令牌 */
String jwt = subscriptionCallService.signJwt(accessKey, path, body, signature, timestamp);
response.setHeader(HttpHeaders.AUTHORIZATION, BEARER + " " + jwt);
}
}
Loading

0 comments on commit b957ca6

Please sign in to comment.