This is an automated email from the ASF dual-hosted git repository.
dengliming pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shenyu-website.git
The following commit(s) were added to refs/heads/main by this push:
new afbdbe0190b Update documentation of Sign plugin (#848)
afbdbe0190b is described below
commit afbdbe0190bf452dbf5f38d06f593229df79d06e
Author: 愿凌飞 <[email protected]>
AuthorDate: Mon Jan 16 11:52:33 2023 +0800
Update documentation of Sign plugin (#848)
* [doc: Sign plugin] update documentation of Sign plugin
* [doc: Sign plugin] update documentation of Sign plugin
* fix style
* style
* fix img
---
docs/developer/custom-sign-algorithm.md | 78 ++---
docs/plugin-center/security/sign-plugin.md | 362 +++++++++++++------
.../current/developer/custom-sign-algorithm.md | 71 ++--
.../current/plugin-center/security/sign-plugin.md | 385 +++++++++++++++------
static/img/shenyu/plugin/sign/request_body.png | Bin 0 -> 24774 bytes
.../shenyu/plugin/sign/version2_sign_request.png | Bin 0 -> 67345 bytes
.../sign/version2_sign_request_with_body.png | Bin 0 -> 66866 bytes
7 files changed, 604 insertions(+), 292 deletions(-)
diff --git a/docs/developer/custom-sign-algorithm.md
b/docs/developer/custom-sign-algorithm.md
index e2db2135d4a..db231ad5c4e 100644
--- a/docs/developer/custom-sign-algorithm.md
+++ b/docs/developer/custom-sign-algorithm.md
@@ -10,24 +10,43 @@ description: specify sign plugins for examination
## Extension
-* The default implementation is
`org.apache.shenyu.plugin.sign.service.DefaultSignService`.
-* Declare a new class named `CustomSignService` and implements
`org.apache.shenyu.plugin.sign.api.SignService`.
+* The default implementation is
`org.apache.shenyu.plugin.sign.service.ComposableSignService`.
+
+ ```java
+ @Bean
+ @ConditionalOnMissingBean(value = SignService.class, search =
SearchStrategy.ALL)
+ public SignService signService() {
+ return new ComposableSignService(new DefaultExtractor(), new
DefaultSignProvider());
+ }
+ ```
+
+
+
+* Declare a new class named `CustomSignService` and implements
`org.apache.shenyu.plugin.plugin.sign.service`.
```java
- public interface SignService {
-
- /**
- * Sign verify pair.
- *
- * @param exchange the exchange
- * @return the pair
- */
- Pair<Boolean, String> signVerify(ServerWebExchange exchange);
- }
+public interface SignService {
+
+ /**
+ * Gets verifyResult.
+ * @param exchange exchange
+ * @param requestBody requestBody
+ * @return result
+ */
+ VerifyResult signatureVerify(ServerWebExchange exchange, String
requestBody);
+
+ /**
+ * Gets verifyResult.
+ * @param exchange exchange
+ * @return result
+ */
+ VerifyResult signatureVerify(ServerWebExchange exchange);
+}
+
```
-* When returning true in Pair, the sign verification passes. If there's false,
the String in Pair will be return to the frontend to show.
+* When returning is `isSuccess()` of VerifyResult, the sign verification
passes. If there's false, the `getReason()` of VerifyResult will be return to
the frontend to show.
* Register defined class as a Spring Bean.
```java
@@ -37,36 +56,3 @@ description: specify sign plugins for examination
}
```
-# Others
-
-> If you only want to modify the signature algorithm, refer to the following.
-
-- The default implementation of the signature algorithm is
`org.apache.shenyu.common.utils.SignUtils#generateSign`.
-- Declare a new class named `CustomSignProvider` and implements
`org.apache.shenyu.plugin.sign.api.SignProvider`.
-
-```java
-/**
- * The Sign plugin sign provider.
- */
-public interface SignProvider {
-
- /**
- * acquired sign.
- *
- * @param signKey sign key
- * @param params params
- * @return sign
- */
- String generateSign(String signKey, Map<String, String> params);
-}
-```
-
-- Put `CustomSignProvider` to `Spring IoC`
-
-```java
-@Bean
-public SignProvider customSignProvider() {
- return new CustomSignProvider();
-}
-```
-
diff --git a/docs/plugin-center/security/sign-plugin.md
b/docs/plugin-center/security/sign-plugin.md
index 8303768bb80..be80121b7d2 100644
--- a/docs/plugin-center/security/sign-plugin.md
+++ b/docs/plugin-center/security/sign-plugin.md
@@ -54,7 +54,7 @@ description: sign plugin
* In `shenyu-admin`--> BasicConfig --> Plugin --> `sign` set to enable.
-## 2.4 Config Plugin With Authorize
+## 2.4 Config Plugin With Authorize(1.0.0)
### 2.4.1 AK/SK Config
@@ -116,90 +116,81 @@ For the created authentication information, you can click
`PathOperation` at the
| version | 1.0.0 | `1.0.0` is a fixed string value |
Sort the above three field natually according to the key, then splice fields
and fields, finally splice SK. The following is a code example.
-The above three fields are spliced with field values, and then 'SK' is spliced
as the 'extSignKey`. The following is a code example.
#### 2.4.3.1 Generate sign with request header
-Step 1: First, construct a `extSignKey`
+Step 1: First, construct a Map.
```java
- //timestamp is string format of millisecond.
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
- String timestamp = "1571711067186";
- String path = "/api/service/abc";
- String version = "1.0.0";
- String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "506EEB535CF740D7A755CB4B9F4A1536");
+ Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
+ //timestamp is string format of millisecond.
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
+ map.put("timestamp","1571711067186"); // Value should be string format of
milliseconds
+ map.put("path", "/api/service/abc");
+ map.put("version", "1.0.0");
```
-* The returned extSignKey value should
be:`timestamp1571711067186path/api/service/abcversion1.0.0506EEB535CF740D7A755CB4B9F4A1536`
-
-Step 2: Md5 encryption and then capitalization.
+Step 2: Sort the `Keys` naturally, then splice the key and values, and finally
splice the `SK` assigned to you.
```java
-DigestUtils.md5DigestAsHex(extSignKey.getBytes()).toUpperCase()
+List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, params.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("506EEB535CF740D7A755CB4B9F4A1536");
```
-* The final returned value is: `F6A9EE877F1C017AF60D8F1200517AA5`.
-
-#### 2.4.3.2 Generate sign with request header and request body
+* The returned sign value should
be:`path/api/service/abctimestamp1571711067186version1.0.0506EEB535CF740D7A755CB4B9F4A1536`
-Step 1: First, construct a `extSignKey`
+Step 3: Md5 encryption and then capitalization.
```java
-
- //timestamp is string format of millisecond.
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
- String timestamp = "1571711067186";
- String path = "/api/service/abc";
- String version = "1.0.0";
- String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "506EEB535CF740D7A755CB4B9F4A1536");
+DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
```
-* The returned extSignKey value should
be:`timestamp1571711067186path/api/service/abcversion1.0.0506EEB535CF740D7A755CB4B9F4A1536`
+* The final returned value is: `A021BF82BE342668B78CD9ADE593D683`.
-Step 2: Construct a 'Map' named 'jsonMap'. And the 'jsonMap' must store the
information of each node of the request body.
-
-```java
- //Skip this step if there is no request body
- Map<String, String> jsonMap = Maps.newHashMapWithExpectedSize(2);
- // if your request body is:{"id":123,"name":"order"}
- jsonMap.put("id", "123");
- jsonMap.put("name", "order");
-```
+#### 2.4.3.2 Generate sign with request header and request body
-Step 3: Construct a 'Map' named 'queryMap'. And the 'queryMap' must store the
information of each node of the uri request parameter.
+Step 1: First, construct a Map, and the map must save every request body
parameters
```java
- //No url request parameter Skip this step
- Map<String, String> queryMap = Maps.newHashMapWithExpectedSize(2);
- // if your request uri is:/api/service/abc?code=10&desc="desc"
- queryMap.put("code", "10");
- queryMap.put("desc", "desc");
+
+ Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
+ //timestamp is string format of millisecond.
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
+ map.put("timestamp","1660659201000"); // Value should be string format of
milliseconds
+ map.put("path", "/http/order/save");
+ map.put("version", "1.0.0");
+ // if your request body is:{"id":123,"name":"order"}
+ map.put("id", "1");
+ map.put("name", "order")
```
-Step 4: `JsonMap 'and' queryMap 'respectively perform the natural sorting of'
Key ', then' Key 'and' Value 'values are spliced to obtain' jsonSign 'and'
querySign ', and finally' jsonSign ',' querySign 'and' extSignKey 'are spliced
to' sign '.
+Step 2: Sort the `Keys` naturally, then splice the key and values, and finally
splice the `SK` assigned to you.
```java
- Map<String, String> empityMap = new HashMap();
- String jsonSign =
Optional.ofNullable(jsonMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, jsonMap.get(key)))
- .collect(Collectors.joining()).trim();
- String querySign =
Optional.ofNullable(queryMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, queryMap.get(key)))
- .collect(Collectors.joining()).trim();
- String sign = String.join("", jsonSign, querySign, signKey);
+List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, params.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("2D47C325AE5B4A4C926C23FD4395C719");
```
-* The returned sign value should
be:`id123nameordercode10descdesctimestamp1571711067186path/api/service/abcversion1.0.0506EEB535CF740D7A755CB4B9F4A1536`
+* The returned sign value should
be:`id123nameorderpath/http/order/savetimestamp1660659201000version1.0.02D47C325AE5B4A4C926C23FD4395C719`
-Step 5: Md5 encryption and then capitalization.
+Step 3: Md5 encryption and then capitalization.
```java
DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
```
-* The final returned value is: `AC8EB7C4E0DAC57C4FCF8A9C58A3E445`.
+* The final returned value is: `35FE61C21F73E9AAFC46954C14F299D7`.
### 2.4.4 Request GateWay
@@ -213,7 +204,7 @@ DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
| -------- | -----: | :----: |
| timestamp | `1571711067186` | Timestamp when signing |
| appKey | `1TEST123456781` | The AK value assigned to you |
-| sign | `AC8EB7C4E0DAC57C4FCF8A9C58A3E445` | The signature obtained
above |
+| sign | `A90E66763793BDBC817CF3B52AAAC041` | The signature obtained
above |
| version | `1.0.0` | `1.0.0` is a fixed value. |
* The signature plugin will filter requests before `5` minutes by default
@@ -247,95 +238,259 @@ DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
* close(signRequestBody): generate signature with request header.
* open(signRequestBody): generate signature with request header and request
body.
-## 2.5 Examples
+## 2.5 Config Plugin With Authorize(2.0.0)
+
+This authentication algorithm is the version 2.0.0 algorithm, which is same as
version1's except **Authentication Guide** and **Request GateWay.**
+
+### 2.5.1 Authentication Guide
+
+Authentication algorithm of Version 2.0.0 generates a Token based on the
signature algorithm, and puts the Token value into the request header
Authorization parameter when sending a request. To distinguish it from version
1.0.0, the version parameter of the request header is left, which is 2.0.0.
+
+#### 2.5.1.1 prepare
+
+* Step 1: `AK/SK` is assigned by the gateway. For example, the `AK` assigned
to you is: `1TEST123456781` SK is: ` 506eeb535cf740d7a755cb49f4a1536'
+
+* Step 2: Decide the gateway path you want to access, such as
`/api/service/abc`
+
+#### 2.5.1.2 Generate Token
+
++ build parameter
+
+ build the `parameters` that is json string
+
+ ```json
+ {
+ "alg":"MD5",
+ "appKey":"506EEB535CF740D7A755CB4B9F4A1536",
+ "timestamp":"1571711067186"
+ }
+ ```
+
+ **alg**: signature algorithm(result is uppercase HEX string)
+
+ - MD5: MD5-HASH(data+key)
+ - HMD5:HMAC-MD5
+ - HS256:HMAC-SHA-256
+ - HS512:HMAC-SHA-512
+
+ **appKey**:appKey
+
+ **timestamp**: timestamp of the length is 13
+
++ Calculate signature value
+
+ ```tex
+ signature = sign(
+ base64Encoding(parameters) + Relative URL + Body*,
+ secret
+ );
+ * indicate Optional , it depends on handler config
+ Relative URL = path [ "?" query ] eg: /apache/shenyu/pulls?name=jack
+ ```
+
+ > note :`Relative URL` is not include fragment
+
++ Calculate Token
+
+ > token = base64Encoding(parameters) + '.' + base64Encoding(signature)
+
+ Put the Token into the request header `Authorization` parameter.
+
+### 2.5.2 Request GateWay
+
+| Field | 值 | 描述 |
+| :------------ | :------ | :---------- |
+| Authorization | Token | Token |
+| version | `2.0.0` | Fixed value |
+
+
-### 2.5.1 Verify api with sign plugin
+## 2.6 Examples
-#### 2.5.1.1 Plugin Config
+### 2.6.1 Verify api with sign plugin(1.0.0)
+
+#### 2.6.1.1 Plugin Config

-#### 2.5.1.2 Selector Config
+#### 2.6.1.2 Selector Config

-#### 2.5.1.3 Rule Config
+#### 2.6.1.3 Rule Config

-#### 2.5.1.5 Add AppKey/SecretKey
+#### 2.6.1.5 Add AppKey/SecretKey

-#### 2.5.1.6 Request Service and check result
+#### 2.6.1.6 Request Service and check result
* build request params with `Authentication Guide`,
```java
public class Test1 {
public static void main(String[] args) {
- //timestamp is string format of millisecond
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
- String timestamp = "1660658725000";
- String path = "/http/order/save";
- String version = "1.0.0";
- String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "2D47C325AE5B4A4C926C23FD4395C719");
-
- System.out.println(extSignKey);
-
-
System.out.println(DigestUtils.md5DigestAsHex(extSignKey.getBytes()).toUpperCase());
+ Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
+ //timestamp为毫秒数的字符串形式
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
+ map.put("timestamp","1660658725000"); //值应该为毫秒数的字符串形式
+ map.put("path", "/http/order/save");
+ map.put("version", "1.0.0");
+ map.put("id", "123");
+ map.put("name", "order");
+ // map.put("body", "{\"id\":123,\"name\":\"order\"}");
+
+ List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+ final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, map.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("2D47C325AE5B4A4C926C23FD4395C719");
+ System.out.println(sign);
+
+
System.out.println(DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase());
}
}
```
-* signature without body:
`timestamp1660658725000path/http/order/saveversion1.0.02D47C325AE5B4A4C926C23FD4395C719`
-* sign without body result is: `A2D81371D99DD4ECB0D5EC6298E3C2EB`
+* signature without body:
`path/http/order/savetimestamp1571711067186version1.0.02D47C325AE5B4A4C926C23FD4395C719`
+* sign without body result is: `9696D3E549A6AEBE763CCC2C7952DDC1`

```java
public class Test2 {
public static void main(String[] args) {
- //timestamp is string format of millisecond
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
- String timestamp = "1660659201000";
- String path = "/http/order/save";
- String version = "1.0.0";
- String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "2D47C325AE5B4A4C926C23FD4395C719");
-
- Map<String, String> jsonMap = Maps.newHashMapWithExpectedSize(2);
- // if your request body is:{"id":123,"name":"order"}
- jsonMap.put("id", "123");
- jsonMap.put("name", "order");
-
- Map<String, String> queryMap = null;
- /* if you have uri params
- Map<String, String> queryMap = Maps.newHashMapWithExpectedSize(2);
- // if your request uri is:/api/service/abc?code=10&desc="desc"
- queryMap.put("code", "10");
- queryMap.put("desc", "desc");
- */
- Map<String, String> empityMap = new HashMap();
- String jsonSign =
Optional.ofNullable(jsonMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, jsonMap.get(key)))
- .collect(Collectors.joining()).trim();
-
- String querySign =
Optional.ofNullable(queryMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, queryMap.get(key)))
- .collect(Collectors.joining()).trim();
- String sign = String.join("", jsonSign, querySign, signKey);
-
+ Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
+ //timestamp为毫秒数的字符串形式
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
+ map.put("timestamp","1660659201000"); //值应该为毫秒数的字符串形式
+ map.put("path", "/http/order/save");
+ map.put("version", "1.0.0");
+
+ List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+ final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, map.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("2D47C325AE5B4A4C926C23FD4395C719");
System.out.println(sign);
+
System.out.println(DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase());
}
}
```
-*signature with
body:`id123nameordertimestamp1660659201000path/http/order/saveversion1.0.02D47C325AE5B4A4C926C23FD4395C719`
-*sign with body result is:`BF485842D2C08A3378308BA9992A309F`
+*signature with
body:`id123nameorderpath/http/order/savetimestamp1660659201000version1.0.02D47C325AE5B4A4C926C23FD4395C719`
+*sign with body result is:`35FE61C21F73E9AAFC46954C14F299D7`

+### 2.6.2 Verify api with sign plugin(2.0.0)
+
+All the configuration parts are the same, so let's look directly at the
parameter part of the calculation request header and the part of sending
request.
+
+### 2.6.1.1 Request Service and check result
+
+- implements the algorithm
+
+ Suppose we use a signature algorithm named MD5. According to the previous
description, the signature value is to concatenate the data and key, and then
hash.
+
+ ```java
+ private static String sign(final String signKey, final String
base64Parameters, final URI uri, final String body) {
+
+ String data = base64Parameters
+ + getRelativeURL(uri)
+ + Optional.ofNullable(body).orElse("");
+
+ return DigestUtils.md5Hex(data+signKey).toUpperCase();
+ }
+
+ private static String getRelativeURL(final URI uri) {
+ if (Objects.isNull(uri.getQuery())) {
+ return uri.getPath();
+ }
+ return uri.getPath() + "?" + uri.getQuery();
+ }
+ ```
+
+- verify without the request body
+
+ ```java
+ public static void main(String[] args) {
+
+ String signKey = "2D47C325AE5B4A4C926C23FD4395C719";
+
+ URI uri = URI.create("/http/order/save");
+
+ String parameters = JsonUtils.toJson(ImmutableMap.of(
+ "alg","MD5",
+ "appKey","BD7980F5688A4DE6BCF1B5327FE07F5C",
+ "timestamp","1673708353996"));
+
+ String base64Parameters = Base64.getEncoder()
+ .encodeToString(parameters.getBytes(StandardCharsets.UTF_8));
+
+ String signature = sign(signKey,base64Parameters,uri,null);
+
+ String Token = base64Parameters+"."+signature;
+
+ System.out.println(Token);
+
+ }
+ ```
+
+ Token:
+
+ ```tex
+
eyJhbGciOiJNRDUiLCJhcHBLZXkiOiJCRDc5ODBGNTY4OEE0REU2QkNGMUI1MzI3RkUwN0Y1QyIsInRpbWVzdGFtcCI6IjE2NzM3MDgzNTM5OTYifQ==.33ED53DF79CA5B53C0BF2448B670AF35
+ ```
+
+ 发送请求:
+
+ 
+
+- verify with the request body
+
+```java
+ public static void main(String[] args) {
+ String signKey = "2D47C325AE5B4A4C926C23FD4395C719";
+
+ URI uri = URI.create("/http/order/save");
+
+ String parameters = JsonUtils.toJson(ImmutableMap.of(
+ "alg","MD5",
+ "appKey","BD7980F5688A4DE6BCF1B5327FE07F5C",
+ "timestamp","1673708905488"));
+
+ String base64Parameters = Base64.getEncoder()
+ .encodeToString(parameters.getBytes(StandardCharsets.UTF_8));
+
+ String requestBody = "{\"id\":123,\"name\":\"order\"}";
+
+ String signature = sign(signKey,base64Parameters,uri,requestBody);
+
+ String Token = base64Parameters+"."+signature;
+
+ System.out.println(Token);
+
+ }
+```
+
+Token:
+
+```tex
+eyJhbGciOiJNRDUiLCJhcHBLZXkiOiJCRDc5ODBGNTY4OEE0REU2QkNGMUI1MzI3RkUwN0Y1QyIsInRpbWVzdGFtcCI6IjE2NzM3MDg5MDU0ODgifQ==.FBCEB6D816644A98378635050AB85EF1
+```
+
+
+
+
+
# 3. How to disable plugin
* In `shenyu-admin`--> BasicConfig --> Plugin --> `sign` set to disabled.
@@ -343,3 +498,4 @@ public class Test2 {
# 4. Extension
* Please refer to: [dev-sign](../../developer/custom-sign-algorithm).
+
diff --git
a/i18n/zh/docusaurus-plugin-content-docs/current/developer/custom-sign-algorithm.md
b/i18n/zh/docusaurus-plugin-content-docs/current/developer/custom-sign-algorithm.md
index 060773d3136..6ca0966a633 100644
---
a/i18n/zh/docusaurus-plugin-content-docs/current/developer/custom-sign-algorithm.md
+++
b/i18n/zh/docusaurus-plugin-content-docs/current/developer/custom-sign-algorithm.md
@@ -10,63 +10,48 @@ description: 自定义sign插件检验
## 扩展
-* 默认的实现为 `org.apache.shenyu.plugin.sign.service.DefaultSignService`。
+* 默认的实现为 `org.apache.shenyu.plugin.sign.service.ComposableSignService`。
-* 新增一个类 `CustomSignService` 实现
`org.apache.shenyu.plugin.sign.api.SignService`。
-
-```java
- public interface SignService {
-
- /**
- * Sign verify pair.
- *
- * @param exchange the exchange
- * @return the pair
- */
- Pair<Boolean, String> signVerify(ServerWebExchange exchange);
- }
+ ```java
+ @Bean
+ @ConditionalOnMissingBean(value = SignService.class, search =
SearchStrategy.ALL)
+ public SignService signService() {
+ return new ComposableSignService(new DefaultExtractor(), new
DefaultSignProvider());
+ }
+ ```
-```
-
-* `Pair`中返回`true`,表示验证通过,为`false`的时候,会把`String`中的信息输出到前端。
+
-* 把新增的实现类注册成为`Spring`的`bean`,如下
+* 新增一个类 `CustomSignService` 实现
`org.apache.shenyu.plugin.sign.api.SignService`。
```java
-@Bean
-public SignService customSignService() {
- return new CustomSignService();
-}
-```
+public interface SignService {
-## 其他扩展
-
-> 当你只希望修改签名算法时可以参考如下。
-
-* 签名算法,默认使用的 `org.apache.shenyu.common.utils.SignUtils#generateSign`,还可以新增一个类
`CustomSignProvider` 实现 `org.apache.shenyu.plugin.sign.api.SignProvider`.
-
-```java
-/**
- * The Sign plugin sign provider.
- */
-public interface SignProvider {
+ /**
+ * Gets verifyResult.
+ * @param exchange exchange
+ * @param requestBody requestBody
+ * @return result
+ */
+ VerifyResult signatureVerify(ServerWebExchange exchange, String
requestBody);
/**
- * acquired sign.
- *
- * @param signKey sign key
- * @param params params
- * @return sign
+ * Gets verifyResult.
+ * @param exchange exchange
+ * @return result
*/
- String generateSign(String signKey, Map<String, String> params);
+ VerifyResult signatureVerify(ServerWebExchange exchange);
}
+
```
-* 把新增的实现类 `CustomSignProvider` 注入到`Spring IoC`即可,如下
+*
`VerifyResult`中`isSuccess()`返回`true`,表示验证通过,为`false`的时候,会把`getReason()`中的信息输出到前端。
+
+* 把新增的实现类注册成为`Spring`的`bean`,如下
```java
@Bean
-public SignProvider customSignProvider() {
- return new CustomSignProvider();
+public SignService customSignService() {
+ return new CustomSignService();
}
```
diff --git
a/i18n/zh/docusaurus-plugin-content-docs/current/plugin-center/security/sign-plugin.md
b/i18n/zh/docusaurus-plugin-content-docs/current/plugin-center/security/sign-plugin.md
index 846af8eb818..09b187f4036 100644
---
a/i18n/zh/docusaurus-plugin-content-docs/current/plugin-center/security/sign-plugin.md
+++
b/i18n/zh/docusaurus-plugin-content-docs/current/plugin-center/security/sign-plugin.md
@@ -54,7 +54,7 @@ description: sign插件
- 在 `shenyu-admin` 基础配置 --> 插件管理 --> `sign` ,设置为开启。
-## 2.4 插件的鉴权配置
+## 2.4 插件的鉴权配置(1.0.0)
### 2.4.1 AK/SK配置
@@ -111,82 +111,75 @@ description: sign插件
| -------- | -----: | :----: |
| timestamp | 当前时间戳(String类型) | 当前时间的毫秒数(网关会过滤掉5分钟之前的请求) |
| path | /api/service/abc | 就是你需要访问的接口路径(根据你访问网关接口自己变更) |
-| version | 1.0.0 | 目前定为1.0.0 写死,String类型 |
+| version | 1.0.0 | 当前鉴权算法为1.0.0 |
-对上述3个字段进行字段与字段值拼接最后再拼接上 `SK`作为`extSignKey` ,代码示例。
+对上述3个字段进行 `key` 的自然排序,然后进行字段与字段值拼接最后再拼接上 `SK` ,代码示例。
-#### 2.4.3.1 无请求体且无uri请求参数的签名参数验证
+#### 2.4.3.1 无请求体的签名参数验证
-第一步:首先构造一个 `extSignKey` 。
+第一步:首先构造一个 `Map` 。
```java
+Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
//timestamp为毫秒数的字符串形式
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
-String timestamp = "1571711067186"; //值应该为毫秒数的字符串形式
-String path = "/api/service/abc";
-String version = "1.0.0";
-String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "506EEB535CF740D7A755CB4B9F4A1536");
+map.put("timestamp","1571711067186"); //值应该为毫秒数的字符串形式
+map.put("path", "/api/service/abc");
+map.put("version", "1.0.0");
```
-* 你得到的 `extSignKey`
值应该为:`timestamp1571711067186path/api/service/abcversion1.0.0506EEB535CF740D7A755CB4B9F4A1536`
-
-第二步:进行 `MD5` 加密后转成大写。
+第二步:进行 `Key` 的自然排序,然后 `Key`,`Value`值拼接最后再拼接分配给你的 `SK`。
```java
-DigestUtils.md5DigestAsHex(extSignKey.getBytes()).toUpperCase()
+List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, params.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("506EEB535CF740D7A755CB4B9F4A1536");
```
-* 最后得到的值为:`F6A9EE877F1C017AF60D8F1200517AA5`
-
-#### 2.4.3.2 有请求体或有`uri`请求参数,请求头的签名参数验证
+* 你得到的 `sign`
值应该为:`path/api/service/abctimestamp1571711067186version1.0.0506EEB535CF740D7A755CB4B9F4A1536`
-第一步:首先构造一个 `extSignKey` 。
+第三步:进行 `MD5` 加密后转成大写。
```java
-//timestamp为毫秒数的字符串形式
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
-String timestamp = "1571711067186"; //值应该为毫秒数的字符串形式
-String path = "/api/service/abc";
-String version = "1.0.0";
-String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "506EEB535CF740D7A755CB4B9F4A1536");
+DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
```
-* 你得到的 `extSignKey`
值应该为:`timestamp1571711067186path/api/service/abcversion1.0.0506EEB535CF740D7A755CB4B9F4A1536`
+* 最后得到的值为:`A021BF82BE342668B78CD9ADE593D683`
-第二步: 构造一个 `Map` 名为 `jsonMap` 。并且该`jsonMap`必须存储请求体的每个节点信息
+#### 2.4.3.2 有请求体,请求头的签名参数验证
-```java
- //无请求体跳过此步
- Map<String, String> jsonMap = Maps.newHashMapWithExpectedSize(2);
- // if your request body is:{"id":123,"name":"order"}
- jsonMap.put("id", "123");
- jsonMap.put("name", "order");
-```
-
-第三步: 构造一个 `Map` 名为 `queryMap` 。并且该`queryMap`必须存储uri请求参数的每个节点信息
+第一步: 首先构造一个 `Map` 。并且该`map`必须存储请求体的每个节点信息
```java
- //无url请求参数跳过此步
- Map<String, String> queryMap = Maps.newHashMapWithExpectedSize(2);
- // if your request uri is:/api/service/abc?code=10&desc="desc"
- queryMap.put("code", "10");
- queryMap.put("desc", "desc");
+
+ Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
+ //timestamp is string format of millisecond.
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
+ map.put("timestamp","1660659201000"); // Value should be string format of
milliseconds
+ map.put("path", "/http/order/save");
+ map.put("version", "1.0.0");
+ // if your request body is:{"id":123,"name":"order"}
+ map.put("id", "1");
+ map.put("name", "order")
```
-第四步: `jsonMap` 和 `queryMap` 分别进行 `Key` 的自然排序,然后 `Key`,`Value`值拼接得到`jsonSign` 和
`querySign`,最后拼接`jsonSign` 、 `querySign` 、`extSignKey` 为 `sign` 。
+第二步:进行 `Key` 的自然排序,然后 `Key`,`Value`值拼接最后再拼接分配给你的 `SK`。
```java
- Map<String, String> empityMap = new HashMap();
- String jsonSign =
Optional.ofNullable(jsonMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, jsonMap.get(key)))
- .collect(Collectors.joining()).trim();
- String querySign =
Optional.ofNullable(queryMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, queryMap.get(key)))
- .collect(Collectors.joining()).trim();
- String sign = String.join("", jsonSign, querySign, signKey);
+List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, params.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("2D47C325AE5B4A4C926C23FD4395C719");
```
-* 你得到的 `sign`
值应该为:`id123nameordercode10descdesctimestamp1571711067186path/api/service/abcversion1.0.0506EEB535CF740D7A755CB4B9F4A1536`
+* 你得到的 `sign`
值应该为:`id123nameorderpath/http/order/savetimestamp1660659201000version1.0.02D47C325AE5B4A4C926C23FD4395C719`
第三步:进行 `MD5` 加密后转成大写。
@@ -194,7 +187,7 @@ String extSignKey = String.join("", "timestamp", timestamp,
"path", path, "versi
DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
```
-* 最后得到的值为: `AC8EB7C4E0DAC57C4FCF8A9C58A3E445`.
+* 最后得到的值为: `35FE61C21F73E9AAFC46954C14F299D7`.
### 2.4.4 请求网关
@@ -208,7 +201,7 @@ DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
| -------- | -----: | :----: |
| timestamp | `1571711067186` | 上述你进行签名的时候使用的时间值 |
| appKey | `1TEST123456781` | 分配给你的AK值 |
-| sign | `AC8EB7C4E0DAC57C4FCF8A9C58A3E445` | 上述得到的签名值 |
+| sign | `A90E66763793BDBC817CF3B52AAAC041` | 上述得到的签名值 |
| version | `1.0.0` | 写死,就为这个值 |
* 签名插件会默认过滤 `5` 分钟之前的请求
@@ -242,95 +235,287 @@ DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
* close(signRequestBody): 仅使用请求头生成签名
* open(signRequestBody): 使用请求头、请求体共同生成签名
-## 2.5 示例
+## 2.5 插件的鉴权配置(2.0.0)
+
+此鉴权算法是2.0.0版本,和版本1.0.0只有**鉴权使用指南**和**请求网关**有所不同,其余皆相同。
+
+### 2.5.1 鉴权使用指南
+
+
版本2.0.0鉴权算法,主要是根据算法生成一个`Token`,发送请求的时候,请求头参数`Authorization`放入这个`Token`值。为了与版本1.0.0作出区分,保留了请求头的version参数,此时它的值应为`2.0.0`。
+
+#### 2.5.1.1 准备工作
+
+前两步骤与此前1.0.0相同:
+
+* 第一步:AK/SK由网关来进行分配,比如分配给你的AK为: `1TEST123456781`
SK为:`506EEB535CF740D7A755CB4B9F4A1536`
+* 第二步:确定好你要访问的网关路径 比如 `/api/service/abc`
+
+#### 2.5.1.2 Token生成
+
+* 构造计算参数
+
+ 构造json参数parameters:
+
+ ```json
+ {
+ "alg":"MD5",
+ "appKey":"506EEB535CF740D7A755CB4B9F4A1536",
+ "timestamp":"1571711067186"
+ }
+ ```
+
+ **alg**: 签名算法(结果统一为大写的HEX字符串)
+
+ + MD5: MD5-HASH(data+key)
+ + HMD5:HMAC-MD5
+ + HS256:HMAC-SHA-256
+ + HS512:HMAC-SHA-512
+
+ **appKey**:appKey,用于查询匹配密钥
+
+ **timestamp**:时间戳,长度13
+
+
+
+* 签名值计算
+
+ 生成签名值`signature`,算法使用的是alg参数中的签名算法
+
+ ```tex
+ signature = sign(
+ base64Encoding(parameters) + Relative URL + Body*,
+ secret
+ );
+ * indicate Optional , it depends on config
+ Relative URL = path [ "?" query ] eg: /apache/shenyu/pulls?name=小明
+ ```
+
+ **sign**: `parameters`参数中对应的签名算法
+
+ **Relative URL:**相对URL,Path加上query的部分,(不包含fragment,服务端收不到)。
+
+ **Body:**body为可选项,依赖Handler配置
+
+ **secret:**`parameters`中appkey所对应的密钥
+
+* 生成Token
+
+ > token = base64Encoding(parameters) + '.' + base64Encoding(signature)
+
+ 把Token放入到请求头`Authorization`参数即可。
+
+详细计算示例请看示例章节。
+
+### 2.5.2请求网关
-### 2.5.1 使用sign插件进行签名验证
+| 字段 | 值 | 描述 |
+| ------------- | ------- | ------------------------- |
+| Authorization | Token | 上述算法计算得到的Token值 |
+| version | `2.0.0` | 写死,就为这个值 |
-#### 2.5.1.1 插件配置
+
+
+## 2.6 示例
+
+### 2.6.1 使用sign插件进行签名验证(1.0.0)
+
+#### 2.6.1.1 插件配置

-#### 2.5.1.2 选择器配置
+#### 2.6.1.2 选择器配置

-#### 2.5.1.3 规则配置
+#### 2.6.1.3 规则配置

-#### 2.5.1.5 添加AppKey/SecretKey
+#### 2.6.1.5 添加AppKey/SecretKey

-#### 2.5.1.6 Request Service and check result
+#### 2.6.1.6 Request Service and check result
* 构造请求参数,请查看`Authentication Guide`目录,
```java
public class Test1 {
public static void main(String[] args) {
+ Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
//timestamp为毫秒数的字符串形式
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
- String timestamp = "1660658725000"; //值应该为毫秒数的字符串形式
- String path = "/http/order/save";
- String version = "1.0.0";
- String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "2D47C325AE5B4A4C926C23FD4395C719");
-
- System.out.println(extSignKey);
-
-
System.out.println(DigestUtils.md5DigestAsHex(extSignKey.getBytes()).toUpperCase());
+ map.put("timestamp","1660658725000"); //值应该为毫秒数的字符串形式
+ map.put("path", "/http/order/save");
+ map.put("version", "1.0.0");
+
+ List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+ final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, map.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("2D47C325AE5B4A4C926C23FD4395C719");
+ System.out.println(sign);
+
+
System.out.println(DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase());
}
}
```
-* 无请求体签名:
`timestamp1660658725000path/http/order/saveversion1.0.02D47C325AE5B4A4C926C23FD4395C719`
-* 无请求体签名结果: `A2D81371D99DD4ECB0D5EC6298E3C2EB`
+* 无请求体签名:
`path/http/order/savetimestamp1571711067186version1.0.02D47C325AE5B4A4C926C23FD4395C719`
+* 无请求体签名结果: `9696D3E549A6AEBE763CCC2C7952DDC1`

```java
public class Test2 {
public static void main(String[] args) {
+ Map<String, String> map = Maps.newHashMapWithExpectedSize(3);
//timestamp为毫秒数的字符串形式
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli())
- String timestamp = "1660659201000"; //值应该为毫秒数的字符串形式
- String path = "/http/order/save";
- String version = "1.0.0";
- String extSignKey = String.join("", "timestamp", timestamp, "path", path,
"version", version, "2D47C325AE5B4A4C926C23FD4395C719");
-
- Map<String, String> jsonMap = Maps.newHashMapWithExpectedSize(2);
- // if your request body is:{"id":123,"name":"order"}
- jsonMap.put("id", "123");
- jsonMap.put("name", "order");
-
- Map<String, String> queryMap =null;
- /* if you have uri params
- Map<String, String> queryMap = Maps.newHashMapWithExpectedSize(2);
- // if your request uri is:/api/service/abc?code=10&desc="desc"
- queryMap.put("code", "10");
- queryMap.put("desc", "desc");
- */
- Map<String, String> empityMap = new HashMap();
- String jsonSign =
Optional.ofNullable(jsonMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, jsonMap.get(key)))
- .collect(Collectors.joining()).trim();
-
- String querySign =
Optional.ofNullable(queryMap).orElse(empityMap).keySet().stream()
- .sorted(Comparator.naturalOrder())
- .map(key -> String.join("", key, queryMap.get(key)))
- .collect(Collectors.joining()).trim();
- String sign = String.join("", jsonSign, querySign, signKey);
-
+ map.put("timestamp","1660659201000"); //值应该为毫秒数的字符串形式
+ map.put("path", "/http/order/save");
+ map.put("version", "1.0.0");
+ map.put("id", "123");
+ map.put("name", "order");
+
+ List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[]{}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+ final String sign = storedKeys.stream()
+ .map(key -> String.join("", key, map.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat("2D47C325AE5B4A4C926C23FD4395C719");
System.out.println(sign);
+
System.out.println(DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase());
}
}
```
-*
有请求体签名为:`id123nameordertimestamp1660659201000path/http/order/saveversion1.0.02D47C325AE5B4A4C926C23FD4395C719`
-* 附带请求体签名结果:`BF485842D2C08A3378308BA9992A309F`
+*
有请求体签名为:`id123nameorderpath/http/order/savetimestamp1660659201000version1.0.02D47C325AE5B4A4C926C23FD4395C719`
+* 附带请求体签名结果:`35FE61C21F73E9AAFC46954C14F299D7`

+
+
+### 2.6.2 使用sign插件进行签名验证(2.0.0)
+
+所有配置部分皆相同,我们直接看计算请求头的参数部分和发送请求部分
+
+### 2.6.1.1 Request Service and check result
+
++ 算法实现
+
+ 假定我们使用的签名算法名称为MD5,按照前面的算法就是把data数据和key进行拼接,然后进行hash计算。
+
+ 现在我们按照上面描述进行算法实现。
+
+
+
+ ```java
+ private static String sign(final String signKey, final String
base64Parameters, final URI uri, final String body) {
+
+ String data = base64Parameters
+ + getRelativeURL(uri)
+ + Optional.ofNullable(body).orElse("");
+
+ return DigestUtils.md5Hex(data+signKey).toUpperCase();
+ }
+
+ private static String getRelativeURL(final URI uri) {
+ if (Objects.isNull(uri.getQuery())) {
+ return uri.getPath();
+ }
+ return uri.getPath() + "?" + uri.getQuery();
+ }
+ ```
+
++ 不校验请求体演示
+
+ ```java
+ public static void main(String[] args) {
+
+ String signKey = "2D47C325AE5B4A4C926C23FD4395C719";
+
+ URI uri = URI.create("/http/order/save");
+
+ String parameters = JsonUtils.toJson(ImmutableMap.of(
+ "alg","MD5",
+ "appKey","BD7980F5688A4DE6BCF1B5327FE07F5C",
+ "timestamp","1673708353996"));
+
+ String base64Parameters = Base64.getEncoder()
+ .encodeToString(parameters.getBytes(StandardCharsets.UTF_8));
+
+ String signature = sign(signKey,base64Parameters,uri,null);
+
+ String Token = base64Parameters+"."+signature;
+
+ System.out.println(Token);
+
+ }
+ ```
+
+ Token的计算结果为
+
+ ```tex
+
eyJhbGciOiJNRDUiLCJhcHBLZXkiOiJCRDc5ODBGNTY4OEE0REU2QkNGMUI1MzI3RkUwN0Y1QyIsInRpbWVzdGFtcCI6IjE2NzM3MDgzNTM5OTYifQ==.33ED53DF79CA5B53C0BF2448B670AF35
+ ```
+
+ 发送请求:
+
+ ![image-20230114230500887]/img/shenyu/plugin/sign/version2_sign_request.png)
+
+
+
+
+
++ 校验请求体演示
+
+ 计算Token
+
+ ```java
+ public static void main(String[] args) {
+ String signKey = "2D47C325AE5B4A4C926C23FD4395C719";
+
+ URI uri = URI.create("/http/order/save");
+
+ String parameters = JsonUtils.toJson(ImmutableMap.of(
+ "alg","MD5",
+ "appKey","BD7980F5688A4DE6BCF1B5327FE07F5C",
+ "timestamp","1673708905488"));
+
+ String base64Parameters = Base64.getEncoder()
+ .encodeToString(parameters.getBytes(StandardCharsets.UTF_8));
+
+ String requestBody = "{\"id\":123,\"name\":\"order\"}";
+
+ String signature = sign(signKey,base64Parameters,uri,requestBody);
+
+ String Token = base64Parameters+"."+signature;
+
+ System.out.println(Token);
+
+ }
+ ```
+
+ Token的计算结果为:
+
+ ```tex
+
eyJhbGciOiJNRDUiLCJhcHBLZXkiOiJCRDc5ODBGNTY4OEE0REU2QkNGMUI1MzI3RkUwN0Y1QyIsInRpbWVzdGFtcCI6IjE2NzM3MDg5MDU0ODgifQ==.FBCEB6D816644A98378635050AB85EF1
+ ```
+
+
+
+ 
+
+

+
+
+
# 3. 如何禁用插件
- 在 `shenyu-admin` 基础配置 --> 插件管理 --> `sign` ,设置为关闭。
diff --git a/static/img/shenyu/plugin/sign/request_body.png
b/static/img/shenyu/plugin/sign/request_body.png
new file mode 100644
index 00000000000..e0f00c9602f
Binary files /dev/null and b/static/img/shenyu/plugin/sign/request_body.png
differ
diff --git a/static/img/shenyu/plugin/sign/version2_sign_request.png
b/static/img/shenyu/plugin/sign/version2_sign_request.png
new file mode 100644
index 00000000000..2c9575db2bc
Binary files /dev/null and
b/static/img/shenyu/plugin/sign/version2_sign_request.png differ
diff --git a/static/img/shenyu/plugin/sign/version2_sign_request_with_body.png
b/static/img/shenyu/plugin/sign/version2_sign_request_with_body.png
new file mode 100644
index 00000000000..7203b7b7bdd
Binary files /dev/null and
b/static/img/shenyu/plugin/sign/version2_sign_request_with_body.png differ