企业微信审批事件回调的安全验证与Java HMAC-SHA256校验实现

企业微信审批事件回调的安全验证与Java HMAC-SHA256校验实现

企业微信在审批状态变更时,会向配置的回调URL推送事件通知。为防止伪造请求,企业微信要求接收方必须校验请求头中的msg_signature,该签名基于tokentimestampnonce和消息体内容,使用HMAC-SHA256算法生成。若校验失败,应拒绝处理。本文基于wlkankan.cn.verify包,完整实现安全验证逻辑,并提供可复用的工具类。

企业微信签名生成规则

根据官方文档,msg_signature计算公式为:

复制代码
msg_signature = SHA1( token + timestamp + nonce + encrypted_msg )

但注意:实际使用的是 HMAC-SHA256 ,密钥为 token,数据为 timestamp + "\n" + nonce + "\n" + msg(含换行符)。这是常见误区。

签名验证工具类

java 复制代码
package wlkankan.cn.verify;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class WeComSignatureVerifier {

    public static boolean verify(String token, String timestamp, String nonce, String body, String signature) {
        if (token == null || timestamp == null || nonce == null || body == null || signature == null) {
            return false;
        }

        try {
            String expectedSignature = generateSignature(token, timestamp, nonce, body);
            return java.security.MessageDigest.isEqual(
                expectedSignature.getBytes(StandardCharsets.UTF_8),
                signature.getBytes(StandardCharsets.UTF_8)
            );
        } catch (Exception e) {
            return false;
        }
    }

    private static String generateSignature(String token, String timestamp, String nonce, String msg) 
            throws Exception {
        String data = String.join("\n", timestamp, nonce, msg);
        Mac mac = Mac.getInstance("HmacSHA256");
        SecretKeySpec secretKey = new SecretKeySpec(token.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
        mac.init(secretKey);
        byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
        return Base64.getEncoder().encodeToString(hash);
    }
}

Spring Boot控制器集成

wlkankan.cn.controller中接收回调并校验:

java 复制代码
package wlkankan.cn.controller;

import wlkankan.cn.verify.WeComSignatureVerifier;
import wlkankan.cn.service.ApprovalEventService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/wecom/callback")
public class ApprovalCallbackController {

    @Value("${wecom.callback.token}")
    private String callbackToken;

    private final ApprovalEventService eventService;

    public ApprovalCallbackController(ApprovalEventService eventService) {
        this.eventService = eventService;
    }

    @PostMapping("/approval")
    public String handleApprovalEvent(
            @RequestHeader("msg_signature") String msgSignature,
            @RequestParam("timestamp") String timestamp,
            @RequestParam("nonce") String nonce,
            @RequestBody String requestBody) {

        if (!WeComSignatureVerifier.verify(callbackToken, timestamp, nonce, requestBody, msgSignature)) {
            // 签名不合法,返回空或错误码(企业微信要求非200即重试)
            return ""; // 企业微信文档建议非法请求直接返回空
        }

        // 解析XML或JSON(企业微信审批事件为XML)
        ApprovalEvent event = parseApprovalEvent(requestBody);
        eventService.process(event);
        return "success"; // 必须返回"success"字符串
    }

    private ApprovalEvent parseApprovalEvent(String xml) {
        // 使用Dom4j或Jackson XML解析
        return XmlParser.parse(xml, ApprovalEvent.class);
    }
}

ApprovalEvent模型示例

java 复制代码
package wlkankan.cn.model;

import javax.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "xml")
public class ApprovalEvent {
    private String ToUserName;
    private String FromUserName;
    private Long CreateTime;
    private String MsgType;
    private String Event;
    private String SpNo; // 审批单号
    private String SpStatus; // APPROVED/REJECTED

    // getters/setters
    public String getSpNo() { return SpNo; }
    public String getSpStatus() { return SpStatus; }
}

安全增强:防重放攻击

即使签名正确,攻击者仍可能重放旧请求。需校验timestamp是否在合理窗口内(如5分钟):

java 复制代码
package wlkankan.cn.verify;

public class TimestampValidator {
    private static final long MAX_TIME_DIFF_MS = 5 * 60 * 1000; // 5分钟

    public static boolean isValidTimestamp(String timestampStr) {
        try {
            long timestamp = Long.parseLong(timestampStr) * 1000L; // 企业微信时间戳为秒
            long now = System.currentTimeMillis();
            return Math.abs(now - timestamp) <= MAX_TIME_DIFF_MS;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}

在控制器中增加校验:

java 复制代码
// 在 handleApprovalEvent 方法中
if (!TimestampValidator.isValidTimestamp(timestamp)) {
    return "";
}

配置与部署

application.yml中配置token:

yaml 复制代码
wecom:
  callback:
    token: "your_secure_token_here"

确保该token与企业微信管理后台【应用管理】->【接收消息】中配置的Token完全一致。

通过wlkankan.cn.verify模块实现的HMAC-SHA256校验与时间戳防重放机制,有效保障了企业微信审批回调接口的安全性,防止未授权调用与数据篡改,满足生产环境安全要求。

相关推荐
2501_944526421 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 收藏功能实现
android·java·开发语言·javascript·python·flutter·游戏
2501_944526421 小时前
Flutter for OpenHarmony 万能游戏库App实战 - 个人中心实现
android·java·javascript·python·flutter·游戏
乾元1 小时前
兵器谱——深度学习、强化学习与 NLP 在安全中的典型应用场景
运维·网络·人工智能·深度学习·安全·自然语言处理·自动化
上海云盾安全满满1 小时前
网络安全威胁是什么,类型有哪些
网络·安全·web安全
牧小七1 小时前
java8的新特性
java
Guheyunyi1 小时前
节能降耗管理系统:从静态优化到动态能源寻优的技术演进
大数据·人工智能·科技·安全·架构·能源
Jing_jing_X1 小时前
AI分析不同阶层思维 十:MQ从概念、底层原理到选型
java·架构·提升·薪资
Pth_you1 小时前
Uptime Kuma安装/定时通知脚本
linux·运维·安全
易晨 微盛·企微管家1 小时前
2025企业微信智能表格应用指南:从功能到场景
大数据·人工智能·企业微信