目录
前言
实现签名算法和方式有很多,但是部分实现没有把参数放到生成签名的逻辑中,只是校验发起接口调用的身份是否合法,如果没有把参数放到生成签名当中,被抓包之后,就可以篡改参数,当然除了这种把参数放到生成签名的逻辑中,还有其他方式可以实现防篡改,例如:发起接口调用生成了2部分数据,一部分是签名,一部分是参数加密信息,生成签名逻辑中没有放入参数,客户端使用对称加密算法实现参数加密,服务端使用相同的秘钥解密,相比较把参数放到签名当中,对参数使用对称加密,也不利于自己排查问题,2种方式各有所长,自己选择,我今天是通过第一种方式,也就是把参数放到生成签名逻辑中。
代码
过滤器实现校验签名逻辑
package com.lwh.filter.signature;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import com.lwh.loancloud.common.VerifySignContact;
import com.lwh.loancloud.filter.ZuulFilterHelper;
import com.lwh.loancloud.filter.zrs.wrapper.HeaderMapRequestWrapper;
import com.lwh.loancloud.response.CommonCode;
import com.lwh.loancloud.response.ServerResponse;
import com.lwh.loancloud.utils.RequestUtil;
import com.lwh.loancloud.utils.encrypt.RSAInternalUtil;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.http.ServletInputStreamWrapper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.List;
import java.util.StringJoiner;
/**
* @author lwh
* @date 2024/2/27
* @description 发起请求,校验本次请求的签名是否合规,该过滤器适配所有项目
**/
@Component
@Slf4j
public class InternalRequestVerifySignFilter extends ZuulFilter {
// 客户端标识,换行符分隔
@Value("#{'${verify.sign.app.id:}'.split('\\n')}")
private List<String> verifySignAppIdList;
// 客户端版本号,换行符分隔
@Value("#{'${verify.sign.version:}'.split('\\n')}")
private List<String> verifySignVersionList;
// 客户端公钥
@Value("${verify.sign.uri.public.key}")
private String verifySignUriPublicKey;
// 接口请求需要校验签名的开关
@Value("${verify.sign.uri.switch}")
private Boolean verifySignUriSwitch;
// 接口请求需要校验签名的前缀,换行符分隔
@Value("#{'${verify.sign.uri.prefix:}'.split('\\n')}")
private List<String> verifySignUriPrefixList;
// 接口请求不需要校验签名的关键字,换行符分隔
@Value("#{'${verify.sign.exclude.uri:}'.split('\\n')}")
private List<String> verifySignExcludeUriList;
/**
* @author lwh
* @date 2024/2/27
* @description pre:请求在被路由之前执行
* routing:在路由请求时调用
* post:在routing和error过滤器之后调用
* error:处理请求时发生错误调用
**/
@Override
public String filterType() {
return "pre";
}
/**
* @author lwh
* @date 2024/2/27
* @description 过虑器序号,越小越被优先执行
**/
@Override
public int filterOrder() {
return 0;
}
/**
* @author lwh
* @date 2024/2/27
* @description true=执行该过滤器,false=不执行该过滤器
**/
@Override
public boolean shouldFilter() {
// 开关如果关闭,不再校验签名
if (!verifySignUriSwitch) {
return false;
}
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
// 获取本次请求路径地址
String requestURI = request.getRequestURI();
// 判断在开关开启状态下,该请求是否在排除的范围内,如果存在则不再执行该过滤器
if (CollectionUtil.isNotEmpty(verifySignExcludeUriList)) {
boolean exclude = verifySignExcludeUriList.stream().anyMatch(k -> requestURI.contains(k));
if (exclude) {
return false;
}
}
// 判断在开关开启状态下,本次请求是否是命中前缀,如果命中,则执行该该过滤器
if (CollectionUtil.isNotEmpty(verifySignUriPrefixList)) {
boolean appoint = verifySignUriPrefixList.stream().anyMatch(k -> requestURI.startsWith(k));
if (appoint) {
return true;
}
}
return false;
}
/**
* @author lwh
* @date 2024/2/28
* @description 校验签名
**/
@Override
public Object run() {
RequestContext requestContext = RequestContext.getCurrentContext();
HttpServletRequest request = requestContext.getRequest();
String appId = RequestUtil.getHeadParams(request, VerifySignContact.APP_ID);
String version = RequestUtil.getHeadParams(request, VerifySignContact.VERSION);
String timestamp = RequestUtil.getHeadParams(request, VerifySignContact.TIMESTAMP);
String sign = RequestUtil.getHeadParams(request, VerifySignContact.SIGN);
StringJoiner joiner = new StringJoiner("_");
joiner.add(appId).add(version).add(timestamp);
try {
// 4个参数只要有一个为空,就是非法请求
if (StringUtils.isAnyBlank(appId, version, timestamp, sign)) {
ZuulFilterHelper.accessDenied(ServerResponse.createByErrorCodeMessage(CommonCode.CODE_00405.code(), "hp is null"));
return null;
}
if (!verifySignAppIdList.contains(appId.trim())) {
ZuulFilterHelper.accessDenied(ServerResponse.createByErrorCodeMessage(CommonCode.CODE_00405.code(), "Illegal a"));
return null;
}
if (!verifySignVersionList.contains(version.trim())) {
ZuulFilterHelper.accessDenied(ServerResponse.createByErrorCodeMessage(CommonCode.CODE_00405.code(), "Illegal v"));
return null;
}
// 文件上传放过
String contentType = request.getHeader("Content-Type");
if (StringUtils.isNotBlank(contentType) && contentType.contains("multipart/form-data")) {
return null;
}
// 获取请求参数
if (StringUtils.isNotBlank(request.getQueryString())) {
joiner.add(URLDecoder.decode(request.getQueryString().trim(), "UTF-8"));
}
String bodyString = RequestUtil.getBodyString(request);
if (StringUtils.isNotBlank(bodyString)) {
joiner.add(URLDecoder.decode(bodyString.trim(), "UTF-8"));
}
log.warn("签名内容:{},签名:{}", joiner.toString(), sign);
// 验证签名
if (!RSAInternalUtil.verify(verifySignUriPublicKey, joiner.toString(), sign)) {
ZuulFilterHelper.accessDenied(ServerResponse.createErrorByCode(CommonCode.CODE_00405));
return null;
}
if (StrUtil.isNotBlank(bodyString)) {
String decodeBody = URLDecoder.decode(bodyString.trim(), "UTF-8");
HeaderMapRequestWrapper httpServletRequestWrapper = new HeaderMapRequestWrapper(requestContext.getRequest()) {
@Override
public ServletInputStream getInputStream() {
return new ServletInputStreamWrapper(decodeBody.getBytes());
}
@Override
public int getContentLength() {
return decodeBody.getBytes().length;
}
@Override
public long getContentLengthLong() {
return decodeBody.getBytes().length;
}
};
requestContext.setRequest(httpServletRequestWrapper);
}
} catch (Exception e) {
ZuulFilterHelper.accessDenied(ServerResponse.createErrorByCode(CommonCode.CODE_00001));
log.error("内部请求验签流程出现异常,签名内容:{},签名:{}\nError:{}", joiner.toString(), sign, ExceptionUtil.getMessage(e));
}
return null;
}
}
签名工具类
package com.lwh.loancloud.utils.encrypt;
import org.apache.commons.codec.binary.Base64;
import org.springframework.util.Assert;
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
/**
* @author lwh
* @date 2024/2/27
* @description 非对称加密
* <p>
* RSA公钥/私钥/签名工具包
* </p>
* <p>
* 字符串格式的密钥在未在特殊说明情况下都为BASE64编码格式<br/>
* 由于非对称加密速度极其缓慢,一般文件不使用它来加密而是使用对称加密,<br/>
* 非对称加密算法可以用来对对称加密的密钥加密,这样保证密钥的安全也就保证了数据的安全
* <p>
**/
public final class RSAInternalUtil {
/**
* 签名算法
*/
private static final String SIGNATURE_ALGORITHM = "SHA256withRSA";
/**
* 密钥大小
*/
private static final int KEY_SIZE = 1024;
/**
* 不可实例化
*/
private RSAInternalUtil() {
}
/**
* 生成密钥对
*
* @return 密钥对
*/
public static KeyPair generateKeyPair() throws NoSuchAlgorithmException {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
return keyPairGenerator.generateKeyPair();
}
/**
* 公钥加密
*
* @param publicKey 公钥
* @param data 数据
* @return 加密后的数据
*/
private static byte[] encrypt(PublicKey publicKey, byte[] data) {
Assert.notNull(publicKey, "parameter [publicKey] must not be empty");
Assert.notNull(data, "parameter [data] must not be empty");
try {
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
return cipher.doFinal(data);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
/**
* 公钥加密
*
* @param publicKey 公钥
* @param text 字符串
* @return Base64编码字符串
*/
public static String encrypt(PublicKey publicKey, String text) {
Assert.notNull(publicKey, "parameter [publicKey] must not be empty");
Assert.notNull(text, "parameter [text] must not be empty");
byte[] data = encrypt(publicKey, text.getBytes());
return data != null ? Base64.encodeBase64String(data) : null;
}
/**
* 公钥加密
*
* @param publicKey 公钥
* @param data 数据
* @return 加密后的数据
*/
public static byte[] encrypt(String publicKey, byte[] data) throws InvalidKeySpecException, NoSuchAlgorithmException {
Assert.notNull(publicKey, "parameter [publicKey] must not be empty");
Assert.notNull(data, "parameter [data] must not be empty");
PublicKey pubKey = _buildPublicKey(publicKey);
return encrypt(pubKey, data);
}
/**
* 公钥加密
*
* @param publicKey 公钥
* @param text 字符串
* @return Base64编码字符串
*/
public static String encrypt(String publicKey, String text) throws InvalidKeySpecException, NoSuchAlgorithmException {
Assert.notNull(publicKey, "parameter [publicKey] must not be empty");
Assert.notNull(text, "parameter [text] must not be empty");
PublicKey pubKey = _buildPublicKey(publicKey);
return encrypt(pubKey, text);
}
/**
* 私钥解密
*
* @param privateKey 私钥
* @param data 数据
* @return 解密后的数据
*/
private static byte[] decrypt(PrivateKey privateKey, byte[] data) {
Assert.notNull(privateKey, "parameter [privateKey] must not be empty");
Assert.notNull(data, "parameter [data] must not be empty");
try {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
return cipher.doFinal(data);
} catch (Exception e) {
return null;
}
}
/**
* 私钥解密
*
* @param privateKey 私钥
* @param data 数据
* @return 解密后的数据
*/
private static byte[] decrypt(String privateKey, byte[] data) throws InvalidKeySpecException, NoSuchAlgorithmException {
Assert.notNull(privateKey, "parameter [privateKey] must not be empty");
Assert.notNull(data, "parameter [data] must not be empty");
PrivateKey priKey = _buildPrivateKey(privateKey);
return decrypt(priKey, data);
}
/**
* 私钥解密
*
* @param privateKey 私钥
* @param text Base64编码字符串
* @return 解密后的数据
*/
public static String decrypt(String privateKey, String text) throws InvalidKeySpecException, NoSuchAlgorithmException {
Assert.notNull(privateKey, "parameter [privateKey] must not be empty");
Assert.notNull(text, "parameter [text] must not be empty");
byte[] data = decrypt(privateKey, Base64.decodeBase64(text));
return data != null ? new String(data) : null;
}
/**
* 私钥对信息生成数字签名
*
* @param privateKey 私钥
* @param data 已加密数据
* @return 签名
*/
private static String sign(PrivateKey privateKey, byte[] data) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Assert.notNull(privateKey, "parameter [privateKey] must not be empty");
Assert.notNull(data, "parameter [data] must not be empty");
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initSign(privateKey);
signature.update(data);
return Base64.encodeBase64String(signature.sign());
}
/**
* 私钥对信息生成数字签名
*
* @param privateKey 私钥 Base64
* @param text 已加密数据Base64
* @return 签名
*/
public static String sign(String privateKey, String text) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException {
Assert.notNull(privateKey, "parameter [privateKey] must not be empty");
Assert.notNull(text, "parameter [text] must not be empty");
PrivateKey priKey = _buildPrivateKey(privateKey);
return sign(priKey, text.getBytes());
}
/**
* @param publicKey 公钥
* @param data 已加密数据
* @param sign 数字签名
* @return 校验结果
*/
private static boolean verify(PublicKey publicKey, byte[] data, String sign) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
Assert.notNull(publicKey, "parameter [publicKey] must not be empty");
Assert.notNull(data, "parameter [data] must not be empty");
Assert.notNull(sign, "parameter [sign] must not be empty");
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
signature.initVerify(publicKey);
signature.update(data);
return signature.verify(Base64.decodeBase64(sign));
}
/**
* 签名校验
*
* @param publicKey 公钥 Base64
* @param text 已加密数据 Base64
* @param sign 数字签名
* @return 校验结果
*/
public static boolean verify(String publicKey, String text, String sign) throws NoSuchAlgorithmException, InvalidKeySpecException, SignatureException, InvalidKeyException {
Assert.notNull(publicKey, "parameter [publicKey] must not be empty");
Assert.notNull(text, "parameter [text] must not be empty");
Assert.notNull(sign, "parameter [sign] must not be empty");
PublicKey pubKey = _buildPublicKey(publicKey);
return verify(pubKey, text.getBytes(), sign);
}
/**
* 私钥转换
*
* @param privateKey 私钥字符串
* @return PrivateKey
*/
private static PrivateKey _buildPrivateKey(String privateKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
Assert.notNull(privateKey, "parameter [privateKey] must not be empty");
byte[] keyBytes = Base64.decodeBase64(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(pkcs8KeySpec);
}
/**
* 公钥转换
*
* @param publicKey 公钥
* @return 公钥
*/
private static PublicKey _buildPublicKey(String publicKey) throws NoSuchAlgorithmException, InvalidKeySpecException {
byte[] keyBytes = Base64.decodeBase64(publicKey);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(keySpec);
}
}
ZuulFilterHelper
import com.junyu.loancloud.response.ServerResponse;
import com.junyu.loancloud.utils.JsonUtil;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
/**
* @author lwh
* @date 2024/2/28
* @description 响应帮助类
**/
@Slf4j
public class ZuulFilterHelper {
/**
* 拒绝访问
*/
public static void accessDenied(ServerResponse serverResponse) {
log.error("网关拒绝访问:{}", JsonUtil.obj2StringPretty(serverResponse));
RequestContext requestContext = RequestContext.getCurrentContext();
//得到response
HttpServletResponse response = requestContext.getResponse();
//拒绝访问
requestContext.setSendZuulResponse(false);
//设置响应代码
requestContext.setResponseStatusCode(200);
//转成json
String jsonString = JsonUtil.objectToJson(serverResponse);
requestContext.setResponseBody(jsonString);
//转成json,设置contentType
response.setContentType("application/json;charset=utf-8");
}
}
HeaderMapRequestWrapper
可以通过httpServletRequestWrapper.addHeader();添加自定义header,向下游传递
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.util.*;
/**
* @author lwh
* @date 2024/1/16
* @description 自定义对request设置header
**/
public class HeaderMapRequestWrapper extends HttpServletRequestWrapper {
public HeaderMapRequestWrapper(HttpServletRequest request) {
super(request);
}
private Map<String, String> headerMap = new HashMap<>();
/**
* @author lwh
* @date 2024/1/16
* @description 添加header
**/
public void addHeader(String name, String value) {
headerMap.put(name, value);
}
/**
* @author lwh
* @date 2024/1/16
* @description 获取指定的header
**/
@Override
public String getHeader(String name) {
String headerValue = super.getHeader(name);
if (headerMap.containsKey(name)) {
headerValue = headerMap.get(name);
}
return headerValue;
}
/**
* @author lwh
* @date 2024/1/16
* @description 获取所有的header名称
**/
@Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
for (String name : headerMap.keySet()) {
names.add(name);
}
return Collections.enumeration(names);
}
/**
* @author lwh
* @date 2024/1/16
* @description 获取数组类型的header
**/
@Override
public Enumeration<String> getHeaders(String name) {
List<String> values = Collections.list(super.getHeaders(name));
if (headerMap.containsKey(name)) {
values = Arrays.asList(headerMap.get(name));
}
return Collections.enumeration(values);
}
}
CommonCode
public enum CommonCode{
//公共部分
SUCCESS(200, "成功"),
CODE_00001(1001, "系统繁忙,请稍后重试!"),
CODE_00002(1002, "参数异常"),
CODE_00404(1003, "请求不存在!"),
CODE_00405(1004, "验签失败!"),
CODE_FAIL_1(-1, "");
CommonCode(Integer code, String msg) {
this.code = code;
this.msg = msg;
}
private Integer code;
private String msg;
@Override
public Integer code() {
return code;
}
@Override
public String msg() {
return msg;
}
}
RequestUtil
package com.lwh.loancloud.utils;
import java.io.*;
import java.net.URLDecoder;
import java.util.*;
import javax.servlet.http.HttpServletRequest;
/**
* request工具类
*/
public class RequestUtil {
/**
* 移除request指定参数
*
* @param request
* @param paramName
* @return
*/
public String removeParam(HttpServletRequest request, String paramName) {
String queryString = "";
Enumeration keys = request.getParameterNames();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
if (key.equals(paramName)) {
continue;
}
if ("".equals(queryString)) {
queryString = key + "=" + request.getParameter(key);
} else {
queryString += "&" + key + "=" + request.getParameter(key);
}
}
return queryString;
}
/**
* 获取请求basePath
*
* @param request
* @return
*/
public static String getBasePath(HttpServletRequest request) {
StringBuffer basePath = new StringBuffer();
String scheme = request.getScheme();
String domain = request.getServerName();
int port = request.getServerPort();
basePath.append(scheme);
basePath.append("://");
basePath.append(domain);
if ("http".equalsIgnoreCase(scheme) && 80 != port) {
basePath.append(":").append(String.valueOf(port));
} else if ("https".equalsIgnoreCase(scheme) && port != 443) {
basePath.append(":").append(String.valueOf(port));
}
return basePath.toString();
}
/**
* 获取ip工具类,除了getRemoteAddr,其他ip均可伪造
*
* @param request
* @return
*/
public static String getIpAddr(HttpServletRequest request) {
String ip = request.getHeader("Cdn-Src-Ip"); // 网宿cdn的真实ip
if (ip == null || ip.length() == 0 || " unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("HTTP_CLIENT_IP"); // 蓝讯cdn的真实ip
}
if (ip == null || ip.length() == 0 || " unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For"); // 获取代理ip
}
if (ip == null || ip.length() == 0 || " unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("Proxy-Client-IP"); // 获取代理ip
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("WL-Proxy-Client-IP"); // 获取代理ip
}
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getRemoteAddr(); // 获取真实ip
}
return ip;
}
/**
* 获取head key
*
* @param request
* @param key
* @return
*/
public static String getHeadParams(HttpServletRequest request, String key) {
String token = request.getHeader(key);
if (token == null) {
token = request.getParameter(key);
if (token == null) {
return null;
}
}
return token.trim();
}
/**
* 从请求中获取body数据
*
* @param request
* @return
* @throws Exception
*/
public static String getBodyString(HttpServletRequest request) throws Exception {
request.setCharacterEncoding("UTF-8");
String body = null;
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
InputStream inputStream = request.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
}
} catch (IOException ex) {
throw ex;
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException ex) {
throw ex;
}
}
}
body = stringBuilder.toString();
return body;
}
/**
* 请求中参数转Map<String, String>,for支付宝异步回调,平时建议直接使用request.getParameterMap(),返回Map<String, String[]>
*
* @param request
* @return
*/
public static Map<String, String> getParameterMap(HttpServletRequest request) {
Map<String, String> result = new LinkedHashMap<>();
Enumeration parameterNames = request.getParameterNames();
while (parameterNames.hasMoreElements()) {
String parameterName = (String) parameterNames.nextElement();
result.put(parameterName, request.getParameter(parameterName));
}
return result;
}
public static String buildUrlParameter(Map<String, String> dateArry) {
StringBuilder postParameter = new StringBuilder();
int postItemTotal = dateArry.keySet().size();
int item = 0;
for (String key : dateArry.keySet()) {
postParameter.append(key).append("=").append(dateArry.get(key));
item++;
if (item < postItemTotal) {
postParameter.append("&");
}
}
return postParameter.toString();
}
/**
* 转换返回参数处理
*
* @return
* @throws Exception
*/
public static Map<String, String> convertMap(String responseString) {
Map<String, String> map = new TreeMap<String, String>();
String[] listObj = responseString.split("&");
for (String temp : listObj) {
if (temp.matches("(.+?)=(.+?)")) {
String[] tempListObj = temp.split("=");
map.put(tempListObj[0], tempListObj[1]);
} else if (temp.matches("(.+?)=")) {
String[] tempListObj = temp.split("=");
map.put(tempListObj[0], "");
} else {
throw new RuntimeException("参数无法分解!");
}
}
return map;
}
/**
* @author lwh
* @date 2024/2/27
* @description 获取有序的参数
**/
public static Map<String, String> getOrderedParams(HttpServletRequest request) throws UnsupportedEncodingException {
Map<String, String> orderedParams = new LinkedHashMap<>();
// 获取原始查询字符串
String queryString = URLDecoder.decode(request.getQueryString(), "utf-8");
if (queryString != null) {
String[] pairs = queryString.split("&");
for (String pair : pairs) {
int idx = pair.indexOf("=");
String key = idx > 0 ? pair.substring(0, idx) : pair;
orderedParams.put(key, idx > 0 && pair.length() > idx + 1 ? pair.substring(idx + 1) : null);
}
}
return orderedParams;
}
}
ServerResponse
import com.fasterxml.jackson.annotation.JsonInclude;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import java.io.Serializable;
@JsonInclude(JsonInclude.Include.NON_NULL)
@ApiModel(value = "服务响应", description = "通用服务响应结果")
public class ServerResponse<T> implements Serializable {
/**
* 成功or失败
*/
@ApiModelProperty(value = "结果", example = "true")
private boolean result;
/**
* 成功or失败
*/
@ApiModelProperty(value = "结果", example = "true")
private boolean success;
/**
* 状态
*/
@ApiModelProperty(value = "状态码,成功:00000", example = "00000")
private Integer code;
/**
* 描述
*/
@ApiModelProperty(value = "描述,错误描述", example = "SUCCESS")
private String message;
/**
* 响应时间
*/
@ApiModelProperty(value = "响应时间")
private long serverTime = System.currentTimeMillis();
private T data;
public ServerResponse() {
}
public ServerResponse(boolean result, Integer code, T data) {
this.result = result;
this.code = code;
this.data = data;
}
public ServerResponse(Integer code, String message, T data) {
this.code = code;
this.message = message;
this.data = data;
}
public ServerResponse(boolean result, Integer code, String message, T data) {
this.result = result;
this.code = code;
this.message = message;
this.data = data;
}
public ServerResponse(boolean result, Integer code, String message) {
this.result = result;
this.code = code;
this.message = message;
}
public Integer getCode() {
return code;
}
public T getData() {
return data;
}
public String getMessage() {
return message;
}
public static <T> ServerResponse<T> createBySuccess() {
return new ServerResponse<T>(true, CommonCode.SUCCESS.code(), CommonCode.SUCCESS.msg());
}
public static <T> ServerResponse<T> createBySuccess(T data) {
return new ServerResponse<T>(true, CommonCode.SUCCESS.code(), CommonCode.SUCCESS.msg(), data);
}
public static <T> ServerResponse<T> createBySuccess(String message, T data) {
return new ServerResponse<T>(true, CommonCode.SUCCESS.code(), message, data);
}
public static <T> ServerResponse<T> createByError() {
return new ServerResponse<T>(false, CommonCode.CODE_00001.code(), CommonCode.CODE_00001.msg());
}
public static <T> ServerResponse<T> createByErrorMessage(String message) {
return new ServerResponse<T>(false, CommonCode.CODE_00001.code(), message);
}
public static <T> ServerResponse<T> createByErrorCodeMessage(Integer code, String message) {
return new ServerResponse<T>(false, code, message);
}
public static <T> ServerResponse<T> createErrorByCode(ResultCode statusCode) {
return new ServerResponse<T>(false, statusCode.code(), statusCode.msg());
}
public void setCode(Integer code) {
this.code = code;
}
public void setData(T data) {
this.data = data;
}
public void setMessage(String message) {
this.message = message;
}
public void setResult(boolean result) {
this.result = result;
}
public boolean isResult() {
return success;
}
public void setSuccess(boolean success) {
this.success = success;
}
public boolean isSuccess() {
return success;
}
public long getServerTime() {
return serverTime;
}
public void setServerTime(long serverTime) {
this.serverTime = serverTime;
}
@Override
public String toString() {
return "ServerResponse{" +
"result=" + result +
", code='" + code + '\'' +
", data=" + data +
", message='" + message + '\'' +
'}';
}
}
解释
首先服务端要生成一对公钥和私钥,私钥给到客户端,公钥自己服务端保存,除了私钥外,还要提供appId和version给到客户端,客户端根据服务端生成签名内容的方式生成签名内容,之后服务端根据客户端传递过来的内容生成签名内容,后面根据签名内容和公钥校验签名sign,对于上传文件则不校验签名。