Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

第三方应用调用与校验 #52

Merged
merged 6 commits into from
Dec 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading