签名生成及验签

签名生成及验签实在开放平台中应用,暂时不清楚调用流程及原理

一,生成签名

签名规则

签名sign是通过应用在平台的私钥和请求参数根据一定算法生成的签名值,主要是防止参数在传输过程中被篡改, 同时对调用方的身份进行校验

签名生成步骤:

    第1步:

      将除签名sign外的所有请求参数按参数名首字母进行升序排序,其中参数包括系统参数和业务参数
    

    第2步:

      将第1步得到的排序结果,依次按照"keyvalue"的形式拼接成待加密的串
    

    第3步

      对第2步得到的新串进行HMAC加密并转化为大写的十六进制签名串
    

签名示例如下:

假设请求为:/anji-open/open-api/request?appId=testApp&method=testApp&sign=8D45C66B6E1E773614E5866541EAF78D&timestamp=111&name=hello&password=123456&age=12

1、关键信息:
应用私钥:29bca37bf0174ea287a770cd0d4ff83c
请求参数:appId=testApp&method=testApp&sign=8D45C66B6E1E773614E5866541EAF78D&timestamp=111&name=hello&password=123456&age=12

2、将除"sign"外所有的参数进行排序:age、appId、method、name、password、timestamp;
依次按照"keyvalue"形式拼接的结果:age12appIdtestAppmethodtestAppnamehellopassword123456timestamp111

3、将得到的新串进行HMAC加密并转化为大写的十六进制签名串:8D45C66B6E1E773614E5866541EAF78D

二,生成签名代码

参数+秘钥(生成随机数)

/**
 * 验证签名,防止数据被篡改
 * @author lr
 * @date 2019-05-29 17:02
 */
public class SignUtils {

    private final static String SIGN_PARAM="sign";

    /**
     * 生产签名
     * @param params
     * @param secret
     * @return
     * @throws IOException
     */
    public static String generateSign(Map<String, String> params, String secret) throws IOException {
        // 第一步:检查参数是否已经排序
        String[] keys = params.keySet().toArray(new String[0]);
        Arrays.sort(keys);

        // 第二步:把所有参数名和参数值串在一起
        StringBuilder query = new StringBuilder();
        for (String key : keys) {
            //跳过参数"sign",sign不参与签名
            if(StringUtils.equals(SIGN_PARAM,key)) {
                continue;
            }
            String value = params.get(key);
            if (StringUtils.isNotBlank(value)) {
                query.append(key).append(value);
            }
        }

        // 第三步:使用HMAC加密
        byte[] bytes = encryptHMAC(query.toString(), secret);

        // 第四步:把二进制转化为大写的十六进制(正确签名应该为32大写字符串,此方法需要时使用)
        return byte2hex(bytes);
    }

    /**
     * 加密
     * @param data
     * @param secret
     * @return
     * @throws IOException
     */
    public static byte[] encryptHMAC(String data, String secret) throws IOException {
        byte[] bytes = null;
        try {
            SecretKey secretKey = new SecretKeySpec(secret.getBytes(ApiConstants.CHARSET_UTF8), "HmacMD5");
            Mac mac = Mac.getInstance(secretKey.getAlgorithm());
            mac.init(secretKey);
            bytes = mac.doFinal(data.getBytes(ApiConstants.CHARSET_UTF8));
        } catch (GeneralSecurityException gse) {
            throw new IOException(gse.toString());
        }
        return bytes;
    }

    /**
     * 转16进制
     * @param bytes
     * @return
     */
    public static String byte2hex(byte[] bytes) {
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex.toUpperCase());
        }
        return sign.toString();
    }

    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>(16);
        map.put("appId","testApp");
        map.put("method","testPost");
        map.put("timestamp","111");
        try {
            String s = generateSign(map, "e113e43bb76a437d94848a2bbbb45b0b");
            System.out.println(s);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3,验签

在网关中进行验签

流程:从请求参数中获取参数+应用中秘钥,再次生成签名,比较入参签名和新生成签名是否一致,是验签通过

###疑问

1,在项目中参数生成签名,与接口接收的参数生成签名中间,参数何时会改变?接口调用流程是怎么样的?

2,如何进行身份验证的?

/**
 * 校验appId及秘钥
 * @author lr
 * @date 2019-07-26 09:54
 */
public class OpenAppInfoGatewayFilterFactory
        extends AbstractGatewayFilterFactory<OpenAppInfoGatewayFilterFactory.Config> implements ParentGatewayFilterFactory{

    private Logger logger = LoggerFactory.getLogger(OpenAppInfoGatewayFilterFactory.class);

    public final static int OPEN_APP_INFO_FILTER_ORDER = 2;

    @Autowired
    private CacheHelper cacheHelper;

    public OpenAppInfoGatewayFilterFactory() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return new OpenAppInfoGatewayFilter();
    }

    /**
     * 内部内的作用,排序,AbstractGatewayFilterFactory实现ordered没效果
     * 原因:获取过滤器调用apply()返回的GatewayFilter,没有实现Ordered接口,无法排序
     * 参考org.springframework.cloud.gateway.route.RouteDefinitionRouteLocator#loadGatewayFilters(java.lang.String, java.util.List):186行
     */
    private class OpenAppInfoGatewayFilter implements GatewayFilter, Ordered {

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
            ServerHttpRequest request = exchange.getRequest();
            MultiValueMap<String, String> params = request.getQueryParams();
            //1、校验应用appId
            String appId = params.getFirst(SystemParamEnum.APPID.getValue());

            //获取缓存中的应用对象
            String appSecret = cacheHelper.getOpenAppSecret(appId);
            if(appSecret == null) {
                return getFilterResult(exchange,getResponseBean(ErrorCode.OPEN_NO_APP,appId));
            }

            //2、校验method是否合法
            String method = params.getFirst(SystemParamEnum.METHOD.getValue());
            //获取缓存中的接口对象
            ApiDocument apiDocument = cacheHelper.getApiDocument(method);

            if(apiDocument == null) {
                return getFilterResult(exchange,getResponseBean(ErrorCode.OPEN_NO_METHOD,method));
            }
            //校验接口对应的项目编码
            String projectCode = apiDocument.getProjectCode();
            if(StringUtils.isBlank(projectCode)) {
                return getFilterResult(exchange,getResponseBean(ErrorCode.METHOD_NO_PROJECT_CODE,method));
            }

            //3、校验接口对应的项目
            String apiProjectUrl = cacheHelper.getApiProjectUrl(projectCode);
            if(apiProjectUrl == null) {
                return getFilterResult(exchange,getResponseBean(ErrorCode.OPEN_NO_PROJECT,projectCode));
            }
***************************验签************************************************************************
            try {
                String openSecret = SignUtils.generateSign(params.toSingleValueMap(), appSecret);
                //判断秘钥
                if(!StringUtils.equals(openSecret,params.getFirst(SystemParamEnum.SIGN.getValue()))) {
                    logger.error("秘钥验证错误,{}",params);
                    return getFilterResult(exchange,getResponseBean(ErrorCode.APP_ERROR_SIGN,appId,openSecret));
                }
            } catch (IOException e) {
                return getFilterResult(exchange,getResponseBean(ErrorCode.OPEN_APP_ERROR_SIGN,appId));
            }

            //校验参数包括请求头、请求参数必输项
            ServerHttpRequest.Builder mutate = request.mutate();

            //4、校验请求头
            String requestHeaders = apiDocument.getRequestHeaders();
            List<JSONObject> requestHeaderList = new ArrayList<>();
            if(StringUtils.isNotBlank(requestHeaders)) {
                requestHeaderList.addAll(OpenUtils.getListJson(requestHeaders));
                //构造新请求
                for(JSONObject header : requestHeaderList) {
                    if(StringUtils.equals("1",header.getString("fixed"))) {
                        mutate.header(header.getString("name"),new String[]{header.getString("value")});
                    }else{
                        //请求头缺少
                        if(StringUtils.isBlank(request.getHeaders().getFirst(header.getString("name")))) {
                            return getFilterResult(exchange, getResponseBean(ErrorCode.API_NO_HEADER,method,header.getString("name")));
                        }
                    }
                }
            }

            //5、校验请求参数
            List<JSONObject> requestParamsJson = OpenUtils.getListJson(apiDocument.getQueryParams());
            //验证必填参数
            for(JSONObject param : requestParamsJson) {
                if(param.getBoolean("require")) {
                    if(StringUtils.isBlank(params.getFirst(param.getString("name")))) {
                        return getFilterResult(exchange,
                                getResponseBean(ErrorCode.METHOD_NO_REQUIRE_PARAM,method,param.getString("name")));
                    }
                }
            }

            //新请求
            ServerHttpRequest newRequest = mutate.build();
            return chain.filter(exchange.mutate().request(newRequest).build());
        }

        @Override
        public int getOrder() {
            return OPEN_APP_INFO_FILTER_ORDER;
        }
    }


    public static class Config{

    }
}

 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值