-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from slipper4j/master
feat: 新增admin4j-signature模块
- Loading branch information
Showing
20 changed files
with
794 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
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> | ||
<parent> | ||
<groupId>com.admin4j</groupId> | ||
<artifactId>framework</artifactId> | ||
<version>${revision}</version> | ||
</parent> | ||
|
||
<groupId>com.admin4j.signature</groupId> | ||
<artifactId>admin4j-signature</artifactId> | ||
<version>${admin4j-signature.version}</version> | ||
<packaging>pom</packaging> | ||
|
||
<properties> | ||
<admin4j-signature.version>0.8.0</admin4j-signature.version> | ||
<maven.compiler.source>8</maven.compiler.source> | ||
<maven.compiler.target>8</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
|
||
<modules> | ||
<module>signature-core</module> | ||
<module>signature-spring-boot-starter</module> | ||
</modules> | ||
|
||
</project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
<?xml version="1.0" encoding="UTF-8"?> | ||
<project xmlns="http://maven.apache.org/POM/4.0.0" | ||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
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> | ||
<parent> | ||
<groupId>com.admin4j.signature</groupId> | ||
<artifactId>admin4j-signature</artifactId> | ||
<version>${admin4j-signature.version}</version> | ||
</parent> | ||
|
||
<artifactId>signature-core</artifactId> | ||
|
||
<properties> | ||
<maven.compiler.source>8</maven.compiler.source> | ||
<maven.compiler.target>8</maven.compiler.target> | ||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.admin4j.common</groupId> | ||
<artifactId>admin4j-common</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework</groupId> | ||
<artifactId>spring-webmvc</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>javax.servlet</groupId> | ||
<artifactId>javax.servlet-api</artifactId> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.admin4j.common</groupId> | ||
<artifactId>admin4j-common-spring-web</artifactId> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.springframework.boot</groupId> | ||
<artifactId>spring-boot-starter-data-redis</artifactId> | ||
</dependency> | ||
</dependencies> | ||
|
||
</project> |
205 changes: 205 additions & 0 deletions
205
...ature/signature-core/src/main/java/com/admin4j/framework/signature/AbstractSignature.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
package com.admin4j.framework.signature; | ||
|
||
import com.admin4j.framework.signature.annotation.Signature; | ||
import com.admin4j.framework.signature.properties.SignatureProperties; | ||
import com.alibaba.fastjson2.JSONObject; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.data.redis.core.StringRedisTemplate; | ||
import org.springframework.util.CollectionUtils; | ||
import org.springframework.util.DigestUtils; | ||
|
||
import javax.servlet.http.HttpServletRequest; | ||
import java.io.IOException; | ||
import java.util.Map; | ||
import java.util.SortedMap; | ||
import java.util.TreeMap; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* @author zhougang | ||
* @since 2023/11/10 10:43 | ||
*/ | ||
public abstract class AbstractSignature implements SignatureService { | ||
|
||
private static final Logger log = LoggerFactory.getLogger(AbstractSignature.class); | ||
|
||
private final StringRedisTemplate stringRedisTemplate; | ||
|
||
private final SignatureProperties signatureProperties; | ||
|
||
private static final String SIGNATURE_NONCE_REDIS_KEY = "signature:nonce:"; | ||
|
||
public AbstractSignature(StringRedisTemplate stringRedisTemplate, SignatureProperties signatureProperties) { | ||
this.stringRedisTemplate = stringRedisTemplate; | ||
this.signatureProperties = signatureProperties; | ||
} | ||
|
||
/** | ||
* 判断请求是否签名通过 | ||
* | ||
* @param request HttpServletRequest | ||
* @return 是否通过 | ||
*/ | ||
@Override | ||
public boolean verify(Signature signature, HttpServletRequest request) throws IOException { | ||
// 根据appId获取appSecret | ||
String appId = request.getHeader(signature.appId().filedName()); | ||
String appSecret; | ||
if (StringUtils.isBlank(appId) || | ||
StringUtils.isBlank(appSecret = getAppSecret(request.getHeader(signature.appId().filedName())))) { | ||
return false; | ||
} | ||
// 根据request 中 header值生成SignatureHeaders实体 | ||
if (!verifyHeaders(signature, request)) { | ||
return false; | ||
} | ||
// 获取全部参数(包括URL和Body上的) | ||
SortedMap<String, String> allParams = getAllParams(signature, request); | ||
// 生成服务端签名 | ||
String plainText = paramsSplicing(allParams, appSecret); | ||
// 将digest 转换成UTF-8 的 byte[] 后 使用MD5算法加密,最后将生成的md5字符串 | ||
String serverSign = digestEncoder(plainText); | ||
// 客户端签名 | ||
String clientSign = request.getHeader(signature.sign().filedName()); | ||
if (!StringUtils.equals(clientSign, serverSign)) { | ||
return false; | ||
} | ||
String nonce = allParams.get(signature.nonce().filedName()); | ||
// 将 nonce 记入缓存,防止重复使用(重点二:此处需要将 ttl 设定为允许 timestamp 时间差的值 x 2 ) | ||
stringRedisTemplate.opsForValue().set(SIGNATURE_NONCE_REDIS_KEY + nonce, nonce, signatureProperties.getExpireTime() * 2, TimeUnit.MILLISECONDS); | ||
return true; | ||
} | ||
|
||
/* | ||
private SignatureHeaders getSignatureHeaders(Signature signature, HttpServletRequest request) { | ||
SignatureHeaders signatureHeaders = new SignatureHeaders(); | ||
signatureHeaders.setAppId(request.getHeader(signature.appId().filedName())); | ||
signatureHeaders.setAppId(request.getHeader(signature.timestamp().filedName())); | ||
signatureHeaders.setAppId(request.getHeader(signature.nonce().filedName())); | ||
signatureHeaders.setAppId(request.getHeader(signature.sign().filedName())); | ||
return signatureHeaders; | ||
} | ||
*/ | ||
|
||
/** | ||
* 1.appId是否合法,appId是否有对应的appSecret。 | ||
* 2.请求是否已经超时,默认10分钟。 | ||
* 3.随机串是否合法,是否在指定时间内已经访问过了。 | ||
* 4.sign是否合法。 | ||
*/ | ||
private boolean verifyHeaders(Signature signature, HttpServletRequest request) { | ||
|
||
String timestamp = request.getHeader(signature.timestamp().filedName()); | ||
//Assert.notNull(timestamp, "timestamp cannot be empty"); | ||
if (StringUtils.isBlank(timestamp)) { | ||
return false; | ||
} | ||
|
||
Long expireTime = signatureProperties.getExpireTime(); | ||
//其他合法性校验 | ||
long requestTimestamp = Long.parseLong(timestamp); | ||
// 检查 timestamp 是否超出允许的范围 (重点一:此处需要取绝对值) | ||
long timestampDisparity = Math.abs(System.currentTimeMillis() - requestTimestamp); | ||
//Assert.isTrue(!(timestampDisparity > expireTime), "Request time exceeds the specified limit"); | ||
if (timestampDisparity > expireTime) { | ||
return false; | ||
} | ||
|
||
String nonce = request.getHeader(signature.nonce().filedName()); | ||
//Assert.notNull(nonce, "Random strings cannot be empty"); | ||
if (StringUtils.isBlank(nonce)) { | ||
return false; | ||
} | ||
//Assert.isTrue(!(nonce.length() < 10), "The random string nonce length is at least 10 bits"); | ||
if (nonce.length() < 10) { | ||
return false; | ||
} | ||
String cacheNonce = stringRedisTemplate.opsForValue().get(SIGNATURE_NONCE_REDIS_KEY + nonce); | ||
//Assert.isNull(cacheNonce, "This nonce has already been used and the request is invalid"); | ||
if (StringUtils.isNotBlank(cacheNonce)) { | ||
return false; | ||
} | ||
|
||
String sign = request.getHeader(signature.sign().filedName()); | ||
//Assert.notNull(sign, "sign cannot be empty"); | ||
return StringUtils.isNotBlank(sign); | ||
} | ||
|
||
/** | ||
* 获取全部参数(包括URL和Body上的) | ||
* | ||
* @param request request | ||
* @return | ||
*/ | ||
protected SortedMap<String, String> getAllParams(Signature signature, HttpServletRequest request) throws IOException { | ||
|
||
SortedMap<String, String> sortedMap = new TreeMap<>(); | ||
|
||
sortedMap.put(signature.appId().filedName(), request.getHeader(signature.appId().filedName())); | ||
sortedMap.put(signature.timestamp().filedName(), request.getHeader(signature.timestamp().filedName())); | ||
sortedMap.put(signature.nonce().filedName(), request.getHeader(signature.nonce().filedName())); | ||
// 有url带动态参数的情况, 所以加上url, 客户端对应也要拼接 | ||
sortedMap.put("url", request.getServletPath()); | ||
|
||
// 获取parameters(对应@RequestParam) | ||
if (!CollectionUtils.isEmpty(request.getParameterMap())) { | ||
Map<String, String[]> requestParams = request.getParameterMap(); | ||
//获取GET请求参数,以键值对形式保存 | ||
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) { | ||
sortedMap.put(entry.getKey(), entry.getValue()[0]); | ||
} | ||
} | ||
|
||
BodyReaderHttpServletRequestWrapper requestWrapper = new BodyReaderHttpServletRequestWrapper(request); | ||
// 分别获取了request input stream中的body信息、parameter信息 | ||
JSONObject data = JSONObject.parseObject(requestWrapper.getBody()); | ||
// 获取POST请求的JSON参数,以键值对形式保存 | ||
for (Map.Entry<String, Object> entry : data.entrySet()) { | ||
sortedMap.put(entry.getKey(), entry.getValue().toString()); | ||
} | ||
|
||
return sortedMap; | ||
} | ||
|
||
/** | ||
* 所有的参数与应用密钥appSecret 进行排序加密后生成签名 | ||
* | ||
* @param sortedMap 根据key升序排序的后所有请求参数 | ||
* @param appSecret 应用id对应的应用密钥 | ||
* @return 生成接口签名 | ||
*/ | ||
protected String paramsSplicing(SortedMap<String, String> sortedMap, String appSecret) { | ||
// 进行key value拼接 | ||
StringBuilder plainText = new StringBuilder(); | ||
for (Map.Entry<String, String> entry : sortedMap.entrySet()) { | ||
plainText.append(entry.getKey()).append(entry.getValue()); | ||
} | ||
|
||
// 结尾拼接应用密钥 appSecret | ||
plainText.append(appSecret); | ||
|
||
// 摘要 | ||
return plainText.toString(); | ||
} | ||
|
||
/** | ||
* 获取appId对应的secret,假数据 | ||
* | ||
* @param appId 应用id | ||
* @return | ||
*/ | ||
protected String getAppSecret(String appId) { | ||
return ""; | ||
} | ||
|
||
/** | ||
* 摘要加密 | ||
* @param plainText | ||
* @return | ||
*/ | ||
protected String digestEncoder(String plainText) throws IOException { | ||
return DigestUtils.md5DigestAsHex(StringUtils.getBytes(plainText, "UTF-8")); | ||
} | ||
} |
68 changes: 68 additions & 0 deletions
68
...re/src/main/java/com/admin4j/framework/signature/BodyReaderHttpServletRequestWrapper.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
package com.admin4j.framework.signature; | ||
|
||
import javax.servlet.ReadListener; | ||
import javax.servlet.ServletInputStream; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletRequestWrapper; | ||
import java.io.*; | ||
|
||
public class BodyReaderHttpServletRequestWrapper extends HttpServletRequestWrapper { | ||
|
||
private final String body; | ||
|
||
public BodyReaderHttpServletRequestWrapper(HttpServletRequest request) throws IOException { | ||
super(request); | ||
StringBuilder stringBuilder = new StringBuilder(); | ||
BufferedReader bufferedReader = null; | ||
try { | ||
InputStream inputStream = request.getInputStream(); | ||
if (inputStream != null) { | ||
bufferedReader = new BufferedReader(new InputStreamReader(inputStream)); | ||
char[] charBuffer = new char[128]; | ||
int bytesRead; | ||
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) { | ||
stringBuilder.append(charBuffer, 0, bytesRead); | ||
} | ||
} | ||
} finally { | ||
if (bufferedReader != null) { | ||
bufferedReader.close(); | ||
} | ||
} | ||
body = stringBuilder.toString(); | ||
} | ||
|
||
@Override | ||
public ServletInputStream getInputStream() { | ||
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes()); | ||
return new ServletInputStream() { | ||
@Override | ||
public boolean isFinished() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public boolean isReady() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public void setReadListener(ReadListener readListener) { | ||
} | ||
|
||
@Override | ||
public int read() { | ||
return byteArrayInputStream.read(); | ||
} | ||
}; | ||
} | ||
|
||
@Override | ||
public BufferedReader getReader() { | ||
return new BufferedReader(new InputStreamReader(this.getInputStream())); | ||
} | ||
|
||
public String getBody() { | ||
return this.body; | ||
} | ||
} |
17 changes: 17 additions & 0 deletions
17
...nature/signature-core/src/main/java/com/admin4j/framework/signature/DefaultSignature.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package com.admin4j.framework.signature; | ||
|
||
import com.admin4j.framework.signature.properties.SignatureProperties; | ||
import org.springframework.data.redis.core.StringRedisTemplate; | ||
|
||
/** | ||
* 签名的默认实现 | ||
* | ||
* @author zhougang | ||
* @since 2023/10/11 13:39 | ||
*/ | ||
public class DefaultSignature extends AbstractSignature { | ||
|
||
public DefaultSignature(StringRedisTemplate stringRedisTemplate, SignatureProperties signatureProperties) { | ||
super(stringRedisTemplate, signatureProperties); | ||
} | ||
} |
Oops, something went wrong.