SpringBoot中拦截器的运用(验证某些请求的时间戳和RSA签名)

本文介绍了在SpringBoot中实现拦截器的方法。需继承WebMvcConfigurerAdapter类,实现相应抽象方法,将自定义拦截器注入bean容器并配置拦截请求。创建拦截器类实现HandlerInterceptor接口,进行RSA加解密验签和时间戳有效性校验。实现拦截器需完成创建拦截器类和重写addInterceptors方法两步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

   SpringBoot中实现拦截器,需要继承WebMvcConfigurerAdapter类,这个类中实现了webMvcConfig接口的抽象类,其次我们可以实现该类中相应的抽象方法。

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(paramsValidInterceptor())
                .addPathPatterns("/terminalCall/syncMachine")
                .addPathPatterns("/terminalCall/channelGoodsList");
    }
    /**
     * 注册对终端调用接口进行签名调用的验证拦截器
     * @return
     */
    @Bean
    public ParamsValidInterceptor paramsValidInterceptor(){
        return new ParamsValidInterceptor();
    }

  上述这段代码做了两件事情,第一,将我们自定义拦截器给注入到bean容器中进行管理,第二,将需要拦截的请求放入拦截器中,这样,我们只需要去实现其中的具体业务逻辑即可。

  • 创建自己的拦截器类并实现 HandlerInterceptor 接口
package com.youfuli.vendor.interceptor;


import com.youfuli.vendor.constant.Messages;
import com.youfuli.vendor.utils.RSA2;

import lombok.extern.slf4j.Slf4j;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.ResourceUtils;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.apache.commons.codec.binary.Base64;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;

import java.util.*;

/**
 * Created by 李啸天 on 2019/3/13.
 */
@Slf4j
@Component
public class ParamsValidInterceptor extends HandlerInterceptorAdapter {

    @Value("${rsa.pubKey}")
    String pubKey;

    @Value("${rsa.priKey}")
    String priKey;

    @Value("${rsa.pwd}")
    String pwd;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String times = (System.currentTimeMillis()/1000)+"";
        log.info("当前时间戳为{}",times);
        log.info("传入验证签名方法的路径为{}",request.getRequestURL());
        Map<String,String[]> maps = request.getParameterMap();
        //时间戳是否失效
            Assert.isTrue(checkTimeStamp(new Long(maps.get("timeStamp")[0])), Messages.TIMESPAN_INVALID);
        //签名数据
        String signStr = maps.get("sign")[0];
        log.info("传入签名数据为{}",signStr);
        //参与签名数据
        String returnString = key_sort(maps);
        log.info("传入参与签名数据为{}",returnString);
        //拿取公钥
        File pubfile = ResourceUtils.getFile(pubKey);
        String publicKey = txt2String(pubfile).replaceAll("(\\\r\\\n|\\\r|\\\n|\\\n\\\r)", "");

        //拿取私钥
        File prifile = ResourceUtils.getFile(priKey);
        String privateKey = txt2String(prifile).replaceAll("(\\\r\\\n|\\\r|\\\n|\\\n\\\r)", "");

        //对现有数据进行签名---本地签名校验
        byte [] signValidStr = RSA2.sign(returnString.getBytes(),privateKey);
        String signValid = Base64.encodeBase64String(signValidStr);
        boolean verfiy;
        if(signValid.equals(signStr)){
            verfiy = RSA2.verify(returnString.getBytes(),signValidStr,publicKey);
            log.info("当前验签结果为{}",verfiy);
        }else{
            verfiy=false;
        }
        if(verfiy==false){
            Assert.isTrue(verfiy,Messages.VALID_SIGN_ERROR);
        }
        return verfiy;
    }

    /**
    *排序
    **/
    public static String key_sort(Map<String, String[]> map) {
        String key_sort = "";

        TreeMap<String, String[]> map2 = new TreeMap<String, String[]>(new Comparator<String>() {
            public int compare(String obj1, String obj2) {
                // 降序排序
                return obj2.compareTo(obj1);
            }
        });
        map2 = new TreeMap<>(map);
        map2.remove("sign");

        Set<String> keySet = map2.keySet();
        Iterator<String> iter = keySet.iterator();
        while (iter.hasNext()) {
            String key = iter.next();
            key_sort = key_sort + key + "=" + map2.get(key)[0] + "&";
        }
        return key_sort.substring(0, key_sort.length() - 1);
    }

    /**
    *判断时间戳是否在一分钟内
    **/
    private static boolean checkTimeStamp(long ts)
    {
        if (Math.abs(ts - System.currentTimeMillis() / 1000) > 60)
        {
            return false;
        }
        return true;
    }

    /**
    *读取文本文件中的数据并输出为String
    **/
    public static String txt2String(File file){
        StringBuilder result = new StringBuilder();
        try{
            BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件
            String s = null;
            while((s = br.readLine())!=null){//使用readLine方法,一次读一行
                result.append(System.lineSeparator()+s);
            }
            br.close();
        }catch(Exception e){
            e.printStackTrace();
        }
        return result.toString();
    }

}

     因为把很多公共的代码给抽象出来,其中对传进来的参数进行排序后利用自身的RSA公私钥进行加解密验签操作,并且对时间戳进行了一分钟的有效性校验,校验成功则返回true继续执行下面的代码,否则返回false执行断言。

    简而言之,要实现拦截器功能需要完成2个步骤:

  • 创建自己的拦截器类并实现 HandlerInterceptor 接口
  • 重写WebMvcConfigurerAdapter类中的addInterceptors方法把自定义的拦截器类添加进来即可

   至于采用RSA加解密的规则,可以参考下面一段代码(另外一篇关于RSA加解密的博客大家也可以看看RSA证书加解密

package com.youfuli.vendor.utils;

import org.apache.commons.codec.binary.Base64;
import sun.misc.BASE64Decoder;
import sun.misc.BASE64Encoder;

import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by 李啸天 on 2019/3/13.
 */
public class RSA2 {
    public static final String KEY_ALGORITHM = "RSA";
    private static final String PUBLIC_KEY = "RSAPublicKey";
    private static final String PRIVATE_KEY = "RSAPrivateKey";
    public static final String SIGNATURE_ALGORITHM = "MD5withRSA";
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 2048;

    //获得公钥字符串
    public static String getPublicKeyStr(Map<String, Object> keyMap) throws Exception {
        //获得map中的公钥对象 转为key对象
        Key key = (Key) keyMap.get(PUBLIC_KEY);
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }


    //获得私钥字符串
    public static String getPrivateKeyStr(Map<String, Object> keyMap) throws Exception {
        //获得map中的私钥对象 转为key对象
        Key key = (Key) keyMap.get(PRIVATE_KEY);
        //编码返回字符串
        return encryptBASE64(key.getEncoded());
    }

    //获取公钥
    public static PublicKey getPublicKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PublicKey publicKey = keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    //获取私钥
    public static PrivateKey getPrivateKey(String key) throws Exception {
        byte[] keyBytes;
        keyBytes = (new BASE64Decoder()).decodeBuffer(key);
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
        PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    //解码返回byte
    public static byte[] decryptBASE64(String key) throws Exception {
        return (new BASE64Decoder()).decodeBuffer(key);
    }


    //编码返回字符串
    public static String encryptBASE64(byte[] key) throws Exception {
        return (new BASE64Encoder()).encodeBuffer(key);
    }

    //***************************签名和验证*******************************
    public static byte[] sign(byte[] data, String privateKeyStr) throws Exception {
        PrivateKey priK = getPrivateKey(privateKeyStr);
        Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initSign(priK);
        sig.update(data);
        return sig.sign();
    }

    public static boolean verify(byte[] data, byte[] sign, String publicKeyStr) throws Exception {
        PublicKey pubK = getPublicKey(publicKeyStr);
        Signature sig = Signature.getInstance(SIGNATURE_ALGORITHM);
        sig.initVerify(pubK);
        sig.update(data);
        return sig.verify(sign);
    }

    //************************加密解密**************************
    public static byte[] encrypt(byte[] plainText, String publicKeyStr) throws Exception {
        PublicKey publicKey = getPublicKey(publicKeyStr);
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        int inputLen = plainText.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        int i = 0;
        byte[] cache;
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
                cache = cipher.doFinal(plainText, offSet, MAX_ENCRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(plainText, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_ENCRYPT_BLOCK;
        }
        byte[] encryptText = out.toByteArray();
        out.close();
        return encryptText;
    }

    public static byte[] decrypt(byte[] encryptText, String privateKeyStr) throws Exception {
        PrivateKey privateKey = getPrivateKey(privateKeyStr);
        Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        int inputLen = encryptText.length;
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        int offSet = 0;
        byte[] cache;
        int i = 0;
        // 对数据分段解密
        while (inputLen - offSet > 0) {
            if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                cache = cipher.doFinal(encryptText, offSet, MAX_DECRYPT_BLOCK);
            } else {
                cache = cipher.doFinal(encryptText, offSet, inputLen - offSet);
            }
            out.write(cache, 0, cache.length);
            i++;
            offSet = i * MAX_DECRYPT_BLOCK;
        }
        byte[] plainText = out.toByteArray();
        out.close();
        return plainText;
    }


    public static void main(String[] args) {
        Map<String, Object> keyMap;
        byte[] cipherText;
        String input = "Hello World!";
        try {
            keyMap = initKey();
            String publicKey = getPublicKeyStr(keyMap);
            System.out.println("公钥------------------");
            System.out.println(publicKey);
            String privateKey = getPrivateKeyStr(keyMap);
            System.out.println("私钥------------------");
            System.out.println(privateKey);

            System.out.println("测试可行性-------------------");
            System.out.println("明文=======" + input);

            cipherText = encrypt(input.getBytes(), publicKey);
            //加密后的东西
            System.out.println("密文=======" + new String(cipherText));
            //开始解密
            byte[] plainText = decrypt(cipherText, privateKey);
            System.out.println("解密后明文===== " + new String(plainText));
            System.out.println("验证签名-----------");

            String str = "431f4dfd4c862122fb8a4e004c9a093b";
            System.out.println("\n原文:" + str);
            byte[] signature = sign(str.getBytes(), privateKey);
            System.out.println(Base64.encodeBase64String(signature));
            boolean status = verify(str.getBytes(), signature, publicKey);
            System.out.println("验证情况:" + status);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static Map<String, Object> initKey() throws Exception {
        KeyPairGenerator keyPairGen = KeyPairGenerator
                .getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(1024);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
    }

}

此次写篇博客记录一下,既是积累也是分享。

<think>好的,我现在需要在Spring Boot项目中实现类似V3Pay的微信支付模拟功能。首先,我需要理解微信支付V3的流程必要的步骤。根据引用[1]、[2]、[3]、[4],微信支付V3版本引入了新的签名方式证书管理,所以模拟功能需要处理这些部分。 首先,准备工作部分,可能需要配置商户信息、API密钥证书。引用[2]提到了准备工作,比如申请商户号设置APIv3密钥。模拟功能是否需要真实的商户信息呢?可能需要使用沙箱环境或者模拟数据。 接下来,签名生成。V3版本的签名使用SHA-256 with RSA,需要加载商户私钥并对请求头进行签名。引用[3][4]都提到了签名的重要性,所以代码中必须有签名生成的步骤。例如,如何构造Authorization头,包含时间戳、随机字符串、签名等信息。 然后,模拟下单接口。微信支付的下单接口是/v3/pay/transactions/jsapi,模拟的话可能需要创建一个本地的Controller来接收请求,处理参数并返回模拟的预支付ID或其他必要数据。引用[2][4]中的示例代码可能有用,比如使用RestTemplate或OkHttpClient发送请求,但模拟的话可能需要拦截这些请求并返回模拟响应。 证书管理方面,V3版本需要定期更新平台证书。模拟功能是否需要处理证书验证?可能可以跳过真实证书的验证,使用模拟的证书或者禁用证书验证,但需要注意安全风险。 另外,回调处理。微信支付成功后会有异步通知,模拟功能可能需要模拟这个回调,发送一个POST请求到应用的回调URL,并包含模拟的支付结果数据。这部分需要编写一个回调接口,并处理签名验证,但在模拟环境中可能简化签名验证步骤。 现在,我需要整合这些步骤,编写示例代码。可能需要以下部分: 1. 配置类:存放商户号、API密钥、证书路径等。 2. 签名工具类:生成Authorization头。 3. 模拟的支付控制器:处理下单请求,返回模拟预支付信息。 4. 回调控制器:接收并处理模拟的支付结果通知。 可能遇到的问题包括签名生成是否正确、如何模拟预支付ID、如何处理异步通知等。需要参考引用中的代码结构,比如引用[2]中的OkHttpUtil引用[4]中的WxPayConfig,但调整为模拟环境。 最后,测试整个流程,确保模拟的下单回调能够正常工作。可能需要使用Postman发送请求,检查返回的数据是否符合预期。 总结步骤: 1. 配置模拟商户信息。 2. 实现签名生成(模拟可能简化)。 3. 创建模拟下单接口,返回固定预支付ID。 4. 实现模拟回调,触发应用中的支付成功逻辑。 5. 处理可能的异常情况,如参数缺失、签名错误等。</think>### Spring Boot实现微信支付V3模拟功能示例 #### 1. 核心配置类 ```java @Configuration public class WxPayMockConfig { @Value("${wx.mock.mch-id}") private String mchId; @Value("${wx.mock.serial-no}") private String serialNo; @Bean public OkHttpClient wxMockClient() { return new OkHttpClient().newBuilder() .addInterceptor(new MockSignInterceptor()) // 模拟签名拦截器 .build(); } } ``` #### 2. 模拟签名生成工具 ```java public class MockSignUtil { public static String createSignature(String method, String url, String body) { // 模拟签名生成(实际环境需使用RSA签名) String signStr = method + "\n" + url + "\n" + System.currentTimeMillis() + "\n" + body + "\n"; return "mock_signature"; } } ``` #### 3. 模拟支付控制器 ```java @RestController @RequestMapping("/mock/pay") public class WxPayMockController { // 模拟预支付接口 @PostMapping("/v3/pay/transactions/jsapi") public Map<String, Object> createOrder(@RequestBody Map<String, Object> params) { return Map.of( "prepay_id", "mock_prepay_123456", "mchid", params.get("mchid"), "expire_time", LocalDateTime.now().plusHours(2).toString() ); } // 模拟支付结果回调 @PostMapping("/mock/notify") public String paymentNotify(HttpServletRequest request) { // 模拟处理回调逻辑 return "<xml><return_code>SUCCESS</return_code></xml>"; } } ``` #### 4. 支付服务封装 ```java @Service public class WxPayMockService { @Autowired private OkHttpClient wxMockClient; public String createJsapiOrder(Map<String, Object> orderParams) { // 构造模拟请求 Request request = new Request.Builder() .url("http://localhost:8080/mock/pay/v3/pay/transactions/jsapi") .post(RequestBody.create(MediaType.get("application/json"), JSON.toJSONString(orderParams))) .build(); try (Response response = wxMockClient.newCall(request).execute()) { return JSON.parseObject(response.body().string()).getString("prepay_id"); } } } ``` #### 5. 配置示例(application.yml) ```yaml wx: mock: mch-id: 1230000001 api-v3-key: mockapikey123456 serial-no: 1234567890ABCD notify-url: /api/pay/notify ``` ### 实现要点说明 1. **签名模拟**:实际环境需要实现微信V3的RSA-SHA256签名算法[^3],此处使用固定值简化 2. **证书管理**:真实环境需定时刷新平台证书[^4],模拟环境可跳过证书验证 3. **支付流程**: - 前端调起支付 -> 后端生成预支付ID - 模拟返回固定prepay_id - 通过定时任务模拟异步通知 ###
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值