Core Module: shenyu-plugin-sign
Core Class: org.apache.shenyu.plugin.sign.SignPlugin
sign
dependency in the pom.xml
file of the gateway<!-- apache shenyu sign plugin start--> <dependency> <groupId>org.apache.shenyu</groupId> <artifactId>shenyu-spring-boot-starter-plugin-sign</artifactId> <version>${project.version}</version> </dependency> <!-- apache shenyu sign plugin end-->
shenyu-admin
--> BasicConfig --> Plugin --> sign
set to enable.AK/SK
and use it with the Sign
plugin to achieve precise authority control based on URI level.First, we can add a piece of authentication information in BasicConfig
- Authentication
Then configure this authentication information
appParam
./order/**
.After submit, a piece of authentication information is generated, which contains AppKey
and AppSecret
, which is the AK/SK
in the Sign
plugin.
Please refer to the detailed instructions of the Sign
plugin: Sign Plugin.
For the created authentication information, you can click PathOperation
at the end of a piece of authentication information.
>
or <
in the middle to move the checked data to the corresponding list.AK/SK
authentication technical scheme.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
Step 3: Construct parameters (the following are general parameters)
Field | Value | Description |
---|---|---|
timestamp | current timestamp(String) | The number of milliseconds of the current time(gateway will filter requests the before 5 minutes) |
path | /api/service/abc | The path that you want to request(Modify by yourself according to your configuration of gateway) |
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.
Step 1: First, construct a Map.
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");
Step 2: Sort the Keys
naturally, then splice the key and values, and finally splice the SK
assigned to you.
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");
path/api/service/abctimestamp1571711067186version1.0.0506EEB535CF740D7A755CB4B9F4A1536
Step 3: Md5 encryption and then capitalization.
DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
A021BF82BE342668B78CD9ADE593D683
.Step 1: First, construct a Map, and the map must save every request body parameters
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 2: Sort the Keys
naturally, then splice the key and values, and finally splice the SK
assigned to you.
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");
id123nameorderpath/http/order/savetimestamp1660659201000version1.0.02D47C325AE5B4A4C926C23FD4395C719
Step 3: Md5 encryption and then capitalization.
DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase()
35FE61C21F73E9AAFC46954C14F299D7
.If your visited path is:/api/service/abc
.
Address: http: domain name of gateway /api/service/abc
.
Set header
,header
Parameter:
Field | Value | Description |
---|---|---|
timestamp | 1571711067186 | Timestamp when signing |
appKey | 1TEST123456781 | The AK value assigned to you |
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
If the authentication fails, will return code 401
, message may change.
{ "code": 401, "message": "sign is not pass,Please check you sign algorithm!", "data": null }
Only those matched requests can be authenticated by signature.
Selectors and rules, please refer to: Selector And Rule Config
This authentication algorithm is the version 2.0.0 algorithm, which is same as version1's except Authentication Guide and Request GateWay.
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.
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
build parameter
build the parameters
that is json string
{ "alg":"MD5", "appKey":"506EEB535CF740D7A755CB4B9F4A1536", "timestamp":"1571711067186" }
alg: signature algorithm(result is uppercase HEX string)
appKey:appKey
timestamp: timestamp of the length is 13
Calculate signature value
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.
Field | 值 | 描述 |
---|---|---|
Authorization | Token | Token |
version | 2.0.0 | Fixed value |
Authentication Guide
,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()) 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()); } }
path/http/order/savetimestamp1571711067186version1.0.02D47C325AE5B4A4C926C23FD4395C719
9696D3E549A6AEBE763CCC2C7952DDC1
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()) 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:id123nameorderpath/http/order/savetimestamp1660659201000version1.0.02D47C325AE5B4A4C926C23FD4395C719
*sign with body result is:35FE61C21F73E9AAFC46954C14F299D7
All the configuration parts are the same, so let's look directly at the the calculation part of parameter of request header and the part of sending request.
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.
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
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:
eyJhbGciOiJNRDUiLCJhcHBLZXkiOiJCRDc5ODBGNTY4OEE0REU2QkNGMUI1MzI3RkUwN0Y1QyIsInRpbWVzdGFtcCI6IjE2NzM3MDgzNTM5OTYifQ==.33ED53DF79CA5B53C0BF2448B670AF35
发送请求:
verify with the request body
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:
eyJhbGciOiJNRDUiLCJhcHBLZXkiOiJCRDc5ODBGNTY4OEE0REU2QkNGMUI1MzI3RkUwN0Y1QyIsInRpbWVzdGFtcCI6IjE2NzM3MDg5MDU0ODgifQ==.FBCEB6D816644A98378635050AB85EF1
shenyu-admin
--> BasicConfig --> Plugin --> sign
set to disabled.