微信开发 企业号(二)-- 回调模式之Tooken验证 .net/python

在企业号开发者中心中,有加密解密源代码,供给开发者使用。(加解密库下载

由于官方只提供了python2.*的类库,使用python3.*的朋友可以再最后下载我修改后的py文件(仅修改验证Tooken代码)。

加解密库分析

一、需要用到的几个数据

在企业号中配置/获取到的数据

string sToken = "QDG6eK";
string sCorpID = "wx5823bf96d3bd56c7";
string sEncodingAESKey = "jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C";

通过URL中获取到的参数

 // string sVerifyMsgSig = Request("msg_signature");
string sVerifyMsgSig = "5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3";
// string sVerifyTimeStamp = Request("timestamp");
string sVerifyTimeStamp = "1409659589";
// string sVerifyNonce = Request("nonce");
string sVerifyNonce = "263014780";
// string sVerifyEchoStr = Request("echostr");
string sVerifyEchoStr = "P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==";

例如:

http://127.0.0.1/?msg_signature=5c45ff5e21c57e6ad56bac8758b79b1d9ac89fd3&timestamp=1409659589&nonce=263014780&echostr=P9nAzCzyDtyTWESHep1vC5X9xho/qYX3Zpb4yKa9SKld1DsH3Iyt3tP3zNdtp+4RPcs8TgAE7OaBO+FZXvnaqQ==

二、环境搭建

1、C#

  只需要在官网下载C#库后将CS文件复制到工程,或编译后引用DLL文件即可。

2、Python

  需要pycrypto第三方库。

I、linux

  只需要运行:pip install pycrypto安装即可

II、windows

  安装比较麻烦,网上有说pip install pycrypto、easy_install pycrypto。

  由于我电脑没安装VS2008,安装的是VS2013。按照网上各种方法都无法对下载的包进行编译。

  最后找到了编译好的exe文件,直接安装即可http://www.voidspace.org.uk/python/modules.shtml#pycrypto

三、修改地方

1、C#

  无修改

2、Python3.*

  删除

reload(sys)

sys.setdefaultencoding(‘utf-8‘) 
将所有
try:
except Exception,e:
    print e

修改为
try:
except Exception as e:
    print(e)
第51行
sha.update("".join(sortlist))
改为
sha.update("".join(sortlist).encode("ascii"))
第174行
pad = ord(plain_text[-1])
改为
pad = plain_text[-1]
第182行
from_corpid = content[xml_len+4:]
改为
from_corpid = content[xml_len+4:].decode("utf8") 

四、分析

1、实例化

//C#
Tencent.WXBizMsgCrypt wxcpt = new Tencent.WXBizMsgCrypt(sToken, sEncodingAESKey, sCorpID);
#Python
wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID)

2、调用URL验证接口

//c#
int ret = 0;
string sEchoStr = "";
ret = wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp, sVerifyNonce, sVerifyEchoStr, ref sEchoStr);
#Pythonret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr)

3、signature验证

I、将token, timestamp, nonce, encrypt的内容按照大小字母顺序排列

II、按顺序将列表中排序号的内容拼接成一个字符串,并对其进行ASCII转码

III、对ASCII转码后的数组做SHA1加密生成 signature

IV、生成的signature和URL中获取到的sMsgSignature进行比对,如果一致则继续,否则返回错误。

//C#public static int GenarateSinature(string sToken, string sTimeStamp, string sNonce, string sMsgEncrypt ,ref string sMsgSignature)
        {
            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;
        }

 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;

            }
        }

//调用
 string hash = "";
            int ret = 0;
            ret = GenarateSinature(sToken, sTimeStamp, sNonce, sMsgEncrypt, ref hash);
            if (ret != 0)
                return ret;
            if (hash == sSigture)
                return 0;
#Python
  def getSHA1(self, token, timestamp, nonce, encrypt):
        """用SHA1算法生成安全签名
        @param token:  票据
        @param timestamp: 时间戳
        @param encrypt: 密文
        @param nonce: 随机字符串
        @return: 安全签名
        """
        try:
            sortlist = [token, timestamp, nonce, encrypt]
            sortlist.sort()
            sha = hashlib.sha1()
            sha.update("".join(sortlist).encode("ascii"))
            return  ierror.WXBizMsgCrypt_OK, sha.hexdigest()
        except Exception as e:
            print(e)
            return  ierror.WXBizMsgCrypt_ComputeSignature_Error, None

4、解密

I、在sEncodingAESKey接入加入等号(“=”)

 sEncodingAESKey = sEncodingAESKey+"="

II、再用Base64对其进行编码

byte[] Key;
Key = Convert.FromBase64String(EncodingAESKey + "=");
Key = base64.b64decode(sEncodingAESKey+"=")

III、根据Key生成AES加密所需要的偏移量IV

//C#byte[] Iv = new byte[16];
Array.Copy(Key, Iv, 16);
#python
Iv = Key[:16]

IV、解密方法

private static byte[] AES_decrypt(String Input, byte[] Iv, byte[] Key)
        {
            RijndaelManaged aes = new RijndaelManaged();
            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;
        }

# -*- coding: utf-8 -*-
#
#  Cipher/blockalgo.py
#
# ===================================================================
# The contents of this file are dedicated to the public domain.  To
# the extent that dedication to the public domain is not available,
# everyone is granted a worldwide, perpetual, royalty-free,
# non-exclusive license to exercise all rights associated with the
# contents of this file for any purpose whatsoever.
# No rights are reserved.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# ===================================================================
"""Module with definitions common to all block ciphers."""

import sys
if sys.version_info[0] == 2 and sys.version_info[1] == 1:
    from Crypto.Util.py21compat import *
from Crypto.Util.py3compat import *

#: *Electronic Code Book (ECB)*.
#: This is the simplest encryption mode. Each of the plaintext blocks
#: is directly encrypted into a ciphertext block, independently of
#: any other block. This mode exposes frequency of symbols
#: in your plaintext. Other modes (e.g. *CBC*) should be used instead.
#:
#: See `NIST SP800-38A`_ , Section 6.1 .
#:
#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
MODE_ECB = 1

#: *Cipher-Block Chaining (CBC)*. Each of the ciphertext blocks depends
#: on the current and all previous plaintext blocks. An Initialization Vector
#: (*IV*) is required.
#:
#: The *IV* is a data block to be transmitted to the receiver.
#: The *IV* can be made public, but it must be authenticated by the receiver and
#: it should be picked randomly.
#:
#: See `NIST SP800-38A`_ , Section 6.2 .
#:
#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
MODE_CBC = 2

#: *Cipher FeedBack (CFB)*. This mode is similar to CBC, but it transforms
#: the underlying block cipher into a stream cipher. Plaintext and ciphertext
#: are processed in *segments* of **s** bits. The mode is therefore sometimes
#: labelled **s**-bit CFB. An Initialization Vector (*IV*) is required.
#:
#: When encrypting, each ciphertext segment contributes to the encryption of
#: the next plaintext segment.
#:
#: This *IV* is a data block to be transmitted to the receiver.
#: The *IV* can be made public, but it should be picked randomly.
#: Reusing the same *IV* for encryptions done with the same key lead to
#: catastrophic cryptographic failures.
#:
#: See `NIST SP800-38A`_ , Section 6.3 .
#:
#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
MODE_CFB = 3

#: This mode should not be used.
MODE_PGP = 4

#: *Output FeedBack (OFB)*. This mode is very similar to CBC, but it
#: transforms the underlying block cipher into a stream cipher.
#: The keystream is the iterated block encryption of an Initialization Vector (*IV*).
#:
#: The *IV* is a data block to be transmitted to the receiver.
#: The *IV* can be made public, but it should be picked randomly.
#:
#: Reusing the same *IV* for encryptions done with the same key lead to
#: catastrophic cryptograhic failures.
#:
#: See `NIST SP800-38A`_ , Section 6.4 .
#:
#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
MODE_OFB = 5

#: *CounTeR (CTR)*. This mode is very similar to ECB, in that
#: encryption of one block is done independently of all other blocks.
#: Unlike ECB, the block *position* contributes to the encryption and no
#: information leaks about symbol frequency.
#:
#: Each message block is associated to a *counter* which must be unique
#: across all messages that get encrypted with the same key (not just within
#: the same message). The counter is as big as the block size.
#:
#: Counters can be generated in several ways. The most straightword one is
#: to choose an *initial counter block* (which can be made public, similarly
#: to the *IV* for the other modes) and increment its lowest **m** bits by
#: one (modulo *2^m*) for each block. In most cases, **m** is chosen to be half
#: the block size.
#:
#: Reusing the same *initial counter block* for encryptions done with the same
#: key lead to catastrophic cryptograhic failures.
#:
#: See `NIST SP800-38A`_ , Section 6.5 (for the mode) and Appendix B (for how
#: to manage the *initial counter block*).
#:
#: .. _`NIST SP800-38A` : http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
MODE_CTR = 6

#: OpenPGP. This mode is a variant of CFB, and it is only used in PGP and OpenPGP_ applications.
#: An Initialization Vector (*IV*) is required.
#:
#: Unlike CFB, the IV is not transmitted to the receiver. Instead, the *encrypted* IV is.
#: The IV is a random data block. Two of its bytes are duplicated to act as a checksum
#: for the correctness of the key. The encrypted IV is therefore 2 bytes longer than
#: the clean IV.
#:
#: .. _OpenPGP: http://tools.ietf.org/html/rfc4880
MODE_OPENPGP = 7

def _getParameter(name, index, args, kwargs, default=None):
    """Find a parameter in tuple and dictionary arguments a function receives"""
    param = kwargs.get(name)
    if len(args)>index:
        if param:
            raise ValueError("Parameter ‘%s‘ is specified twice" % name)
        param = args[index]
    return param or default

class BlockAlgo:
    """Class modelling an abstract block cipher."""

    def __init__(self, factory, key, *args, **kwargs):
        self.mode = _getParameter(‘mode‘, 0, args, kwargs, default=MODE_ECB)
        self.block_size = factory.block_size

        if self.mode != MODE_OPENPGP:
            self._cipher = factory.new(key, *args, **kwargs)
            self.IV = self._cipher.IV
        else:
            # OPENPGP mode. For details, see 13.9 in RCC4880.
            #
            # A few members are specifically created for this mode:
            #  - _encrypted_iv, set in this constructor
            #  - _done_first_block, set to True after the first encryption
            #  - _done_last_block, set to True after a partial block is processed

            self._done_first_block = False
            self._done_last_block = False
            self.IV = _getParameter(‘iv‘, 1, args, kwargs)
            if not self.IV:
                raise ValueError("MODE_OPENPGP requires an IV")

            # Instantiate a temporary cipher to process the IV
            IV_cipher = factory.new(key, MODE_CFB,
                    b(‘\x00‘)*self.block_size,      # IV for CFB
                    segment_size=self.block_size*8)

            # The cipher will be used for...
            if len(self.IV) == self.block_size:
                # ... encryption
                self._encrypted_IV = IV_cipher.encrypt(
                    self.IV + self.IV[-2:] +        # Plaintext
                    b(‘\x00‘)*(self.block_size-2)   # Padding
                    )[:self.block_size+2]
            elif len(self.IV) == self.block_size+2:
                # ... decryption
                self._encrypted_IV = self.IV
                self.IV = IV_cipher.decrypt(self.IV +   # Ciphertext
                    b(‘\x00‘)*(self.block_size-2)       # Padding
                    )[:self.block_size+2]
                if self.IV[-2:] != self.IV[-4:-2]:
                    raise ValueError("Failed integrity check for OPENPGP IV")
                self.IV = self.IV[:-2]
            else:
                raise ValueError("Length of IV must be %d or %d bytes for MODE_OPENPGP"
                    % (self.block_size, self.block_size+2))

            # Instantiate the cipher for the real PGP data
            self._cipher = factory.new(key, MODE_CFB,
                self._encrypted_IV[-self.block_size:],
                segment_size=self.block_size*8)

    def encrypt(self, plaintext):
        """Encrypt data with the key and the parameters set at initialization.

        The cipher object is stateful; encryption of a long block
        of data can be broken up in two or more calls to `encrypt()`.
        That is, the statement:

            >>> c.encrypt(a) + c.encrypt(b)

        is always equivalent to:

             >>> c.encrypt(a+b)

        That also means that you cannot reuse an object for encrypting
        or decrypting other data with the same key.

        This function does not perform any padding.

         - For `MODE_ECB`, `MODE_CBC`, and `MODE_OFB`, *plaintext* length
           (in bytes) must be a multiple of *block_size*.

         - For `MODE_CFB`, *plaintext* length (in bytes) must be a multiple
           of *segment_size*/8.

         - For `MODE_CTR`, *plaintext* can be of any length.

         - For `MODE_OPENPGP`, *plaintext* must be a multiple of *block_size*,
           unless it is the last chunk of the message.

        :Parameters:
          plaintext : byte string
            The piece of data to encrypt.
        :Return:
            the encrypted data, as a byte string. It is as long as
            *plaintext* with one exception: when encrypting the first message
            chunk with `MODE_OPENPGP`, the encypted IV is prepended to the
            returned ciphertext.
        """

        if self.mode == MODE_OPENPGP:
            padding_length = (self.block_size - len(plaintext) % self.block_size) % self.block_size
            if padding_length>0:
                # CFB mode requires ciphertext to have length multiple of block size,
                # but PGP mode allows the last block to be shorter
                if self._done_last_block:
                    raise ValueError("Only the last chunk is allowed to have length not multiple of %d bytes",
                        self.block_size)
                self._done_last_block = True
                padded = plaintext + b(‘\x00‘)*padding_length
                res = self._cipher.encrypt(padded)[:len(plaintext)]
            else:
                res = self._cipher.encrypt(plaintext)
            if not self._done_first_block:
                res = self._encrypted_IV + res
                self._done_first_block = True
            return res

        return self._cipher.encrypt(plaintext)

    def decrypt(self, ciphertext):
        """Decrypt data with the key and the parameters set at initialization.

        The cipher object is stateful; decryption of a long block
        of data can be broken up in two or more calls to `decrypt()`.
        That is, the statement:

            >>> c.decrypt(a) + c.decrypt(b)

        is always equivalent to:

             >>> c.decrypt(a+b)

        That also means that you cannot reuse an object for encrypting
        or decrypting other data with the same key.

        This function does not perform any padding.

         - For `MODE_ECB`, `MODE_CBC`, and `MODE_OFB`, *ciphertext* length
           (in bytes) must be a multiple of *block_size*.

         - For `MODE_CFB`, *ciphertext* length (in bytes) must be a multiple
           of *segment_size*/8.

         - For `MODE_CTR`, *ciphertext* can be of any length.

         - For `MODE_OPENPGP`, *plaintext* must be a multiple of *block_size*,
           unless it is the last chunk of the message.

        :Parameters:
          ciphertext : byte string
            The piece of data to decrypt.
        :Return: the decrypted data (byte string, as long as *ciphertext*).
        """
        if self.mode == MODE_OPENPGP:
            padding_length = (self.block_size - len(ciphertext) % self.block_size) % self.block_size
            if padding_length>0:
                # CFB mode requires ciphertext to have length multiple of block size,
                # but PGP mode allows the last block to be shorter
                if self._done_last_block:
                    raise ValueError("Only the last chunk is allowed to have length not multiple of %d bytes",
                        self.block_size)
                self._done_last_block = True
                padded = ciphertext + b(‘\x00‘)*padding_length
                res = self._cipher.decrypt(padded)[:len(ciphertext)]
            else:
                res = self._cipher.decrypt(ciphertext)
            return res

        return self._cipher.decrypt(ciphertext)

Python AES 解密,已存在与blockalgo.py文件中

V、AES解密,并对解密后的数据进行拆分

//c#
byte[] btmpMsg = AES_decrypt(Input, Iv, Key); //调用解密方法解密

//返回由字节数组中指定位置的四个字节转换来的 32 位有符号整数
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); //用来和m_sCorpID验证,解密是否正确
        try:
            pad = plain_text[-1]
            # 去除16位随机字符串
            content = plain_text[16:-pad]
            #struct.unpack("I",content[ : 4])[0]    返回由字节数组中指定位置的四个字节转换来的 32 位有符号整数
            #socket.ntohl 将数字由网络字节顺序转换为主机字节顺序。
            xml_len = socket.ntohl(struct.unpack("I",content[ : 4])[0])
            xml_content = content[4 : xml_len+4]
            from_corpid = content[xml_len+4:].decode("utf8")
            print(from_corpid)
        except Exception as e:
            print(e)
            return  ierror.WXBizMsgCrypt_IllegalBuffer,None    

5、调用解密方法,返回明文及cpid

//C#
sReplyEchoStr = Cryptography.AES_decrypt(sEchoStr, m_sEncodingAESKey, ref cpid);
#python
pc = Prpcrypt(self.key)ret,sReplyEchoStr = pc.decrypt(sEchoStr,self.m_sCorpid)

6、解密后需要比对m_sCorpID和cpid是否一致

五、疑问

由于技术有限,并没做过太多Socket编程,并不很了解如下内容

如有高手路过,请指点一下,谢谢。

//返回由字节数组中指定位置的四个字节转换来的 32 位有符号整数
int len = BitConverter.ToInt32(btmpMsg, 16);
//将数字由网络字节顺序转换为主机字节顺序。
len = IPAddress.NetworkToHostOrder(len);
xml_len = socket.ntohl(struct.unpack("I",content[ : 4])[0])

六、Python3.*版本WXBizMsgCrypt文件下载

修改后Python3.* 对应WXBizMsgCrypt.2014.09.28.zip文件。

该版本仅使用本文中修改方法进行修改,并且仅测试过URL验证方法,其他方法暂时可能存在问题,后续慢慢完善。

如有高手愿意提供WXBizMsgCrypt.py,请直接留言,谢谢。

七、Python3.*版本 测试代码

from http.server import BaseHTTPRequestHandler
from http.server import HTTPServer
from socketserver import ThreadingMixIn
import urllib.parse
from WXBizMsgCrypt import WXBizMsgCrypt
import xml.etree.cElementTree as ET

hostIP = ‘‘
portNum = 8080

serverMessage = "msg_signature"

sToken="QDG6eK"
sEncodingAESKey="jWmYm7qr5nMoAUwZRjGtBxmz3KA1tkAj3ykkR6q2B2C"
sCorpID="wx5823bf96d3bd56c7"

class mySoapServer( BaseHTTPRequestHandler ):
    def do_head( self ):
        pass

    def do_GET( self ):
        try:
            if self.path.find(serverMessage) == -1:
                self.send_error( 404, message = None )
                return

            #解析请求
            query=GetQuery(self.path)

            sVerifyMsgSig=query["msg_signature"][0]
            sVerifyTimeStamp=query["timestamp"][0]
            sVerifyNonce=query["nonce"][0]
            sVerifyEchoStr=query["echostr"][0] 

            wxcpt=WXBizMsgCrypt(sToken,sEncodingAESKey,sCorpID)
            ret,sEchoStr=wxcpt.VerifyURL(sVerifyMsgSig, sVerifyTimeStamp,sVerifyNonce,sVerifyEchoStr)

            self.send_response( 200, message = None )
            self.send_header( ‘Content-type‘, ‘text/html‘ )
            self.end_headers()            

            if ret == 0:
                res=sEchoStr.decode("utf8")
            else:
                res = "%s" % ret

            self.wfile.write( res.encode( encoding = ‘utf_8‘, errors = ‘strict‘ ) )
        except IOError:
            self.send_error( 404, message = None )

def GetQuery(str):
    params = str[str.index("?")+1:]

    parsed_result = {}

    list = [param for param in params.split(‘&‘)]

    for item in list:
        if item.find("=") > -1:
            name = item[:item.index("=")]
            value = urllib.parse.unquote(item[item.index("=")+1:])
        else:
            name = item
            value = ""

        if name in parsed_result:
            parsed_result[name].append(value)
        else:
            parsed_result[name] = [value]     

    return parsed_result  #urllib.parse.parse_qs(temp)

class ThreadingHttpServer( ThreadingMixIn, HTTPServer ):
    pass

myServer = ThreadingHttpServer( ( hostIP, portNum ), mySoapServer )
print("Server Started ....")
myServer.serve_forever()
myServer.server_close()
时间: 2024-10-14 19:35:14

微信开发 企业号(二)-- 回调模式之Tooken验证 .net/python的相关文章

用c#开发微信(1)服务号的服务器配置和企业号的回调模式 - url接入 (源码下载)

最近研究了下服务号的服务器配置和企业号的回调模式.真正实现完后,觉得很简单,但一开始还是走了点弯路,所以写了个web程序,只用改下配置文件里的参数就可以直接用了.下面介绍下详细的用法以及实现步骤. 一.用法 1. 下载web程序 http://yunpan.cn/cjeTSAKwUVmv9  访问密码 7ab3 2. 修改配置文件web.config <appSettings> <!--微信的Token--> <add key="WeixinToken"

C#微信企业号开发二:配置回调模式与接入验证

转载请附加本文链接:http://www.cnblogs.com/hispring/p/4496575.html(Created by Aaron) 官方提供了针对不同语言开发的包,下载地址为:下载地址 一.配置回调模式 填写url.token.encodingAESKey即可. 在这里进行配置后,微信会在我们填写的url后附加几个参数用以进行验证,格式如下: http://xxxx?msg_signature=xxxx&timestamp=xxxx&nonce=xxxx&echo

微信开发 企业号(一)-- 申请试用

“企业号,是微信为企业用户提供的移动应用入口” 一.在首页申请试用 进入首页(Http://qy.weixin.qq.com) 通过首页顶上的“开发者中心”进入开发者中心界面 点击“申请体验”进入申请界面申请试用号. 体验号只作开发者测试使用,通讯录最多支持10个用户,使用期限最长为90天(从申请成功日开始),90天之后强制回收,回收后会清空体验号所有数据,如需注册正式企业号请点击注册企业号 . 二.登陆 注册成功后回到首页,使用微信客户端扫面右侧二维码,并授权登陆. 如果是首次登陆,需要绑定企

php微信开发 -- 两种运营模式及服务器配置

微信的两种运营模式 编辑模式:使用微信公众平台提供的功能 开发者模式:通过腾讯的api接口调用相应程序进行二次开发 编辑模式 应用场景: l 不具备开发能力的运营者 l 主要是进行品牌宣传.新闻媒体.自助客服的公众帐号 l 运营初期,不需要特别多的功能 l 开发模式系统升级.故障等特殊情况 功能演示: 1)自动回复 被添加自动回复:当我们订阅或关注微信公众平台时,系统自动发送的回复,我们称之为关注回复或订阅回复. 关键词自动回复:当用户输入的关键词与我们系统设置的关键词相匹配时,自动返回的回复.

微信开发之启用开发者模式(三)

一.准备环境 1.JDK1.6及以上版本 2.Eclipse 3.Tomcat 4.Ngrok 二.步骤 1.访问微信公众平台开发者手册  https://mp.weixin.qq.com/wiki  如下是接入规则(来自开发者手册):  开发者提交信息后,微信服务器将发送GET请求到填写的服务器地址URL上,GET请求携带参数如下表所示: 参数 描述 signature 微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数.nonce参数. time

带领技术小白入门——基于java的微信公众号开发(包括服务器配置、java web项目搭建、tomcat手动发布web项目、微信开发所需的url和token验证)

微信公众号对于每个人来说都不陌生,但是许多人都不清楚是怎么开发的.身为技术小白的我,在闲暇之余研究了一下基于java的微信公众号开发.下面就是我的实现步骤,写的略显粗糙,希望大家多多提议! 一.申请服务器 1.我购买的是阿里云服务器,购买后要设置一下服务器密码,默认用户名是administrator,购买好后如下: 2.申请好后,copy一下此服务器的IP地址(公有),在本地ping一下看看是否可用,j键盘Win+R,输入cmd,输入ping+IP回车,如下即为成功: 二.配置服务器 1.下载远

微信开发(二)设置微信回调服务器 ( Node.js )

div#cpmenu {height:200px;float:left;} div#cpcontent {height:200px;width:150px;float:left;} 文章作者:松阳 原文链接:http://blog.csdn.net/fansongy/article/details/43341405 概述 上一篇中简单介绍了Token的获取,这篇中介绍如何设置回调服务器.使用技术为Node.js中的Express. 搭建服务器 这里我使用Node.js中的Express框架实现一

微信企业号开启回调模式--php

首先选择回头模式,然后生成Tokey   EncodingAESKey. 把附件的代码上传到网站目录下. 然后修改 Sample.php  ,最后验证就可以了. include_once "WXBizMsgCrypt.php"; $encodingAesKey = "xxxxxxxxxxxxxxxxxxxxxxx"; //替换成上面生成的 $token = "xxxxxx"; //替换成上面生成的 $corpId = "xxxxxxxx

微信开发只 二维码生成类库

最近weiphp 二次开真的有点累,漏洞百出.代码维护代价有点高. <?php /** * Created by PhpStorm. * User: bin * Date: 15-1-16 * Time: 上午9:48 */ namespace Home\Common; // 微信处理类 set_time_limit(30); class Weixin{ //构造方法 static $qrcode_url = "https://api.weixin.qq.com/cgi-bin/qrcod