Springboot 实现api接口(二)

Springboot 实现api接口(二)

1、序言

思想:先给大家讲讲我们如何来实现接口加密,我们主要通过签名验证的方式来实现接口加密,前端给后端接口传参数时,把所有参数排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串,生成一个sign签名,后端写一个拦截器对其进行签名验证,后端接收到参数后,也通过同样的方法, 对其参数加密生成一个sign,两者相对比,如何相同则签名成功!

现在加密的方式有很多,比如国产哈希算法SM3等,自己根据安全要求合理选择。

2、加密示例

工具类

  • 主要实现参数去除空值和参数排序
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class SignatureUtil {

    /**
     * 除去数组中的空值、签名参数和Token
     *
     * @param params 签名参数组
     * @return 去掉空值与签名参数后的新签名参数组
     */
    public static Map<String, Object> parameterFilter(Map<String, Object> params) {

        Map<String, Object> result = new HashMap<String, Object>();

        if (params == null || params.size() <= 0) {
            return result;
        }

        for (String key : params.keySet()) {
            Object value = params.get(key);

            //如果取出来为数组,将数组转换为字符串
            if (value instanceof String[]) {
                value = ((String[]) value)[0];
            }

            if (value == null || value.equals("") || key.equalsIgnoreCase("sign")
                    || key.equalsIgnoreCase("token")) {
                continue;
            }
            result.put(key, value.toString());
        }

        return result;
    }

    /**
     * 把数组所有元素排序,并按照“参数=参数值”的模式用“&”字符拼接成字符串
     *
     * @param params 需要排序并参与字符拼接的参数组
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, Object> params) {

        List<String> keys = new ArrayList<String>(params.keySet());
        Collections.sort(keys);

        String prestr = "";

        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key).toString();

            if (i == keys.size() - 1) {//拼接时,不包括最后一个&字符
                prestr = prestr + key + "=" + value;
            } else {
                prestr = prestr + key + "=" + value + "&";
            }
        }

        return prestr;
    }

}
  • 进行md5加密
import java.security.MessageDigest;

public class MD5Util {
    /**
     * MD5普通加密
     *
     * @param rawPass 明文
     * @return
     */
    public static String encrypt(String rawPass) {
        return encrypt(rawPass, null);
    }

    /**
     * * MD5盐值加密
     *
     * @param rawPass 明文
     * @param salt    盐值
     * @return
     */
    public static String encrypt(String rawPass, Object salt) {
        String saltedPassword = mergePasswordAndSalt(rawPass, salt);
        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            byte[] digest = messageDigest.digest(saltedPassword.getBytes("UTF-8"));
            return new String(encode(digest));
        } catch (Exception e) {
            return rawPass;
        }
    }

    /**
     * 拼接密码与盐值
     *
     * @param password
     * @param salt
     * @return 密码{盐值}
     */
    private static String mergePasswordAndSalt(String password, Object salt) {
        if (salt == null || "".equals(salt.toString().trim())) {
            return password;
        }
        return password + "{" + salt.toString() + "}";
    }

    /**
     * encrypt
     *
     * @param bytes
     * @return
     */
    private static char[] encode(byte[] bytes) {
        char[] HEX = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
        int nBytes = bytes.length;
        char[] result = new char[2 * nBytes];
        int j = 0;
        for (byte aByte : bytes) {
            result[j++] = HEX[(0xF0 & aByte) >>> 4];
            result[j++] = HEX[(0x0F & aByte)];
        }
        return result;
    }

    /**
     * 校验明文加验证MD5后的值是否等于密文
     *
     * @param plainText  明文
     * @param salt       盐值
     * @param cipherText 密文
     * @return
     */
    public static boolean verify(String plainText, String salt, String cipherText) {
        return (MD5Util.encrypt(plainText, salt)).equals(cipherText);
    }

}

后端对接请求示例

	@Test
    public void testInsert() throws Exception {

        Map<String, Object> param = Maps.newHashMap();
        param.put("appid", "appid值");
        param.put("stamp", UUID.randomUUID().toString());
        param.put("参数1", "参数值1");
        param.put("参数2", "参数值2");
        String newSignString = SignatureUtil.createLinkString(SignatureUtil.parameterFilter(param)) + "&" + "key值";
        param.put("sign", MD5Util.encrypt(newSignString));
        String result = HttpUtil.post("请求地址", param);
        System.out.println(result);
    }

3、后端拦截器

拦截器

import com.cch.error.BaseBusinessException;
import com.cch.error.SignError;
import com.cch.util.MD5Util;
import com.cch.util.SignatureUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.yaml.snakeyaml.util.ArrayUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

/**
 * @Auther: cch
 * @Date: 2020/9/22 09:12
 * @Description: 签名拦截器
 */
@Component
public class SignatureInterceptor extends HandlerInterceptorAdapter  {

    private static final Logger logger = LoggerFactory.getLogger(SignatureInterceptor.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        logger.info("请求地址:" + request.getRequestURI());

        //验证签名
        String signStr = this.doAssembleSignString(request);
        if (logger.isDebugEnabled()) {
            logger.debug("signStr:" + signStr);
        }

        String sign = request.getParameter("sign");
        boolean result = MD5Util.verify(signStr, null, sign);

        if (logger.isDebugEnabled()) {
            logger.debug("sign result:" + result);
        }

        if (!result) {
            throw new BaseBusinessException(SignError.SIGNATURE_ERROR);
        }

        //replay防止重复调用
        //拿到标识后,先判断是否有值。没有值抛异常,有值验证同一时间是否已经存在,存在抛异常,不存在保存此值
        String stamp = request.getParameter("stamp");
        stampService.verifyApiStamp(stamp);

        return true;
    }

    /**
     * 拼接签名字符串
     *
     * @param request
     * @return
     */
    private String doAssembleSignString(HttpServletRequest request) {

        //获取待签名的字符串
        String newSignString = SignatureUtil.createLinkString(SignatureUtil.parameterFilter(getRequestData(request)));

        String appid = request.getParameter("appid");
        if(!"123".equals(appid)){
            throw new BaseBusinessException(SignError.SIGNATURE_ERROR);
        }
        newSignString = newSignString + "&" + "456";
        //从redis中获取用户Secret
//        App app = appService.getAppById(appid);
//        if (app == null) {
//            throw new BaseBusinessException(SignError.SIGNATURE_ERROR);
//        }
//        newSignString = newSignString + "&" + app.getKey();
        return newSignString;
    }


    private Map<String,Object> getRequestData(HttpServletRequest request) {
        Map<String,Object> map = new HashMap();
        Enumeration enumeration = request.getParameterNames();
        while (enumeration.hasMoreElements()) {
            String paramName = (String) enumeration.nextElement();
            String paramValue = request.getParameter(paramName);
            //形成键值对应的map
            map.put(paramName, paramValue);
        }
        return map;
    }

}

将自定义的拦截器进行注册

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.Arrays;
import java.util.List;

/**
 * @Auther: cch
 * @Date: 2020/9/22 10:29
 * @Description:
 */
@Configuration
public class WebAppConfigurer implements WebMvcConfigurer {
	//放行了接口文档的请求
    private static List<String> EXCLUDE_PATH = Arrays.asList("/image/**","/js/**","/webjars/**","/error/**","/favicon.ico","/swagger-ui/**","/v3/**","/swagger-resources/**");

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //添加拦截器的顺序就是拦截的顺序
        registry.addInterceptor(new SignatureInterceptor()).excludePathPatterns(EXCLUDE_PATH);
    }

}

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值