前言:

  前段时间因为公司业务需求,需要将微信小程序与企业微信对接通,也就是把小程序绑定到对应的企业微信账号下,在该企业微信的用户可以将该小程序绑定到工作台中,然后可以在工作台中打开该小程序并授权。不过将微信小程序与企业微信对接通需要后台去做数据回调URL和指令回调URL验证,因为第一次接触这个然后企业微信文档写的也不是很详细,并且在全网没有找到一篇.NET相关企业微信回调配置验证有用的文章,所以这里把自己的配置详细过程分享出来,希望能够帮助更多的同学。

企业微信回调配置相关文档

回调配置:

主要讲的是回调配置的一些验证流程和请求接口。

https://work.weixin.qq.com/api/doc/90000/90135/90930

C#解密类库:

https://open.work.weixin.qq.com/wwopen/downloadfile/csharp.zip

企业微信回调配置验证完整流程

注意:配置回调服务时,需要能同时支持HttpGet以及HttpPost两种能力,注意接口一定要是https的安全域名地址。

  • HttpGet接口用于验证数据回调URL有效性
  • HttpPost接口用于验证指令回调URL有效性

所以我们可以只定义一个接口,通过企业微信请求过来的类型进行不同回调URL的有效性验证。

处理企业号的信息接口:

public class EnterpriseWechatCallbackController : Controller
{ //企业微信后台开发者设置的token, corpID, EncodingAESKey
private readonly string sToken = "追逐时光者";//企业微信后台,开发者设置的Token
private readonly string sCorpID = "追逐时光者";//企业号corpid是企业号的专属编号(CorpID)[不同场景含义不同,详见文档说明(ToUserName:企业微信的CorpID,当为第三方应用回调事件时,CorpID的内容为suiteid)]
private readonly string sEncodingAESKey = "追逐时光者";//企业微信后台,开发者设置的EncodingAESKey /// <summary>
/// 处理企业号的信息
/// get:数据回调URL验证;
/// post:指令回调URL验证;
/// </summary>
public ActionResult EtWechatCommunication()
{
string httpMethod = Request.HttpMethod.ToUpper(); if (httpMethod == "POST")
{
//获取请求中的xml数据
string postString = GetXMLParameters(Request); string responseContent = "响应失败,未获取到xml中的请求参数";
if (!string.IsNullOrEmpty(postString))
{
//指令响应回调
responseContent = CommandCallback(Request, postString);
} return Content(responseContent);
}
else
{
return EtWachatCheckVerifyURL();
}
} /// <summary>
/// 数据回调URL验证
/// </summary>
/// <returns></returns>
public ActionResult EtWachatCheckVerifyURL()
{
string signature = Request.QueryString["msg_signature"];//微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
string timestamp = Request.QueryString["timestamp"];//时间戳
string nonce = Request.QueryString["nonce"];//随机数
string httpMethod = Request.HttpMethod.ToUpper(); if (httpMethod == "GET")//验证回调URL(注意:企业回调的url-该url不做任何的业务逻辑,仅仅微信查看是否可以调通)
{
try
{
//在1秒内响应GET请求,响应内容为上一步得到的明文消息内容decryptEchoString(不能加引号,不能带bom头,不能带换行符)
string echostr = Request.QueryString["echostr"];//加密的随机字符串,以msg_encrypt格式提供。需要解密并返回echostr明文,解密后有random、msg_len、msg、$CorpID四个字段,其中msg即为echostr明文 if (!IsNullOrWhiteSpace(signature) && !IsNullOrWhiteSpace(timestamp) && !IsNullOrWhiteSpace(nonce) && !IsNullOrWhiteSpace(echostr))
{
string decryptEchoString = null;
if (CheckVerifyURL(sToken, signature, timestamp, nonce, sCorpID, sEncodingAESKey, echostr, ref decryptEchoString))
{
if (!string.IsNullOrEmpty(decryptEchoString))
{
//必须要返回解密之后的明文
return Content(decryptEchoString);
}
}
}
else
{
return Content("fail");
}
}
catch (Exception ex)
{
return Content("fail");
}
} return Content("fail");
} /// <summary>
/// 验证URL有效性
/// </summary>
/// <param name="token">企业微信后台,开发者设置的Token</param>
/// <param name="signature">签名串,对应URL参数的msg_signature</param>
/// <param name="timestamp">时间戳</param>
/// <param name="nonce">随机数</param>
/// <param name="corpId">ToUserName为企业号的CorpID</param>
/// <param name="encodingAESKey">企业微信后台,开发者设置的EncodingAESKey</param>
/// <param name="echostr">随机串,对应URL参数的echostr</param>
/// <param name="retEchostr">解密之后的echostr,当return返回0时有效</param>
/// <returns></returns>
private bool CheckVerifyURL(string token, string signature, string timestamp, string nonce, string corpId, string encodingAESKey, string echostr, ref string retEchostr)
{
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(token, encodingAESKey, corpId); int result = wxcpt.VerifyURL(signature, timestamp, nonce, echostr, ref retEchostr); if (result != 0)
{
return false;//FAIL
} //result==0表示验证成功、retEchostr参数表示明文
//用户需要将retEchostr作为get请求的返回参数、返回给企业微信号
return true;
} /// <summary>
/// 指令响应回调
/// </summary>
/// <param name="Request"></param>
/// <param name="postString">post请求的xml参数</param>
/// <returns></returns>
private string CommandCallback(HttpRequestBase Request, string postString)
{
string signature = Request.QueryString["msg_signature"];//微信加密签名,msg_signature结合了企业填写的token、请求中的timestamp、nonce参数、加密的消息体
string timestamp = Request.QueryString["timestamp"];//时间戳
string nonce = Request.QueryString["nonce"];//随机数
var xmlDoc = XDocument.Parse(postString);//xml数据转化 try
{
//https://work.weixin.qq.com/api/doc/90001/90143/90613
//在发生授权、通讯录变更、ticket变化等事件时,企业微信服务器会向应用的“指令回调URL”推送相应的事件消息。
//消息结构体将使用创建应用时的EncodingAESKey进行加密(特别注意, 在第三方回调事件中使用加解密算法,receiveid的内容为suiteid),请参考接收消息解析数据包。 本章节的回调事件,服务商在收到推送后都必须直接返回字符串 “success”,若返回值不是 “success”,企业微信会把返回内容当作错误信息。
if (xmlDoc.Root.Element("Encrypt") != null)
{
//将post请求的数据进行xml解析,并将<Encrypt> 标签的内容进行解密,解密出来的明文即是用户回复消息的明文
//接收并读取POST过来的XML文件流
string decryptionParame = null; // 解析之后的明文 // 注意注意:sCorpID
// @param sReceiveId: 不同场景含义不同,详见文档说明([消息加密时为 CorpId]ToUserName:企业微信的CorpID,当为第三方应用回调事件时,CorpID的内容为suiteid) WXBizMsgCrypt crypt = new WXBizMsgCrypt(sToken, sEncodingAESKey, xmlDoc.Root.Element("ToUserName").Value);
var result = crypt.DecryptMsg(signature, timestamp, nonce, postString, ref decryptionParame); if (result != 0)
{
return "fial";
} //响应应答处理
return new InstructionCallbackResponse().ReceiveResponse(decryptionParame, timestamp, signature,sToken, sEncodingAESKey, sCorpID);
}
}
catch (Exception ex)
{
//LoggerHelper._.Debug("异常:" + ex.Message);
} return "fail";
} /// <summary>
/// 验证是否为空
/// </summary>
/// <param name="strParame">验证参数</param>
/// <returns></returns>
private bool IsNullOrWhiteSpace(string strParame)
{
if (string.IsNullOrWhiteSpace(strParame))
{
return true;
}
else
{
return false;
}
} /// <summary>
/// 获取post请求中的xml参数
/// </summary>
/// <returns></returns>
private string GetXMLParameters(HttpRequestBase Request)
{
string replyMsg;
using (Stream stream = Request.InputStream)
{
Byte[] postBytes = new Byte[stream.Length];
stream.Read(postBytes, 0, (Int32)stream.Length);
replyMsg = Encoding.UTF8.GetString(postBytes);
}
return replyMsg;
} }

指令回调响应应答处理:

    /// <summary>
/// 指令回调响应应答处理
/// </summary>
public class InstructionCallbackResponse
{
/// <summary>
/// 响应应答处理
/// </summary>
/// <param name="sMsg">解密参数</param>
/// <param name="timestamp">时间戳</param>
/// <param name="signature">签名</param>
/// <param name="sToken">企业微信后台,开发者设置的Token</param>
/// <param name="sEncodingAESKey">开发者设置的EncodingAESKey</param>
/// <param name="sCorpID">业号corpid是企业号的专属编号(CorpID)</param>
/// <returns></returns>
public string ReceiveResponse(string sMsg, string timestamp, string signature, string sToken, string sEncodingAESKey, string sCorpID)
{
string responseMessage = "success";//响应内容
var xmlDoc = XDocument.Parse(sMsg);//xml数据转化 //区分普通消息与第三方应用授权推送消息,MsgType有值说明是普通消息,反之则是第三方应用授权推送消息
if (xmlDoc.Root.Element("MsgType") != null)
{
var msgType = (ResponseMsgType)Enum.Parse(typeof(ResponseMsgType), xmlDoc.Root.Element("MsgType").Value, true);
switch (msgType)
{
case ResponseMsgType.Text://文本消息
responseMessage = ResponseMessageText(xmlDoc, timestamp, signature,sToken,sEncodingAESKey,sCorpID);
break;
case ResponseMsgType.Image:
responseMessage = ResponseMessageImage();
break;
case ResponseMsgType.Voice:
responseMessage = ResponseMessageVoice();
break;
case ResponseMsgType.Video:
responseMessage = ResponseMessageVideo();
break;
case ResponseMsgType.News:
responseMessage = ResponseMessageNews();
break;
}
}
else if (xmlDoc.Root.Element("InfoType") != null)
{
//第三方回调
var infoType = (ResponseInfoType)Enum.Parse(typeof(ResponseInfoType), xmlDoc.Root.Element("InfoType").Value, true); switch (infoType)
{
case ResponseInfoType.suite_ticket:
{
//LoggerHelper._.Warn("suite_ticket===>>>>>,进来了,获取到的SuiteTicket票据为" + xmlDoc.Root.Element("SuiteTicket").Value);
}
break;
}
}
else
{
//其他情况
} // result==0表示解密成功,sMsg表示解密之后的明文xml串
//服务器未正确返回响应字符串 “success”
return responseMessage;
} #region 相关事件实现 /// <summary>
/// 消息文本回复
/// </summary>
/// <returns></returns>
public string ResponseMessageText(XDocument xmlDoc, string timestamp, string nonce,string sToken,string sEncodingAESKey,string sCorpID)
{
string FromUserName = xmlDoc.Root.Element("FromUserName").Value;
string ToUserName = xmlDoc.Root.Element("ToUserName").Value;
string Content = xmlDoc.Root.Element("Content").Value; string xml = "<xml>";
xml += "<ToUserName><![CDATA[" + ToUserName + "]]></ToUserName>";
xml += "<FromUserName><![CDATA[" + FromUserName + "]]></FromUserName>";
xml += "<CreateTime>" + GetCurrentTimeUnix() + "</CreateTime>";
xml += "<MsgType><![CDATA[text]]></MsgType>";
xml += "<Content><![CDATA[" + Content + "]]></Content>";
xml += "</xml>";
//"" + Content + "0";//回复内容 FuncFlag设置为1的时候,自动星标刚才接收到的消息,适合活动统计使用
WXBizMsgCrypt wxcpt = new WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
string sEncryptMsg = "";// 加密后的可以直接回复用户的密文;
wxcpt.EncryptMsg(xml, timestamp, nonce, ref sEncryptMsg); //返回
return sEncryptMsg;
} /// <summary>
/// 图片消息
/// </summary>
/// <returns></returns> public string ResponseMessageImage()
{
return "success";
} /// <summary>
/// 语音消息
/// </summary>
/// <returns></returns>
public string ResponseMessageVoice()
{
return "success";
} /// <summary>
/// 视频消息
/// </summary>
/// <returns></returns>
public string ResponseMessageVideo()
{
return "success";
} /// <summary>
/// 图文消息
/// </summary>
/// <returns></returns>
public string ResponseMessageNews()
{
return "success";
} #endregion /// <summary>
/// 获取当前时间戳
/// </summary>
/// <returns></returns>
public static string GetCurrentTimeUnix()
{
TimeSpan cha = (DateTime.Now - TimeZone.CurrentTimeZone.ToLocalTime(new System.DateTime(1970, 1, 1)));
long t = (long)cha.TotalSeconds;
return t.ToString();
}
}

消息类型枚举(enumType):

第三方应用授权推送消息类型(ResponseInfoType)

/// <summary>
/// 第三方应用授权推送消息类型
/// </summary>
public enum ResponseInfoType
{
/// <summary>
/// 推送suite_ticket 企业微信服务器会定时(每十分钟)推送ticket。ticket会实时变更,并用于后续接口的调用。
/// </summary>
suite_ticket =1, /// <summary>
/// 授权成功通知
/// </summary>
create_auth = 2, /// <summary>
/// 成员通知事件
/// </summary>
change_contact = 3
}

普通消息响应类型(ResponseMsgType)

/// <summary>
/// 普通消息响应类型
/// </summary>
public enum ResponseMsgType
{
/// <summary>
/// 文本消息
/// </summary>
Text = 0,
/// <summary>
/// 图文消息
/// </summary>
News = 1,
/// <summary>
/// 图片消息
/// </summary>
Image = 3,
/// <summary>
/// 语音消息
/// </summary>
Voice = 4,
/// <summary>
/// 视频消息
/// </summary>
Video = 5
}

C#解密类库

WXBizMsgCrypt

 public class WXBizMsgCrypt
{
string m_sToken;
string m_sEncodingAESKey;
string m_sReceiveId; //-40001 : 签名验证错误
//-40002 : xml解析失败
//-40003 : sha加密生成签名失败
//-40004 : AESKey 非法
//-40005 : corpid 校验错误
//-40006 : AES 加密失败
//-40007 : AES 解密失败
//-40008 : 解密后得到的buffer非法
//-40009 : base64加密异常
//-40010 : base64解密异常 enum WXBizMsgCryptErrorCode
{
WXBizMsgCrypt_OK = 0,
WXBizMsgCrypt_ValidateSignature_Error = -40001,
WXBizMsgCrypt_ParseXml_Error = -40002,
WXBizMsgCrypt_ComputeSignature_Error = -40003,
WXBizMsgCrypt_IllegalAesKey = -40004,
WXBizMsgCrypt_ValidateCorpid_Error = -40005,
WXBizMsgCrypt_EncryptAES_Error = -40006,
WXBizMsgCrypt_DecryptAES_Error = -40007,
WXBizMsgCrypt_IllegalBuffer = -40008,
WXBizMsgCrypt_EncodeBase64_Error = -40009,
WXBizMsgCrypt_DecodeBase64_Error = -40010
}; //构造函数
// @param sToken: 企业微信后台,开发者设置的Token
// @param sEncodingAESKey: 企业微信后台,开发者设置的EncodingAESKey
// @param sReceiveId: 不同场景含义不同,详见文档说明([消息加密时为 CorpId]ToUserName:企业微信的CorpID,当为第三方应用回调事件时,CorpID的内容为suiteid)
public WXBizMsgCrypt(string sToken, string sEncodingAESKey, string sReceiveId)
{
m_sToken = sToken;
m_sReceiveId = sReceiveId;
m_sEncodingAESKey = sEncodingAESKey;
} //验证URL
// @param sMsgSignature: 签名串,对应URL参数的msg_signature
// @param sTimeStamp: 时间戳,对应URL参数的timestamp
// @param sNonce: 随机串,对应URL参数的nonce
// @param sEchoStr: 随机串,对应URL参数的echostr
// @param sReplyEchoStr: 解密之后的echostr,当return返回0时有效
// @return:成功0,失败返回对应的错误码
public int VerifyURL(string sMsgSignature, string sTimeStamp, string sNonce, string sEchoStr, ref string sReplyEchoStr)
{
int ret = 0;
if (m_sEncodingAESKey.Length != 43)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
}
ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEchoStr, sMsgSignature);
if (0 != ret)
{
return ret;
}
sReplyEchoStr = "";
string cpid = "";
try
{
sReplyEchoStr = Cryptography.AES_decrypt(sEchoStr, m_sEncodingAESKey, ref cpid); //m_sReceiveId);
}
catch (Exception)
{
sReplyEchoStr = "";
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
}
if (cpid != m_sReceiveId)
{
sReplyEchoStr = "";
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
}
return 0;
} // 检验消息的真实性,并且获取解密后的明文
// @param sMsgSignature: 签名串,对应URL参数的msg_signature
// @param sTimeStamp: 时间戳,对应URL参数的timestamp
// @param sNonce: 随机串,对应URL参数的nonce
// @param sPostData: 密文,对应POST请求的数据
// @param sMsg: 解密后的原文,当return返回0时有效
// @return: 成功0,失败返回对应的错误码
public int DecryptMsg(string sMsgSignature, string sTimeStamp, string sNonce, string sPostData, ref string sMsg)
{
if (m_sEncodingAESKey.Length != 43)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
}
XmlDocument doc = new XmlDocument();
XmlNode root;
string sEncryptMsg;
try
{
doc.LoadXml(sPostData);
root = doc.FirstChild;
sEncryptMsg = root["Encrypt"].InnerText; LoggerHelper._.Info("进来解密了,解密密文sEncryptMsg为:" + sEncryptMsg);
}
catch (Exception)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ParseXml_Error;
}
//verify signature
int ret = 0;
ret = VerifySignature(m_sToken, sTimeStamp, sNonce, sEncryptMsg, sMsgSignature); LoggerHelper._.Info("进来解密了,解密结果:" + ret); if (ret != 0)
return ret;
//decrypt
string cpid = "";
try
{
sMsg = Cryptography.AES_decrypt(sEncryptMsg, m_sEncodingAESKey, ref cpid);
}
catch (FormatException)
{
sMsg = "";
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecodeBase64_Error;
}
catch (Exception)
{
sMsg = "";
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_DecryptAES_Error;
}
if (cpid != m_sReceiveId)
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateCorpid_Error;
return 0;
} //将企业号回复用户的消息加密打包
// @param sReplyMsg: 企业号待回复用户的消息,xml格式的字符串
// @param sTimeStamp: 时间戳,可以自己生成,也可以用URL参数的timestamp
// @param sNonce: 随机串,可以自己生成,也可以用URL参数的nonce
// @param sEncryptMsg: 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串,当return返回0时有效
// return:成功0,失败返回对应的错误码
public int EncryptMsg(string sReplyMsg, string sTimeStamp, string sNonce, ref string sEncryptMsg)
{
if (m_sEncodingAESKey.Length != 43)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_IllegalAesKey;
}
string raw = "";
try
{
raw = Cryptography.AES_encrypt(sReplyMsg, m_sEncodingAESKey, m_sReceiveId);
}
catch (Exception)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_EncryptAES_Error;
}
string MsgSigature = "";
int ret = 0;
ret = GenarateSinature(m_sToken, sTimeStamp, sNonce, raw, ref MsgSigature);
if (0 != ret)
return ret;
sEncryptMsg = ""; string EncryptLabelHead = "<Encrypt><![CDATA[";
string EncryptLabelTail = "]]></Encrypt>";
string MsgSigLabelHead = "<MsgSignature><![CDATA[";
string MsgSigLabelTail = "]]></MsgSignature>";
string TimeStampLabelHead = "<TimeStamp><![CDATA[";
string TimeStampLabelTail = "]]></TimeStamp>";
string NonceLabelHead = "<Nonce><![CDATA[";
string NonceLabelTail = "]]></Nonce>";
sEncryptMsg = sEncryptMsg + "<xml>" + EncryptLabelHead + raw + EncryptLabelTail;
sEncryptMsg = sEncryptMsg + MsgSigLabelHead + MsgSigature + MsgSigLabelTail;
sEncryptMsg = sEncryptMsg + TimeStampLabelHead + sTimeStamp + TimeStampLabelTail;
sEncryptMsg = sEncryptMsg + NonceLabelHead + sNonce + NonceLabelTail;
sEncryptMsg += "</xml>";
return 0;
} public class DictionarySort : System.Collections.IComparer
{
public int Compare(object oLeft, object oRight)
{
string sLeft = oLeft as string;
string sRight = oRight as string;
int iLeftLength = sLeft.Length;
int iRightLength = sRight.Length;
int index = 0;
while (index < iLeftLength && index < iRightLength)
{
if (sLeft[index] < sRight[index])
return -1;
else if (sLeft[index] > sRight[index])
return 1;
else
index++;
}
return iLeftLength - iRightLength; }
}
//Verify Signature
private static int VerifySignature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, string sSigture)
{
string hash = "";
int ret = 0;
ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
if (ret != 0)
return ret;
if (hash == sSigture)
return 0;
else
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ValidateSignature_Error;
}
} public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt, ref string sMsgSignature)
{
//校验规则:https://work.weixin.qq.com/api/doc#90000/90139/90968/%E6%B6%88%E6%81%AF%E4%BD%93%E7%AD%BE%E5%90%8D%E6%A0%A1%E9%AA%8C ArrayList AL = new ArrayList();
AL.Add(sToken);
AL.Add(sTimeStamp);
AL.Add(sNonce);
AL.Add(sMsgEncrypt);
AL.Sort(new DictionarySort());
string raw = "";
for (int i = 0; i < AL.Count; ++i)
{
raw += AL[i];
} SHA1 sha;
ASCIIEncoding enc;
string hash = "";
try
{
sha = new SHA1CryptoServiceProvider();
enc = new ASCIIEncoding();
byte[] dataToHash = enc.GetBytes(raw);
byte[] dataHashed = sha.ComputeHash(dataToHash);
hash = BitConverter.ToString(dataHashed).Replace("-", "");
hash = hash.ToLower();
}
catch (Exception)
{
return (int)WXBizMsgCryptErrorCode.WXBizMsgCrypt_ComputeSignature_Error;
}
sMsgSignature = hash;
return 0;
} }

Cryptography

    public class Cryptography
{
public static UInt32 HostToNetworkOrder(UInt32 inval)
{
UInt32 outval = 0;
for (int i = 0; i < 4; i++)
outval = (outval << 8) + ((inval >> (i * 8)) & 255);
return outval;
} public static Int32 HostToNetworkOrder(Int32 inval)
{
Int32 outval = 0;
for (int i = 0; i < 4; i++)
outval = (outval << 8) + ((inval >> (i * 8)) & 255);
return outval;
}
/// <summary>
/// 解密方法
/// </summary>
/// <param name="Input">密文</param>
/// <param name="EncodingAESKey"></param>
/// <returns></returns>
///
public static string AES_decrypt(String Input, string EncodingAESKey, ref string corpid)
{
byte[] Key;
Key = Convert.FromBase64String(EncodingAESKey + "=");
byte[] Iv = new byte[16];
Array.Copy(Key, Iv, 16);
byte[] btmpMsg = AES_decrypt(Input, Iv, Key); int len = BitConverter.ToInt32(btmpMsg, 16);
len = IPAddress.NetworkToHostOrder(len); byte[] bMsg = new byte[len];
byte[] bCorpid = new byte[btmpMsg.Length - 20 - len];
Array.Copy(btmpMsg, 20, bMsg, 0, len);
Array.Copy(btmpMsg, 20 + len, bCorpid, 0, btmpMsg.Length - 20 - len);
string oriMsg = Encoding.UTF8.GetString(bMsg);
corpid = Encoding.UTF8.GetString(bCorpid); return oriMsg;
} public static String AES_encrypt(String Input, string EncodingAESKey, string corpid)
{
byte[] Key;
Key = Convert.FromBase64String(EncodingAESKey + "=");
byte[] Iv = new byte[16];
Array.Copy(Key, Iv, 16);
string Randcode = CreateRandCode(16);
byte[] bRand = Encoding.UTF8.GetBytes(Randcode);
byte[] bCorpid = Encoding.UTF8.GetBytes(corpid);
byte[] btmpMsg = Encoding.UTF8.GetBytes(Input);
byte[] bMsgLen = BitConverter.GetBytes(HostToNetworkOrder(btmpMsg.Length));
byte[] bMsg = new byte[bRand.Length + bMsgLen.Length + bCorpid.Length + btmpMsg.Length]; Array.Copy(bRand, bMsg, bRand.Length);
Array.Copy(bMsgLen, 0, bMsg, bRand.Length, bMsgLen.Length);
Array.Copy(btmpMsg, 0, bMsg, bRand.Length + bMsgLen.Length, btmpMsg.Length);
Array.Copy(bCorpid, 0, bMsg, bRand.Length + bMsgLen.Length + btmpMsg.Length, bCorpid.Length); return AES_encrypt(bMsg, Iv, Key); }
private static string CreateRandCode(int codeLen)
{
string codeSerial = "2,3,4,5,6,7,a,c,d,e,f,h,i,j,k,m,n,p,r,s,t,A,C,D,E,F,G,H,J,K,M,N,P,Q,R,S,U,V,W,X,Y,Z";
if (codeLen == 0)
{
codeLen = 16;
}
string[] arr = codeSerial.Split(',');
string code = "";
int randValue = -1;
Random rand = new Random(unchecked((int)DateTime.Now.Ticks));
for (int i = 0; i < codeLen; i++)
{
randValue = rand.Next(0, arr.Length - 1);
code += arr[randValue];
}
return code;
} private static String AES_encrypt(String Input, byte[] Iv, byte[] Key)
{
//var aes = new RijndaelManaged();
#if NET45
var aes = new RijndaelManaged();
#else
var aes = Aes.Create();
#endif
//秘钥的大小,以位为单位
aes.KeySize = 256;
//支持的块大小
aes.BlockSize = 128;
//填充模式
aes.Padding = PaddingMode.PKCS7;
aes.Mode = CipherMode.CBC;
aes.Key = Key;
aes.IV = Iv;
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] xBuff = null; using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
{
byte[] xXml = Encoding.UTF8.GetBytes(Input);
cs.Write(xXml, 0, xXml.Length);
}
xBuff = ms.ToArray();
}
String Output = Convert.ToBase64String(xBuff);
return Output;
} private static String AES_encrypt(byte[] Input, byte[] Iv, byte[] Key)
{
//var aes = new RijndaelManaged();
#if NET45
var aes = new RijndaelManaged();
#else
var aes = Aes.Create();
#endif
//秘钥的大小,以位为单位
aes.KeySize = 256;
//支持的块大小
aes.BlockSize = 128;
//填充模式
//aes.Padding = PaddingMode.PKCS7;
aes.Padding = PaddingMode.None;
aes.Mode = CipherMode.CBC;
aes.Key = Key;
aes.IV = Iv;
var encrypt = aes.CreateEncryptor(aes.Key, aes.IV);
byte[] xBuff = null; #region 自己进行PKCS7补位,用系统自己带的不行
byte[] msg = new byte[Input.Length + 32 - Input.Length % 32];
Array.Copy(Input, msg, Input.Length);
byte[] pad = KCS7Encoder(Input.Length);
Array.Copy(pad, 0, msg, Input.Length, pad.Length);
#endregion #region 注释的也是一种方法,效果一样
//ICryptoTransform transform = aes.CreateEncryptor();
//byte[] xBuff = transform.TransformFinalBlock(msg, 0, msg.Length);
#endregion using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, encrypt, CryptoStreamMode.Write))
{
cs.Write(msg, 0, msg.Length);
}
xBuff = ms.ToArray();
} String Output = Convert.ToBase64String(xBuff);
return Output;
} private static byte[] KCS7Encoder(int text_length)
{
int block_size = 32;
// 计算需要填充的位数
int amount_to_pad = block_size - (text_length % block_size);
if (amount_to_pad == 0)
{
amount_to_pad = block_size;
}
// 获得补位所用的字符
char pad_chr = chr(amount_to_pad);
string tmp = "";
for (int index = 0; index < amount_to_pad; index++)
{
tmp += pad_chr;
}
return Encoding.UTF8.GetBytes(tmp);
}
/**
* 将数字转化成ASCII码对应的字符,用于对明文进行补码
*
* @param a 需要转化的数字
* @return 转化得到的字符
*/
static char chr(int a)
{ byte target = (byte)(a & 0xFF);
return (char)target;
}
private static byte[] AES_decrypt(String Input, byte[] Iv, byte[] Key)
{
//RijndaelManaged aes = new RijndaelManaged();
#if NET45
var aes = new RijndaelManaged();
#else
var aes = Aes.Create();
#endif
aes.KeySize = 256;
aes.BlockSize = 128;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.None;
aes.Key = Key;
aes.IV = Iv;
var decrypt = aes.CreateDecryptor(aes.Key, aes.IV);
byte[] xBuff = null;
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, decrypt, CryptoStreamMode.Write))
{
byte[] xXml = Convert.FromBase64String(Input);
byte[] msg = new byte[xXml.Length + 32 - xXml.Length % 32];
Array.Copy(xXml, msg, xXml.Length);
cs.Write(xXml, 0, xXml.Length);
}
xBuff = decode2(ms.ToArray());
}
return xBuff;
}
private static byte[] decode2(byte[] decrypted)
{
int pad = (int)decrypted[decrypted.Length - 1];
if (pad < 1 || pad > 32)
{
pad = 0;
}
byte[] res = new byte[decrypted.Length - pad];
Array.Copy(decrypted, 0, res, 0, decrypted.Length - pad);
return res;
}
}

企业微信后台验证回调URL有效性

【详细、开箱即用】.NET企业微信回调配置(数据回调URL和指令回调URL验证)的更多相关文章

  1. Java企业微信开发_07_总结一下企业微信的配置

    一.企业微信后台 1.回调url 2.可信域名 3.菜单跳转按钮中的链接 4.PC端网页授权 二.代码内 1.企业微信的配置信息:WeiXinParamesUtil

  2. zabbix企业微信告警配置教程

    前言: zabbix企业微信告警只需要配置一次就可以使用很久了,但是发现再次配置时,总会有遗忘,很麻烦又要去重新熟悉,所以,现在记录一份详细的配置过程,方便日后再次配置. 1.zabbix_serve ...

  3. 2.Vue 获取企业微信的Code并把Code发送的后台进行验证

    1 . 在企业微信配置请求的页面写入下面代码 mounted() { //获取微信请求的的Code let code = this.$route.query.code; if (code) { thi ...

  4. Zabbix 3.0 配置企业微信报警(配置zabbix-web)

    一.添加报警媒体类型 Name:自定义 Type:选择script Scripts name:填写脚本名称 Script parameters:脚本参数 --corpid=XXX --corpsecr ...

  5. .NET Core 企业微信回调配置

    1.配置API接收 2.下载加密解密库 地址:https://developer.work.weixin.qq.com/devtool/introduce?id=36388,也可以复制下面的代码 2. ...

  6. Java企业微信开发_09_身份验证之移动端网页授权(有完整项目源码)

    注: 源码已上传github: https://github.com/shirayner/WeiXin_QiYe_Demo 一.本节要点 1.1 授权回调域(可信域名) 在开始使用网页授权之前,需要先 ...

  7. 拉仇恨!webhook + 企业微信给同事做了个代码提交监听工具

    本文案例收录在 https://github.com/chengxy-nds/Springboot-Notebook 大家好,我是小富~ 最近接个任务,用webhook做了个代码提交监听功能,就是有人 ...

  8. Django + Taro 前后端分离项目实现企业微信登录

    前言 还是最近在做的一个小项目,后端用的是Django搭配RestFramework做接口,前端第一次尝试用京东开源的Taro框架来做多端(目前需要做用于企业微信的H5端和微信小程序) 本文记录一下企 ...

  9. Zabbix4.2.0使用Python连接企业微信报警

    目录 1. 配置企业微信 2. 脚本配置 2.1 安装python依赖的库 2.2 编写脚本 2. 搭建FTP 3. 配置Zabbix监控FTP 3.1 添加FTP模板 3.2 添加报警媒介 3.3 ...

随机推荐

  1. 机器学习 - k-means聚类

    k-means简介 k-means是无监督学习下的一种聚类算法,简单说就是不需要数据标签,仅靠特征值就可以将数据分为指定的几类.k-means算法的核心就是通过计算每个数据点与k个质心(或重心)之间的 ...

  2. 为什么大家都在用WebRTC?

    WebRTC代表网络实时通信.它是一种非常令人兴奋,强大且具有高度破坏性的尖端技术和标准.自从WebRTC诞生以来,80%的浏览器都开始支持它.有数据显示,2017年~2021年期间,WebRTC市场 ...

  3. 租了一台华为云耀云服务器,却直接被封公网ip,而且是官方的bug导致!

    小弟在博客园也有十多个年头了,因为离开了.net圈子,所以很少发文,今天可算是被华为云气疯了.写下这篇文章,大家也要注意自查一下,避免无端端被封公网ip. 因为小弟创业公司业务发展,需要一个公网做宣传 ...

  4. web知识架构思维导图

    图片双击放大还是很清晰的.原图大小5.1M

  5. C++ //运算符重载 +号

    1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 //1.成员函数重载 +号 6 cla ...

  6. [TensorFlow2.0]-Fashion-MNIST本地数据集及fit_generator()的使用

    本人人工智能初学者,现在在学习TensorFlow2.0,对一些学习内容做一下笔记.笔记中,有些内容理解可能较为肤浅.有偏差等,各位在阅读时如有发现问题,请评论或者邮箱(右侧边栏有邮箱地址)提醒. 若 ...

  7. elsa-core—2.Hello World: HTTP

    在本快速入门中,我们将了解一个执行工作流的最小 ASP.NET Core 应用程序.工作流将侦听传入的 HTTP 请求并写回一个简单的响应. 我们将: 创建 ASP.NET Core 应用程序. 使用 ...

  8. S3C2440—10.代码重定位

    文章目录 一.启动方式 1.1 NAND FLASH 启动 1.2 NOR FLASH 启动 二. 段的概念 2.1 重定位数据段 2.2 加载地址的引出 三.链接脚本 3.1 链接脚本的引入 3.2 ...

  9. 两个线程交叉打印一个打印A一个打印B 循环打印?

    public static Object obj1 = new Object(); public static void printAB(){ Thread t1 = new Thread(() -& ...

  10. SpringBoot返回枚举对象中的指定属性

    枚举 package com.meeno.boot.oa.employee.enums; import com.alibaba.fastjson.annotation.JSONType; import ...