Official account development is abandoned due to too little access authority.

preface

Recently, the egg pain has been trying to discard the official account service provided by WeChat, and want to move public service to cloud server. However, it has found awful interface permissions.

1, Basic configuration

It mainly adds url and token. Here, the url must start with http: / / or https: / /, and supports port 80 and port 443 respectively. So the server uses Nginx to monitor port 443, and then forwards it to the port allocated to wechat api (8000 in this paper).

2, Server authentication

When your own server receives a GET request, you need to verify whether the message is sent by the wechat server. The code is as follows.

@app.route('/', methods=['GET', 'POST'])
def wechat():
    args = request.args
    signature = args['signature']
    timestamp = args['timestamp']
    nonce = args['nonce']
    token = 'xxxxxxxxx'
    
    if not all([signature, timestamp, nonce, echostr]):
        abort(400)
    
    # Construct hashcode according to token, timestamp and nonce
    list = sorted([token, timestamp, nonce])
    s = list[0]+list[1]+list[2]
    hashcode = hashlib.sha1(s.encode('utf-8')).hexdigest()

    if request.method=='GET':
        # GET is only used to check whether hashcode is equal to signature when configuring the server
        echostr = args['echostr']
        if hashcode == signature:
            return echostr
        else:
            return "Sign error."

3, Processing user messages

The user sends it to the wechat server, and the wechat server makes it into XML and sends it to its own server, similar to the following. MsgType is the message type, such as text, picture, applet, video, voice, etc. Process and reply according to MsgType respectively.

<xml>
 <ToUserName><![CDATA[official account]]></ToUserName>
 <FromUserName><![CDATA[Fan number]]></FromUserName>
 <CreateTime>1460537339</CreateTime>
 <MsgType><![CDATA[text]]></MsgType>
 <Content><![CDATA[Big brother muyao nb]]></Content>
 <MsgId>3272960105994287638</MsgId>
</xml>

After receiving the xml, I use xmltodict Parse method converts xml into a similar dictionary for subsequent processing, and finally uses xmltodict The Unparse method converts dict into xml and returns it to the wechat server, which sends it to the user.

The complete code is as follows:

from flask import Flask, jsonify, request, Response, abort
from gevent import pywsgi
from flask_cors import CORS
import time
import hashlib
import xmltodict

app = Flask(__name__)
app.config['JSON_AS_ASCII'] = False  # Support Chinese
app.config['JSON_SORT_KEYS'] = False  # Disable json auto sorting
CORS(app, supports_credentials=True)  # Allow cross domain


@app.route('/', methods=['GET', 'POST']) #, strict_slashes=False)
def wechat():
    args = request.args
    signature = args['signature']
    timestamp = args['timestamp']
    nonce = args['nonce']
    token = 'xxxxxxxxxxxx'
    
    if not all([signature, timestamp, nonce]):
        abort(400)
    
    list = sorted([token, timestamp, nonce])
    s = list[0]+list[1]+list[2]
    hashcode = hashlib.sha1(s.encode('utf-8')).hexdigest()

    if request.method=='GET':
        # It is only used for verification when configuring the server
        echostr = args['echostr']
        if hashcode == signature:
            return echostr
        else:
            return "Sign error."
    
    if request.method=='POST':
    
        # Monitor whether the request is sent by wechat
        if hashcode != signature:
            abort(403)
        
        xml = request.data
        if not xml:
            abort(400)
        
        # parse: xml -> dict
        req = xmltodict.parse(xml)['xml']
        print(req)
        
        
        # The user sent text
        if 'text' == req.get('MsgType'):
            resp = {
                'ToUserName':req.get('FromUserName'),
                'FromUserName':req.get('ToUserName'),
                'CreateTime':int(time.time()),
                'MsgType':'text',
                'Content':req.get('Content')
            }
        
        # The user sent voice
        elif 'voice' == req.get('MsgType'):
            reg = req.get('Recognition')
            resp = {
                'ToUserName':req.get('FromUserName'),
                'FromUserName':req.get('ToUserName'),
                'CreateTime':int(time.time()),
                'MsgType':'text',
                'Content':reg if reg else 'What you said! I cannot understand you'
            }
        
        # The user sent pictures        
        elif 'image' == req.get('MsgType'):
            resp = {
                'ToUserName': req.get('FromUserName'),
                'FromUserName': req.get('ToUserName'),
                'CreateTime': int(time.time()),
                'MsgType': 'text',
                'Content': 'Good picture, good picture!'
            }
        
        # The user sent the location        
        elif 'location' == req.get('MsgType'):
            resp = {
                'ToUserName': req.get('FromUserName'),
                'FromUserName': req.get('ToUserName'),
                'CreateTime': int(time.time()),
                'MsgType': 'text',
                'Content': f'So you're in(X, Y)=({req.get("Location_X")}, {req.get("Location_Y")})of{req.get("Label")}!'
            }
        
        # The user sent a video     
        elif 'video' == req.get('MsgType'):
            resp = {
                'ToUserName': req.get('FromUserName'),
                'FromUserName': req.get('ToUserName'),
                'CreateTime': int(time.time()),
                'MsgType': 'text',
                'Content': 'Don't send anything weird'
            }
        
        # The user sent an event
        elif 'event' == req.get('MsgType'):
            if "subscribe" == req.get("Event"):
                resp = {
                    "ToUserName":req.get("FromUserName", ""),
                    "FromUserName":req.get("ToUserName", ""),
                    "CreateTime":int(time.time()),
                    "MsgType":"text",
                    "Content":u"Thank you for your attention!"
                }
            else:
                print('Take it off!')
                resp = None
            
        # The user sent something else and returned a string casually, otherwise there was a problem with the user's display service
        else:
            return "success"
        
        # unparse: dict -> xml
        xml = xmltodict.unparse({'xml':resp if resp else ''})
        return xml

if __name__ == '__main__':
    server = pywsgi.WSGIServer(('0.0.0.0', 8000), app)
    server.serve_forever()

Supplement: authorization of wechat built-in web page

1. Official account permissions set up the domain name of the callback.
2. The user agrees to the authorization and obtains the code (the user accesses the following website, and the REDIRECT_URI is the URL to jump to after clicking the authorization, and the jump will take the code parameter)

https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect


3. Exchange the code obtained in step 2 for web page authorized access_token. The method is to send a GET request on your own server as follows:

https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code


The returned results are as follows:

{
   "access_token":"ACCESS_TOKEN",
   "expires_in":7200,
   "refresh_token":"REFRESH_TOKEN",
   "openid":"OPENID",
   "scope":"SCOPE"
}

4. Take ACCESS_TOKEN and OPENID change the user's basic information, which is also a GET request:

https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID&lang=zh_CN


The returned json result contains the following fields, and then take it to the front-end display to finish it:

Added by met0555 on Thu, 03 Mar 2022 14:29:29 +0200