filter实现接口验签流程+保证body请求体不丢失

本文详细介绍了如何通过过滤器实现接口验签流程,解决了从HttpServletRequest获取InputStream后导致的body丢失问题。通过HttpServletRequestWrapper包装inputStream,确保请求体可重复读取。同时,文中给出了验签的具体步骤,包括参数排序、MD5摘要计算以及与前端传递的签名比对。

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

接口验签流程

  1. 接收前端传来的入参,并根据入参的key通过ASCII码进行排序(放到treeMap即可)
  2. 将排序好的json字符创进行md5摘要。(将sign字段去除)
  3. 将排序好的字符串与前端传入的sign字段进行比对,如果正确,继续,错误返回错误信息

存在的问题(body丢失,所有post请求失效)

  1. 从HttpServletRequest获取的InputStream读一次就无法再读了
    解决办法:将inputStream通过 extends HttpServletRequestWrapper进行inputStream的包装,让inputStream从ByteArrayInputStream中进行流的读取,该对象可以从内存中进行数据的读取,不会读取一次之后就无法再读了。
    1.请求体存在流中,保证后面可以读取到请求体,需要复制流
    2.inputStream的读取方式为:会有一个pos指针,他指示每次读取之后下一次要读取的起始位置,当读到最后一个字符的时候,pos指针不会重置
    3.但不是所有的流都是这种读取方式,ByteArrayInputStream 就是从内存中读取byte[],每次读取都会重新遍历。就像遍历List时,会新建一个
    Iterator,所以pos也就回到了开始的位置。
  2. 未理解的点?
    为什么在复制流的时候,要从流中read出来,在write出去,而不能直接使用httpservlet.getInputStream。

代码实现过程

  1. 实现过滤器接口
  2. 重写doFilter方法
  3. 新建类继承HttpServletRequestWrapper,Stream.copyInputStream。copy出一个字节数组。重写getInputStream()和getReader(),改为内存读取流,保证body不丢失
  4. 获取url上和body中的参数,并通过treemap进行排序
  5. 对map中的数据进行除了sign字段的摘要
  6. 与前端传入的md5摘要进行比对

代码实现

@Slf4j
@WebFilter(urlPatterns = {
   "/user"})
public class SignAuthFilter implements Filter {
   

    @Override
    public void init(FilterConfig filterConfig) {
   
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
   

        HttpServletResponse response = (HttpServletResponse) res;
        // 防止流读取一次后就没有了, 所以需要将流继续写出去
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletRequest requestWrapper = new BodyReaderHttpServletRequestWrapper(request);

        //获取全部参数(包括URL和body上的)
        SortedMap<String, String> allParams = HttpUtils.getAllParams(requestWrapper);
        //获取时间为空
        String currentTime = allParams.get(Constant.System.SIGN_TIME);
        if (StringUtils.isBlank(currentTime)) {
   
            log.error("current_time字段为空,验签失败,param:{}", JSON.toJSONString(allParams));
            HttpUtils.setResponsemessage(response, "参数异常");
            return;
        }
        //对参数时间进行验证 超时
        boolean isTimeOut = SignUtil.verifyStayTime(currentTime);
        if (Boolean.FALSE.equals(isTimeOut)) {
   
            log.error("页面停留时间过长,请重新提交,param:{}", JSON.toJSONString(allParams));
            HttpUtils.setResponsemessage(response, "页面停留时间过长,请重新提交");
            return;
        }
        //对参数进行签名验证
        boolean isSigned = SignUtil.verifySign(allParams);
        if (isSigned) {
   
            chain.doFilter(requestWrapper, response);
        } else {
   
            log.error("验签失败,param:{}", JSON.toJSONString(allParams));
            HttpUtils.setResponsemessage(response, "参数异常");
        }
    }


    @Override
    public void 
<think>好的,用户现在问的是如何实现支付功能。之前他问的是关于数据大屏的设计,现在转向支付,可能是在做毕业设计或者一个完整的项目,需要整合多个模块。首先,我需要确定用户的具体需求,是个人项目还是商业级别的?因为不同的场景支付方案的选择会不同。比如,个人项目可能用第三方支付接口就够了,而商业项目需要考虑更高的安全性和合规性。 用户可能是一个学生,正在开发一个涉及预订或结算的系统,比如餐厅管理或者房间预订系统。支付功能是其中的关键部分,需要确保交易安全和用户体。接下来,我需要考虑用户可能需要的支付方式,比如支付宝、微信支付、银联等,这些在国内比较常用。同时,是否需要国际支付方式?可能暂时不需要,但可以提一下Stripe之类的。 然后,我需要考虑实现的步骤。首先选择支付渠道,然后设计订单系统,集成SDK,处理异步通知,考虑安全性,最后测试和上线。用户可能对技术细节不太清楚,需要给出具体的实现建议,比如使用支付宝的沙箱环境,或者微信支付的开发文档。 另外,用户可能关心如何保证支付的安全性,比如加密传输、防止重复支付、对账机制等。需要提醒用户注意这些方面,避免在项目中出现漏洞。还有是否需要分阶段实现?比如先模拟支付,再接入真实接口,这样可以逐步完善功能。 可能用户没有考虑到支付后的处理,比如订单状态更新,库存管理,或者发送通知。这部分也需要在支付流程中设计好。另外,是否需要退款功能?虽然用户没提到,但完整的支付系统应该包含退款流程。 还要考虑法律合规性,比如申请支付接口需要营业执照,个人可能无法直接申请,这时候可以用沙箱环境或者测试账号来模拟。同时,数据隐私保护也是重点,比如敏感信息的加密存储。 最后,测试环节很重要,用户可能需要知道如何模拟支付成功和失败的情况,如何调试回调接口。可以推荐一些工具,比如Postman测试回调,或者使用沙箱环境的各种测试用例。 总结来说,用户需要一个清晰、分步骤的实现指南,涵盖从选择支付渠道到测试上线的全过程,同时提醒关键的安全和合规问题,确保支付功能稳定可靠。</think>实现支付功能需要根据业务场景选择合适的技术方案,以下是分阶段的实现指南: --- ### **一、确定支付场景与需求** | **维度** | **选项** | **推荐方案(国内通用)** | |----------------|-----------------------------------|-------------------------------| | 用户端 | 网页/H5/小程序/APP | 根据产品形态选择SDK(如微信JSAPI/支付宝手机网站支付)| | 支付方式 | 支付宝/微信/银联/聚合支付(如Ping++)| 优先接入微信+支付宝(覆盖90%用户)| | 交易类型 | 即时到账/担保交易/分账系统 | 普通即时到账(简单场景) | | 合规要求 | 个人开发者/企业资质 | 企业需营业执照约,个人可用「当面付」等特殊接口| --- ### **二、核心实现步骤** #### **1. 支付渠道接入(以微信支付为例)** ```python # Python示例:统一下单接口(需替换为真实参数) import requests import hashlib import xml.etree.ElementTree as ET def create_order(amount, order_id): url = "https://api.mch.weixin.qq.com/pay/unifiedorder" params = { "appid": "你的APPID", "mch_id": "商户号", "nonce_str": hashlib.md5(str(time.time()).encode()).hexdigest(), "body": "商品描述", "out_trade_no": order_id, "total_fee": str(int(amount*100)), # 单位分 "spbill_create_ip": "用户IP", "notify_url": "https://你的域名/pay/notify", # 支付结果回调地址 "trade_type": "JSAPI" # 根据场景选择NATIVE/APP等 } # 生成名(需按微信规则排序并MD5加密) sign = generate_sign(params) params["sign"] = sign # 发送请求并解析返回的预付单信息 response = requests.post(url, data=dict_to_xml(params)) prepay_result = xml_to_dict(response.content) return prepay_result["prepay_id"] # 用于前端调起支付 ``` #### **2. 前端调起支付(Web示例)** ```javascript // 微信JSAPI支付调起 function onBridgeReady(prepayId){ WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId": "wx1234567890", "timeStamp": "1621234567", "nonceStr": "随机字符串", "package": "prepay_id=" + prepayId, "signType": "MD5", "paySign": "通过服务端计算的名" }, function(res){ if(res.err_msg == "get_brand_wcpay_request:ok") { // 支付成功逻辑 } } ); } ``` #### **3. 异步通知处理(关键!)** ```java // Java示例:支付宝异步通知 public boolean verifyNotification(Map<String,String> params) { String sign = params.get("sign"); String content = params.entrySet().stream() .filter(e -> !e.getKey().equals("sign") && !e.getKey().equals("sign_type")) .sorted(Map.Entry.comparingByKey()) .map(e -> e.getKey() + "=" + e.getValue()) .collect(Collectors.joining("&")); // 使用支付宝公钥名(需处理RSA2算法) PublicKey pubKey = getAlipayPublicKey(); Signature signature = Signature.getInstance("SHA256WithRSA"); signature.initVerify(pubKey); signature.update(content.getBytes(StandardCharsets.UTF_8)); return signature.verify(Base64.decodeBase64(sign)); } ``` --- ### **三、必须实现的防护机制** | **风险类型** | **解决方案** | **代码示例** | |------------------|-----------------------------------------|----------------------------------| | 重复支付 | 订单状态机校(创建→支付中→已支付) | SQL: `UPDATE orders SET status='paid' WHERE status='unpaid'` | | 网络劫持 | 前端支付参数名校 | 前端隐藏真实金额,后端二次证 | | 异步通知丢失 | 增加主动订单查询定时任务 | 使用Quartz调度每5分钟查询未确认订单 | | 金额篡改 | 服务端计算金额(拒绝前端传递的金额参数) | `total_fee = Order.getAmount()` | --- ### **四、企业级优化建议** #### **1. 支付路由策略** - **智能分流**:根据用户支付方式历史成功率自动选择渠道 - **灾备切换**:当支付宝接口超时,自动切换微信支付 #### **2. 监控体系** ```bash # 使用Prometheus监控支付成功率 rate(payment_requests_total{status="success"}[5m]) / rate(payment_requests_total[5m]) ``` #### **3. 合规性要求** - **资金存管**:企业账户需与银行/第三方支付机构约 - **敏感信息脱敏**:日志中自动过滤银行卡号(正则替换) --- ### **五、开发调试技巧** 1. **沙箱环境**: - 支付宝沙箱账号自动生成测试金额 - 微信支付V2/V3工具(官方提供名生成器) 2. **模拟支付工具**: ```bash # 使用curl测试回调接口 curl -X POST -d "notify_data=<XML数据>" https://你的回调URL ``` 3. **数据一致性检查**: ```sql -- 每日对账SQL(对比支付平台与本地订单) SELECT p.platform_order_id, o.amount FROM payment_records p LEFT JOIN orders o ON p.order_id = o.id WHERE p.amount != o.amount; ``` --- ### **六、技术选型对比** | **方案** | **优势** | **劣势** | **适用场景** | |-----------------|-------------------------|-----------------------------|----------------------| | 直接接入支付宝/微信 | 零手续费(资金直达商户) | 需分别对接各平台API | 有技术团队的企业 | | 聚合支付(Ping++) | 统一API接入所有渠道 | 额外0.3%-0.6%手续费 | 快速上线的创业项目 | | 银行直连 | 费率最低(约0.2%) | 需订线下协议,开发周期长 | 大型金融机构 | --- **总结**:支付实现的核心在于**安全性与异常处理**,务必做好: 1. **异步通知的幂等性处理** 2. **金额校的双端一致性** 3. **每日对账机制** 建议先用沙箱环境开发,再逐步接入真实支付渠道,同时结合业务需求添加退款、分账等扩展功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值