diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/Isv.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/Isv.java index 5eb8e287..32863b60 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/Isv.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/Isv.java @@ -17,6 +17,12 @@ public interface Isv { */ String getSecretInfo(); + /** + * 获取平台的私钥 + * @return 返回私钥 + */ + String getPrivateKeyPlatform(); + /** * 0启用,1禁用 * @return 返回状态 diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/IsvDefinition.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/IsvDefinition.java index 89dc010d..c7c4f738 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/IsvDefinition.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/IsvDefinition.java @@ -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 + '}'; } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java index 2f824745..d97e921a 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/bean/SopConstants.java @@ -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 = "{}"; diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java index 78f50e60..5e531fe4 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/easyopen/EasyopenZuulConfiguration.java @@ -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()) { diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java index ab7394e4..34ca08cc 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/result/BaseExecutorAdapter.java @@ -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 implements ResultExecutor { 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 implements ResultExecutor 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 implements ResultExecutor } 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 implements ResultExecutor /** * 该路由是否合并结果 * - * @param request + * @param request request * @return true:需要合并 */ protected boolean isMergeResult(T request) { @@ -137,13 +145,8 @@ public abstract class BaseExecutorAdapter implements ResultExecutor protected ApiInfo getApiInfo(T request) { Map 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,46 +181,66 @@ public abstract class BaseExecutorAdapter implements ResultExecutor 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 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); + } + // 添加服务端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 ret.toJSONString(); + return finalData.toJSONString(); } - protected void appendReturnSign(ApiConfig apiConfig, Map 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 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 - return null; + protected String createResponseSign(ApiConfig apiConfig, Map 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 diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/SecretContext.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/SecretContext.java deleted file mode 100644 index 811e9901..00000000 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/secret/SecretContext.java +++ /dev/null @@ -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 secretGetter = (isv) -> isv.getPubKey(); - - public static Function getSecretGetter() { - return secretGetter; - } - - public static void setSecretGetter(Function secretGetter) { - SecretContext.secretGetter = secretGetter; - } -} diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java index f9bffe6d..7a20acba 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/ApiValidator.java @@ -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(); + } + } + } + } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySignature.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySignature.java index 7ba2f51e..2ad8bb75 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySignature.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/validate/alipay/AlipaySignature.java @@ -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); diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java index 051d1a5d..b961ced1 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/configuration/BaseZuulConfiguration.java @@ -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(); - } - /** * 错误处理扩展 */ diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreRoutePermissionFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreRoutePermissionFilter.java index 37cda8e7..b100dc8e 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreRoutePermissionFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PreRoutePermissionFilter.java @@ -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() {