All Downloads are FREE. Search and download functionalities are using the official Maven repository.

top.jfunc.http.paramsign.AbstractParamSigner Maven / Gradle / Ivy

package top.jfunc.http.paramsign;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.jfunc.http.base.Method;
import top.jfunc.common.utils.MultiValueMap;
import top.jfunc.common.utils.StrUtil;

import java.io.IOException;

/**
 * 参数验签:包括时间戳和签名验证,参考微信支付宝的
 * https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3
 * https://opendocs.alipay.com/open/291/105974
 *
 * 1、分GET、POST|x-www-form-urlencoded、POST|json(代表有一个body的,其他的类型也如此)等
 * 2、对于前两者可以看做key-value键值对,加入时间戳ts、随机串noncestr,按照字典序排序后md5
 * 3、后者对整个body进行md5,并用param作为key,加入时间戳ts、随机串noncestr,按照字典序排序后md5
 * 4、md5后的值作为签名传递,同时传递ts、noncestr,原参数保持不变
 * 5、后端校验的时候首先校验时间戳ts,后再校验签名
 *
 * @author xiongshiyan at 2020/7/20 , contact me with email [email protected] or phone 15208384257
 */
public abstract class AbstractParamSigner implements ParamSigner {
    private static final Logger logger     = LoggerFactory.getLogger(AbstractParamSigner.class);

    @Override
    public void validIfNecessary(R r) throws IOException{
        SignParam signParam = checkSignParam(r);
        if(null == signParam){
            return;
        }
        valid(r , signParam);
    }

    protected void valid(R r , SignParam signParam) throws IOException {
        //校验是否有必要的参数
        validHasParam(r, signParam);

        //校验时间戳,目前要求以服务器时间前后60秒
        validTimeStamp(r , signParam);

        //校验请求参数
        validRequest(r, signParam);
    }

    protected void validHasParam(R r , SignParam signParam) {
        if (StrUtil.isEmpty(signParam.getTimeStamp())
                || StrUtil.isEmpty(signParam.getNonceStr())
                || StrUtil.isEmpty(signParam.getSign())) {
            logger.info("参数为空:" + signParam.getPath());
            throw new ParamSignException("参数异常,请升级APP重试" , signParam);
        }
    }

    protected void validTimeStamp(R r , SignParam signParam) {
        String ts = signParam.getTimeStamp();
        long timestamp = Long.parseLong(ts);
        long now = System.currentTimeMillis();
        //if(ts >= now || now - ts > PARAM_SIGN_INTERVAL){
        //因为客户端时间可能不准,给一个裕量
        long diff = now - timestamp;
        long abs = Math.abs(diff);
        if (abs > getParamSignInterval()) {
            logger.info(signParam.getPath() + ":" + now + "-" + ts + "=" + diff);
            throw new ParamSignException("时间校验失败:您的手机时间大约"+(diff>0?"慢":"快")+"了"  + abs/1000 + "秒,请调整后重试;如果手机时间准确,可能是网络延迟太大,请稍后重试。" , signParam);
        }
    }

    protected void validRequest(R r, SignParam signParam) throws IOException{
        //校验GET请求,Query参数参与签名
        validGet(r, signParam);

        //校验POST请求,分Form和其他的含有body的(主要是json),Form所有参数参与签名,body类型的使用param=md5(body)参与签名
        validPost(r, signParam);
    }

    protected void validGet(R r, SignParam signParam) throws IOException{
        if (Method.GET.name().equalsIgnoreCase(signParam.getMethod())) {
            MultiValueMap paramMap = mappedParamForGet(r);
            validParam(paramMap, signParam);
        }
    }

    protected void validPost(R r, SignParam signParam) throws IOException {
        if (Method.POST.name().equalsIgnoreCase(signParam.getMethod())) {
            MultiValueMap paramMap = mappedParamForPost(r);
            validParam(paramMap, signParam);
        }
    }


    protected void validParam(MultiValueMap paramMap, SignParam signParam) {
        MultiValueMap handleMap = handleMap(paramMap, signParam);

        String signStr = getSignStr(handleMap, signParam);

        String signToJudge = doSign(signStr, signParam);

        if (!signToJudge.equals(signParam.getSign())) {
            logger.info(signParam.getPath() + ":" + signStr + " -> " + signToJudge + " ?= " + signParam.getSign());
            throw new ParamSignException("参数签名异常,请升级APP重试" , signParam);
        }
    }

    /**可以进一步对map进行处理,比如把secret放进去一起排序*/
    protected MultiValueMap handleMap(MultiValueMap paramMap, SignParam signParam){
        return ParamSignUtil.handleMap(paramMap, signParam.getTimeStamp(), signParam.getNonceStr());
    }
    protected String getSignStr(MultiValueMap paramMap, SignParam signParam) {
        return ParamSignUtil.getSignStr(paramMap);
    }

    /**
     * 获取请求用于签名的参数,可以放在header中,可以拼成一个字段放在header中,同样可以放在Query中
     * @param r 代表请求
     * @return SignParam 用于参数签名的参数。如果此方法返回null,则不进行参数签名,可以在此方法中判断是否需要参数签名
     * @throws IOException IOException
     */
    protected abstract SignParam checkSignParam(R r) throws IOException;
    /**
     * 时间戳裕量
     */
    protected long getParamSignInterval(){return 60000;}
    /**
     * 将post请求的body组装成一个map,一般分为POST|x-www-form-urlencoded、POST|json
     * @param r 代表请求
     * @return MultiValueMap POST参数map
     * @throws IOException IOException
     */
    protected abstract MultiValueMap mappedParamForPost(R r) throws IOException;
    /**
     * 将get请求的query组装成一个map
     * @param r 代表请求
     * @return MultiValueMap GET参数map
     * @throws IOException IOException
     */
    protected abstract MultiValueMap mappedParamForGet(R r) throws IOException;
    /**
     * 签名方法,具体的签名方法
     */
    protected abstract String doSign(String toSign, SignParam signParam);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy