mirror of
https://gitee.com/durcframework/SOP.git
synced 2025-08-11 21:57:56 +08:00
秘钥管理改造,服务端返回sign
This commit is contained in:
@@ -17,6 +17,12 @@ public interface Isv {
|
||||
*/
|
||||
String getSecretInfo();
|
||||
|
||||
/**
|
||||
* 获取平台的私钥
|
||||
* @return 返回私钥
|
||||
*/
|
||||
String getPrivateKeyPlatform();
|
||||
|
||||
/**
|
||||
* 0启用,1禁用
|
||||
* @return 返回状态
|
||||
|
@@ -2,9 +2,6 @@ package com.gitee.sop.gatewaycommon.bean;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
@@ -13,6 +10,8 @@ import java.util.Date;
|
||||
@Setter
|
||||
public class IsvDefinition implements Isv {
|
||||
|
||||
public static final int SIGN_TYPE_RSA2 = 1;
|
||||
|
||||
public IsvDefinition() {
|
||||
}
|
||||
|
||||
@@ -21,35 +20,38 @@ public class IsvDefinition implements Isv {
|
||||
this.secret = secret;
|
||||
}
|
||||
|
||||
private Long id;
|
||||
|
||||
private String appKey;
|
||||
|
||||
/** 秘钥,如果是支付宝开放平台,对应的pubKey */
|
||||
/** 秘钥,签名方式为MD5时有用 */
|
||||
private String secret;
|
||||
|
||||
private String pubKey;
|
||||
/** 开发者生成的公钥, 数据库字段:public_key_isv */
|
||||
private String publicKeyIsv;
|
||||
|
||||
/** 平台生成的私钥, 数据库字段:private_key_platform */
|
||||
private String privateKeyPlatform;
|
||||
|
||||
/** 0启用,1禁用 */
|
||||
private Byte status;
|
||||
|
||||
private Date gmtCreate;
|
||||
|
||||
private Date gmtModified;
|
||||
/** 签名类型:1:RSA2,2:MD5 */
|
||||
private Byte signType = 1;
|
||||
|
||||
@Override
|
||||
public String getSecretInfo() {
|
||||
return StringUtils.isBlank(pubKey) ? secret : pubKey;
|
||||
return signType == SIGN_TYPE_RSA2 ? publicKeyIsv : secret;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "IsvDefinition{" +
|
||||
"id=" + id +
|
||||
", appKey='" + appKey + '\'' +
|
||||
"appKey='" + appKey + '\'' +
|
||||
", secret='" + secret + '\'' +
|
||||
", publicKeyIsv='" + publicKeyIsv + '\'' +
|
||||
", privateKeyPlatform='" + privateKeyPlatform + '\'' +
|
||||
", status=" + status +
|
||||
", gmtCreate=" + gmtCreate +
|
||||
", gmtModified=" + gmtModified +
|
||||
", signType=" + signType +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ public class SopConstants {
|
||||
private SopConstants() {}
|
||||
|
||||
public static final Charset CHARSET_UTF8 = StandardCharsets.UTF_8;
|
||||
public static final String UTF8 = "UTF-8";
|
||||
public static final String FORMAT_JSON = "json";
|
||||
public static final String DEFAULT_SIGN_METHOD = "md5";
|
||||
public static final String EMPTY_JSON = "{}";
|
||||
|
@@ -3,7 +3,6 @@ package com.gitee.sop.gatewaycommon.easyopen;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.secret.SecretContext;
|
||||
import com.gitee.sop.gatewaycommon.zuul.configuration.BaseZuulConfiguration;
|
||||
|
||||
/**
|
||||
@@ -11,10 +10,6 @@ import com.gitee.sop.gatewaycommon.zuul.configuration.BaseZuulConfiguration;
|
||||
*/
|
||||
public class EasyopenZuulConfiguration extends BaseZuulConfiguration {
|
||||
|
||||
static {
|
||||
SecretContext.setSecretGetter((isvDefinition)-> isvDefinition.getSecret());
|
||||
}
|
||||
|
||||
public EasyopenZuulConfiguration() {
|
||||
ApiConfig apiConfig = ApiContext.getApiConfig();
|
||||
if (compatibilityModel()) {
|
||||
|
@@ -7,17 +7,23 @@ import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseServiceRouteInfo;
|
||||
import com.gitee.sop.gatewaycommon.bean.ErrorDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.Isv;
|
||||
import com.gitee.sop.gatewaycommon.bean.SopConstants;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorMeta;
|
||||
import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||
import com.gitee.sop.gatewaycommon.validate.alipay.AlipayConstants;
|
||||
import com.gitee.sop.gatewaycommon.validate.alipay.AlipaySignature;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang.BooleanUtils;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
@@ -26,6 +32,7 @@ import java.util.Optional;
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
@Slf4j
|
||||
public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R> {
|
||||
private static final ErrorMeta SUCCESS_META = ErrorEnum.SUCCESS.getErrorMeta();
|
||||
private static final ErrorMeta ISP_UNKNOW_ERROR_META = ErrorEnum.ISP_UNKNOW_ERROR.getErrorMeta();
|
||||
@@ -36,6 +43,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
public static final String ARRAY_START = "[";
|
||||
public static final String ARRAY_END = "]";
|
||||
public static final String ROOT_JSON = "{'items':%s}".replace("'", "\"");
|
||||
public static final String ERROR_METHOD = "error";
|
||||
|
||||
|
||||
/**
|
||||
@@ -70,33 +78,33 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
}
|
||||
serviceResult = wrapResult(serviceResult);
|
||||
int responseStatus = this.getResponseStatus(request);
|
||||
JSONObject jsonObjectService;
|
||||
JSONObject responseData;
|
||||
if (responseStatus == HttpStatus.OK.value()) {
|
||||
// 200正常返回
|
||||
jsonObjectService = JSON.parseObject(serviceResult);
|
||||
jsonObjectService.put(GATEWAY_CODE_NAME, SUCCESS_META.getCode());
|
||||
jsonObjectService.put(GATEWAY_MSG_NAME, SUCCESS_META.getError().getMsg());
|
||||
responseData = JSON.parseObject(serviceResult);
|
||||
responseData.put(GATEWAY_CODE_NAME, SUCCESS_META.getCode());
|
||||
responseData.put(GATEWAY_MSG_NAME, SUCCESS_META.getError().getMsg());
|
||||
} else if (responseStatus == SopConstants.BIZ_ERROR_STATUS) {
|
||||
// 如果是业务出错
|
||||
this.storeError(request, ErrorType.BIZ);
|
||||
jsonObjectService = JSON.parseObject(serviceResult);
|
||||
jsonObjectService.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
|
||||
jsonObjectService.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg());
|
||||
responseData = JSON.parseObject(serviceResult);
|
||||
responseData.put(GATEWAY_CODE_NAME, ISP_BIZ_ERROR.getCode());
|
||||
responseData.put(GATEWAY_MSG_NAME, ISP_BIZ_ERROR.getError().getMsg());
|
||||
} else {
|
||||
this.storeError(request, ErrorType.UNKNOWN);
|
||||
// 微服务端有可能返回500错误
|
||||
// {"path":"/book/getBook3","error":"Internal Server Error","message":"id不能为空","timestamp":"2019-02-13T07:41:00.495+0000","status":500}
|
||||
jsonObjectService = new JSONObject();
|
||||
jsonObjectService.put(GATEWAY_CODE_NAME, ISP_UNKNOW_ERROR_META.getCode());
|
||||
jsonObjectService.put(GATEWAY_MSG_NAME, ISP_UNKNOW_ERROR_META.getError().getMsg());
|
||||
responseData = new JSONObject();
|
||||
responseData.put(GATEWAY_CODE_NAME, ISP_UNKNOW_ERROR_META.getCode());
|
||||
responseData.put(GATEWAY_MSG_NAME, ISP_UNKNOW_ERROR_META.getError().getMsg());
|
||||
}
|
||||
return this.merge(request, jsonObjectService);
|
||||
return this.merge(request, responseData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存错误信息
|
||||
*
|
||||
* @param request
|
||||
* @param request request
|
||||
*/
|
||||
protected void storeError(T request, ErrorType errorType) {
|
||||
ApiInfo apiInfo = this.getApiInfo(request);
|
||||
@@ -116,7 +124,7 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
/**
|
||||
* 该路由是否合并结果
|
||||
*
|
||||
* @param request
|
||||
* @param request request
|
||||
* @return true:需要合并
|
||||
*/
|
||||
protected boolean isMergeResult(T request) {
|
||||
@@ -137,13 +145,8 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
|
||||
protected ApiInfo getApiInfo(T request) {
|
||||
Map<String, Object> params = this.getApiParam(request);
|
||||
String name = Optional.ofNullable(params)
|
||||
.map(map -> (String) map.get(ParamNames.API_NAME))
|
||||
.orElse("method.unknown");
|
||||
|
||||
String version = Optional.ofNullable(params)
|
||||
.map(map -> (String) map.get(ParamNames.VERSION_NAME))
|
||||
.orElse("version.unknown");
|
||||
String name = this.getParamValue(params, ParamNames.API_NAME, "method.unknown");
|
||||
String version = this.getParamValue(params, ParamNames.VERSION_NAME, "version.unknown");
|
||||
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(name + version);
|
||||
|
||||
@@ -178,47 +181,67 @@ public abstract class BaseExecutorAdapter<T, R> implements ResultExecutor<T, R>
|
||||
return serviceResult;
|
||||
}
|
||||
|
||||
public String merge(T exchange, JSONObject jsonObjectService) {
|
||||
JSONObject ret = new JSONObject();
|
||||
String name = "error";
|
||||
public String merge(T exchange, JSONObject responseData) {
|
||||
JSONObject finalData = new JSONObject();
|
||||
Map<String, Object> params = this.getApiParam(exchange);
|
||||
if (params != null) {
|
||||
Object method = params.get(ParamNames.API_NAME);
|
||||
if (method != null) {
|
||||
name = String.valueOf(method);
|
||||
}
|
||||
}
|
||||
String name = this.getParamValue(params, ParamNames.API_NAME, ERROR_METHOD);
|
||||
ApiConfig apiConfig = ApiConfig.getInstance();
|
||||
// 点换成下划线
|
||||
DataNameBuilder dataNameBuilder = apiConfig.getDataNameBuilder();
|
||||
String method = dataNameBuilder.build(name);
|
||||
ret.put(method, jsonObjectService);
|
||||
this.appendReturnSign(apiConfig, params, ret);
|
||||
// alipay_goods_get_response
|
||||
String responseDataNodeName = dataNameBuilder.build(name);
|
||||
finalData.put(responseDataNodeName, responseData);
|
||||
ResultAppender resultAppender = apiConfig.getResultAppender();
|
||||
// 追加额外的结果
|
||||
if (resultAppender != null) {
|
||||
resultAppender.append(ret, params, exchange);
|
||||
resultAppender.append(finalData, params, exchange);
|
||||
}
|
||||
return ret.toJSONString();
|
||||
// 添加服务端sign
|
||||
if (apiConfig.isShowReturnSign() && !CollectionUtils.isEmpty(params)) {
|
||||
// 添加try...catch,生成sign出错不影响结果正常返回
|
||||
try {
|
||||
String sign = this.createResponseSign(apiConfig, params, responseData.toJSONString());
|
||||
if (StringUtils.hasLength(sign)) {
|
||||
finalData.put(ParamNames.SIGN_NAME, sign);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("生成平台签名失败, params: {}, serviceResult:{}", JSON.toJSONString(params), responseData, e);
|
||||
}
|
||||
}
|
||||
return finalData.toJSONString();
|
||||
}
|
||||
|
||||
protected void appendReturnSign(ApiConfig apiConfig, Map<String, ?> params, JSONObject ret) {
|
||||
if (apiConfig.isShowReturnSign() && params != null) {
|
||||
Object appKey = params.get(ParamNames.APP_KEY_NAME);
|
||||
String sign = this.createReturnSign(String.valueOf(appKey));
|
||||
ret.put(ParamNames.SIGN_NAME, sign);
|
||||
}
|
||||
protected String getParamValue(Map<String, Object> apiParam, String key, String defaultValue) {
|
||||
return CollectionUtils.isEmpty(apiParam) ? defaultValue : (String) apiParam.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 这里需要使用平台的私钥生成一个sign,需要配置两套公私钥。目前暂未实现
|
||||
* 这里需要使用平台的私钥生成一个sign,需要配置两套公私钥。
|
||||
*
|
||||
* @param appKey
|
||||
* @return
|
||||
* @param apiConfig 配置
|
||||
* @param params 请求参数
|
||||
* @param serviceResult 业务返回结果
|
||||
* @return 返回平台生成的签名
|
||||
*/
|
||||
protected String createReturnSign(String appKey) {
|
||||
// TODO: 返回sign
|
||||
protected String createResponseSign(ApiConfig apiConfig, Map<String, Object> params, String serviceResult) {
|
||||
IsvManager isvManager = apiConfig.getIsvManager();
|
||||
// 根据appId获取秘钥
|
||||
String appKey = this.getParamValue(params, ParamNames.APP_KEY_NAME, "");
|
||||
if (StringUtils.isEmpty(appKey)) {
|
||||
return null;
|
||||
}
|
||||
Isv isvInfo = isvManager.getIsv(appKey);
|
||||
String privateKeyPlatform = isvInfo.getPrivateKeyPlatform();
|
||||
if (StringUtils.isEmpty(privateKeyPlatform)) {
|
||||
return null;
|
||||
}
|
||||
String charset = Optional.ofNullable(params.get(ParamNames.CHARSET_NAME))
|
||||
.map(String::valueOf)
|
||||
.orElse(SopConstants.UTF8);
|
||||
String signType = getParamValue(params, ParamNames.SIGN_TYPE_NAME, AlipayConstants.SIGN_TYPE_RSA2);
|
||||
return AlipaySignature.rsaSign(serviceResult, privateKeyPlatform, charset, signType);
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
|
@@ -1,20 +0,0 @@
|
||||
package com.gitee.sop.gatewaycommon.secret;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.IsvDefinition;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* @author tanghc
|
||||
*/
|
||||
public class SecretContext {
|
||||
private static volatile Function<IsvDefinition, String> secretGetter = (isv) -> isv.getPubKey();
|
||||
|
||||
public static Function<IsvDefinition, String> getSecretGetter() {
|
||||
return secretGetter;
|
||||
}
|
||||
|
||||
public static void setSecretGetter(Function<IsvDefinition, String> secretGetter) {
|
||||
SecretContext.secretGetter = secretGetter;
|
||||
}
|
||||
}
|
@@ -2,8 +2,11 @@ package com.gitee.sop.gatewaycommon.validate;
|
||||
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.ApiContext;
|
||||
import com.gitee.sop.gatewaycommon.bean.BaseRouteDefinition;
|
||||
import com.gitee.sop.gatewaycommon.bean.Isv;
|
||||
import com.gitee.sop.gatewaycommon.bean.RouteConfig;
|
||||
import com.gitee.sop.gatewaycommon.bean.TargetRoute;
|
||||
import com.gitee.sop.gatewaycommon.manager.IsvRoutePermissionManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteConfigManager;
|
||||
import com.gitee.sop.gatewaycommon.manager.RouteRepositoryContext;
|
||||
import com.gitee.sop.gatewaycommon.message.ErrorEnum;
|
||||
@@ -12,6 +15,7 @@ import com.gitee.sop.gatewaycommon.param.ParamNames;
|
||||
import com.gitee.sop.gatewaycommon.param.UploadContext;
|
||||
import com.gitee.sop.gatewaycommon.secret.IsvManager;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.util.Assert;
|
||||
@@ -57,6 +61,7 @@ public class ApiValidator implements Validator {
|
||||
checkTimeout(param);
|
||||
checkFormat(param);
|
||||
checkUploadFile(param);
|
||||
checkPermission(param);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -156,9 +161,8 @@ public class ApiValidator implements Validator {
|
||||
throw ErrorEnum.ISV_MISSING_SIGNATURE_CONFIG.getErrorMeta().getException();
|
||||
}
|
||||
Signer signer = apiConfig.getSigner();
|
||||
boolean isRightSign = signer.checkSign(param, secret);
|
||||
// 错误的sign
|
||||
if (!isRightSign) {
|
||||
if (!signer.checkSign(param, secret)) {
|
||||
throw ErrorEnum.ISV_INVALID_SIGNATURE.getErrorMeta().getException(param.fetchNameVersion());
|
||||
}
|
||||
} finally {
|
||||
@@ -177,4 +181,23 @@ public class ApiValidator implements Validator {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验访问权限
|
||||
* @param apiParam
|
||||
*/
|
||||
protected void checkPermission(ApiParam apiParam) {
|
||||
String routeId = apiParam.fetchNameVersion();
|
||||
TargetRoute targetRoute = RouteRepositoryContext.getRouteRepository().get(routeId);
|
||||
BaseRouteDefinition routeDefinition = targetRoute.getRouteDefinition();
|
||||
boolean needCheckPermission = BooleanUtils.toBoolean(routeDefinition.getPermission());
|
||||
if (needCheckPermission) {
|
||||
IsvRoutePermissionManager isvRoutePermissionManager = ApiConfig.getInstance().getIsvRoutePermissionManager();
|
||||
String appKey = apiParam.fetchAppKey();
|
||||
boolean hasPermission = isvRoutePermissionManager.hasPermission(appKey, routeId);
|
||||
if (!hasPermission) {
|
||||
throw ErrorEnum.ISV_ROUTE_NO_PERMISSIONS.getErrorMeta().getException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -88,16 +88,16 @@ public class AlipaySignature {
|
||||
* sha256WithRsa 加签
|
||||
*
|
||||
* @param content
|
||||
* @param publicKey
|
||||
* @param privateKey
|
||||
* @param charset
|
||||
* @return
|
||||
*/
|
||||
public static String rsa256Sign(String content, String publicKey,
|
||||
public static String rsa256Sign(String content, String privateKey,
|
||||
String charset) {
|
||||
|
||||
try {
|
||||
PrivateKey priKey = getPrivateKeyFromPKCS8(AlipayConstants.SIGN_TYPE_RSA,
|
||||
new ByteArrayInputStream(publicKey.getBytes()));
|
||||
new ByteArrayInputStream(privateKey.getBytes()));
|
||||
|
||||
java.security.Signature signature = java.security.Signature
|
||||
.getInstance(AlipayConstants.SIGN_SHA256RSA_ALGORITHMS);
|
||||
|
@@ -7,7 +7,6 @@ import com.gitee.sop.gatewaycommon.zuul.filter.ErrorFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.FormBodyWrapperFilterExt;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PostResultFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreLimitFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreRoutePermissionFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.PreValidateFilter;
|
||||
import com.gitee.sop.gatewaycommon.zuul.filter.Servlet30WrapperFilterExt;
|
||||
import com.gitee.sop.gatewaycommon.zuul.route.SopRouteLocator;
|
||||
@@ -96,14 +95,6 @@ public class BaseZuulConfiguration extends AbstractConfiguration {
|
||||
return new PreLimitFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 权限校验
|
||||
*/
|
||||
@Bean
|
||||
PreRoutePermissionFilter preRoutePermissionFilter() {
|
||||
return new PreRoutePermissionFilter();
|
||||
}
|
||||
|
||||
/**
|
||||
* 错误处理扩展
|
||||
*/
|
||||
|
@@ -15,7 +15,9 @@ import org.apache.commons.lang3.BooleanUtils;
|
||||
/**
|
||||
* 路由权限校验,有些接口需要配置权限才能访问。
|
||||
* @author tanghc
|
||||
* @deprecated 已经整合到ApiValidator中,见ApiValidator.checkPermission()
|
||||
*/
|
||||
@Deprecated
|
||||
public class PreRoutePermissionFilter extends BaseZuulFilter {
|
||||
@Override
|
||||
protected FilterType getFilterType() {
|
||||
|
Reference in New Issue
Block a user