Desarrollo de applets de WeChat: python + sanic para lograr el registro de inicio de sesión de applet

Al desarrollar el applet WeChat, el inicio de sesión autorizado para acceder al applet puede realizar rápidamente los pasos de registro e inicio de sesión del usuario, que es un paso importante para establecer rápidamente un sistema de usuario. Este artículo presentará el applet python + sanic + WeChat para registrarse rápidamente e iniciar sesión en la solución de pila completa.

El diagrama de secuencia de inicio de sesión del applet WeChat es el siguiente:

Diagrama de secuencia de inicio de sesión

Este proceso se divide en dos partes:

  1. El applet usa la API wx.login () para obtener el código, llama a la API wx.getUserInfo () para obtener los datos cifrados y iv, y luego envía las tres informaciones al servidor de terceros.

  2. Después de obtener el código, encryptedData, y iv, el servidor de terceros usa el código para intercambiar por session_key, y luego descifra la session_key usando encryptedData y iv para obtener información del usuario en el lado del servidor. Devuelva datos jwt basados ​​en la información del usuario para completar el inicio de sesión.

Primero veamos la API proporcionada por el applet.

API de inicio de sesión de Applet

En el proceso de autorización de inicio de sesión, las API utilizadas son las siguientes:

  • wx.login

  • wx.getUserInfo

wx.chekSession Es opcional y no se usa aquí.

wx.login (OBJETO)

Llame a esta interfaz para obtener las credenciales de inicio de sesión (código) a cambio de la información del estado de inicio de sesión del usuario, incluido el identificador único del usuario (openid) y la clave de sesión (session_key) para este inicio de sesión.

Si la llamada de la interfaz es exitosa, el resultado de la devolución es el siguiente:

Nombre del parámetro Tipo Explicación
errMsg Cuerda Resultado de la llamada
código Cuerda Después de que el usuario pueda iniciar sesión, el contenido de devolución de llamada traerá un código (válido por cinco minutos). El desarrollador debe enviar el código al fondo del servidor del desarrollador, usar el código para cambiar la API de session_key y reemplazar el código con openid y session_key

código a cambio de session_key

El servidor del desarrollador usa el código de credencial de inicio de sesión para obtener la session_key y openid. Donde session_key es la clave para cifrar y firmar los datos del usuario. Para la seguridad de su propia aplicación, session_key no debe transmitirse en la red. Por lo tanto, este paso debe implementarse en el lado del servidor.

wx.getUserInfo

Esta interfaz se utiliza para obtener información del usuario.

Como withCredentialses cierto, han sido llamados previamente requerido wx.login estado de inicio de sesión y, sin embargo, no caducado, los datos devueltos en este momento contendrán encryptedData, iv y otra información sensible; cuando withCredentials es falsa, no requiere un estado de inicio de sesión, los datos devueltos no incluye encryptedData, iv y otra información confidencial.

Cuando la interfaz tiene éxito, los parámetros de retorno son los siguientes:

Nombre del parámetro Tipo Explicación
Información de usuario OBJETO Objeto de información del usuario, no contiene información confidencial como openid
rawData Cuerda La cadena de datos original que excluye información confidencial se utiliza para calcular la firma.
firma Cuerda Use sha1 (rawData + sessionkey) para obtener la cadena, que se utiliza para verificar la información del usuario, consulte la firma del documento.
encryptedData Cuerda Datos cifrados de información completa del usuario, incluidos datos confidenciales, consulte Algoritmo de descifrado de datos cifrados para obtener más información
iv Cuerda Vector inicial del algoritmo de cifrado, consulte el algoritmo de descifrado de datos de cifrado para obtener detalles

encryptedData Después del descifrado, es la siguiente estructura json. Para obtener más información, consulte el algoritmo de descifrado de datos de cifrado

{
    "openId": "OPENID",
    "nickName": "NICKNAME",
    "gender": GENDER,
    "city": "CITY",
    "province": "PROVINCE",
    "country": "COUNTRY",
    "avatarUrl": "AVATARURL",
    "unionId": "UNIONID",
    "watermark":
    {
        "appid":"APPID",
    "timestamp":TIMESTAMP
    }
}

Dado que decryptedData necesita session_key y iv, es necesario enviar código, encryptedData y iv juntos en el proceso de enviar la verificación de autorización al servidor.

API proporcionada por el servidor

La autorización del lado del servidor debe proporcionar dos API:

  1. / oauth / token Obtenga el token del servidor a través de la información de verificación proporcionada por el applet

  2. / cuentas / wxapp Si el usuario conectado es un usuario no registrado, use esta interfaz para registrarse como un nuevo usuario.

A cambio de un token de terceros (/ oauth / token)

Al iniciar la autorización, el applet llama a esta API a cambio de jwt, si el usuario no está registrado devuelve 401, si el usuario envía parámetros incorrectos, devuelve 403.

Cuando la interfaz obtiene jwt con éxito, los parámetros de retorno son los siguientes:

Nombre del parámetro Tipo Explicación
ID de la cuenta cuerda ID de usuario del usuario actualmente autorizado
token de acceso cuerda jwt (clave_sesión de terceros en el proceso de inicio de sesión
token_type cuerda tipo de token (portador fijo)

Después de autorizar el applet, se debe llamar primero a esta interfaz. Si el resultado es que el usuario no está registrado, se debe llamar a la nueva interfaz de registro de usuario para registrar primero al nuevo usuario. Después de un registro exitoso, se llama a esta interfaz a cambio de jwt.

Registro de nuevo usuario (/ accounts / wxapp)

Al registrar un nuevo usuario, el servidor necesita almacenar el openid del usuario actual, por lo que, al igual que la interfaz de autorización, los parámetros necesarios para la solicitud son código, datos cifrados y iv.

Después de un registro exitoso, se devolverá la identificación de usuario y el tiempo de registro. En este momento, debe llamar a la interfaz para volver a obtener el token a cambio del token de terceros para el próximo inicio de sesión.

Proceso de implementación

Después de definir la interfaz, echemos un vistazo al proceso general de inicio de sesión de autorización de los extremos frontal y posterior.

Proceso de inicio de sesión de autorización de applet

Este proceso debe tenerse en cuenta que después del paso C (usando el código a cambio de sesión), obtenemos la clave de sesión y luego debemos descifrar la clave de sesión para obtener los datos del usuario.

Luego use openid para determinar si el usuario ha sido registrado, si el usuario ya está registrado, genere jwt y regrese al applet.
Si el usuario no está registrado, se devuelve 401, lo que indica que el usuario no está registrado.

jwt(3rd_session) Se utiliza para verificar el estado de inicio de sesión entre el servidor de terceros y el applet. Para garantizar la seguridad, jwt debe cumplir:

  1. Suficientemente largo Se recomienda tener una combinación de 2 ^ 128

  2. Evite usar srand (hora actual) y luego rand (), pero use el mecanismo de números aleatorios reales provisto por el sistema operativo.

  3. Establecer un cierto tiempo efectivo,

Por supuesto, también puede usar su número de teléfono móvil para iniciar sesión en el applet, pero esta es otra función, así que no lo describiré aquí.

Implementación de código

Habiendo dicho tanto, veamos el código a continuación.

Código de Applet

La lógica del código es:

  1. Autorización de usuario en applet

  2. El applet envía un mensaje de autorización al servidor. El servidor verifica si el usuario está registrado. Si el registro devuelve jwt, si el usuario no está registrado, le solicita que no se registre. Luego, el applet solicita la interfaz de registro nuevamente para registrar al usuario. Repita este paso después de un registro exitoso.

Para simplificar, se solicita autorización cuando se inicia el applet. El código se implementa de la siguiente manera.

//app.js
var config = require('./config.js')

App({
    onLaunch: function() {
        //调用API从本地缓存中获取数据
        var jwt = wx.getStorageSync('jwt');
        var that = this;
        if (!jwt.access_token){ //检查 jwt 是否存在 如果不存在调用登录
            that.login();
        } else {
            console.log(jwt.account_id);
        }
    },
    login: function() {
        // 登录部分代码
        var that = this;
        wx.login({
            // 调用 login 获取 code
            success: function(res) {
                var code = res.code;
                wx.getUserInfo({
                    // 调用 getUserInfo 获取 encryptedData 和 iv
                    success: function(res) {
                        // success
                        that.globalData.userInfo = res.userInfo;
                        var encryptedData = res.encryptedData || 'encry';
                        var iv = res.iv || 'iv';
                        console.log(config.basic_token);
                        wx.request({ // 发送请求 获取 jwt
                            url: config.host + '/auth/oauth/token?code=' + code,
                            header: {
                                Authorization: config.basic_token
                            },
                            data: {
                                username: encryptedData,
                                password: iv,
                                grant_type: "password",
                                auth_approach: 'wxapp',
                            },
                            method: "POST",
                            success: function(res) {
                                if (res.statusCode === 201) {
                                    // 得到 jwt 后存储到 storage,
                                    wx.showToast({
                                        title: '登录成功',
                                        icon: 'success'
                                    });
                                    wx.setStorage({
                                        key: "jwt",
                                        data: res.data
                                    });
                                    that.globalData.access_token = res.data.access_token;
                                    that.globalData.account_id = res.data.sub;
                                } else if (res.statusCode === 401){
                                    // 如果没有注册调用注册接口
                                    that.register();
                                } else {
                                    // 提示错误信息
                                    wx.showToast({
                                        title: res.data.text,
                                        icon: 'success',
                                        duration: 2000
                                    });
                                }
                            },
                            fail: function(res) {
                                console.log('request token fail');
                            }
                        })
                    },
                    fail: function() {
                        // fail
                    },
                    complete: function() {
                        // complete
                    }
                })
            }
        })

    },
    register: function() {
        // 注册代码
        var that = this;
        wx.login({ // 调用登录接口获取 code
            success: function(res) {
                var code = res.code;
                wx.getUserInfo({
                    // 调用 getUserInfo 获取 encryptedData 和 iv
                    success: function(res) {
                        // success
                        that.globalData.userInfo = res.userInfo;
                        var encryptedData = res.encryptedData || 'encry';
                        var iv = res.iv || 'iv';
                        console.log(iv);
                        wx.request({ // 请求注册用户接口
                            url: config.host + '/auth/accounts/wxapp',
                            header: {
                                Authorization: config.basic_token
                            },
                            data: {
                                username: encryptedData,
                                password: iv,
                                code: code,
                            },
                            method: "POST",
                            success: function(res) {
                                if (res.statusCode === 201) {
                                    wx.showToast({
                                        title: '注册成功',
                                        icon: 'success'
                                    });
                                    that.login();
                                } else if (res.statusCode === 400) {
                                    wx.showToast({
                                        title: '用户已注册',
                                        icon: 'success'
                                    });
                                    that.login();
                                } else if (res.statusCode === 403) {
                                    wx.showToast({
                                        title: res.data.text,
                                        icon: 'success'
                                    });
                                }
                                console.log(res.statusCode);
                                console.log('request token success');
                            },
                            fail: function(res) {
                                console.log('request token fail');
                            }
                        })
                    },
                    fail: function() {
                        // fail
                    },
                    complete: function() {
                        // complete
                    }
                })
            }
        })

    },

    get_user_info: function(jwt) {
        wx.request({
            url: config.host + '/auth/accounts/self',
            header: {
                Authorization: jwt.token_type + ' ' + jwt.access_token
            },
            method: "GET",
            success: function (res) {
                if (res.statusCode === 201) {
                    wx.showToast({
                        title: '已注册',
                        icon: 'success'
                    });
                } else if (res.statusCode === 401 || res.statusCode === 403) {
                    wx.showToast({
                        title: '未注册',
                        icon: 'error'
                    });
                }

                console.log(res.statusCode);
                console.log('request token success');
            },
            fail: function (res) {
                console.log('request token fail');
            }
        })
    },

    globalData: {
        userInfo: null
    }
})

Código del servidor

El servicio utiliza sanicel marco + swagger_py_codegenpara generar resto-api.
La base de datos utiliza MongoDB, que python-weixinrealiza la función de intercambio de código para el descifrado de sesión_clave y cifrado de datos durante el proceso de inicio de sesión, por lo que python-weixin se usa como Python WeChat SDK.

Para filtrar solicitud válida, el servidor requiere el usuario para llevar en el encabezado o en el momento de obtener la autorización token de Authorizationinformación. AuthorizationAntes de iniciar sesión, se utiliza la autenticación básica (clave básica). La clave básica es client_id + client_secret para el procesamiento BASE64. Solo se utiliza para verificar si el cliente solicitado es legal. Sin embargo, Basic es básicamente equivalente a texto plano, y no se puede usar para una verificación de autorización estricta.

JWT principio y ver para comprender la certificación y la práctica JWT (JSON Web Token)

La estructura del código generado usando swagger es la siguiente:

Debido a que el código es demasiado largo, aquí solo se coloca la lógica para obtener jwt:

def get_wxapp_userinfo(encrypted_data, iv, code):
    from weixin.lib.wxcrypt import WXBizDataCrypt
    from weixin import WXAPPAPI
    from weixin.oauth2 import OAuth2AuthExchangeError
    appid = Config.WXAPP_ID
    secret = Config.WXAPP_SECRET
    api = WXAPPAPI(appid=appid, app_secret=secret)
    try:
        # 使用 code  换取 session key    
        session_info = api.exchange_code_for_session_key(code=code)
    except OAuth2AuthExchangeError as e:
        raise Unauthorized(e.code, e.description)
    session_key = session_info.get('session_key')
    crypt = WXBizDataCrypt(appid, session_key)
    # 解密得到 用户信息
    user_info = crypt.decrypt(encrypted_data, iv)
    return user_info


def verify_wxapp(encrypted_data, iv, code):
    user_info = get_wxapp_userinfo(encrypted_data, iv, code)
    # 获取 openid
    openid = user_info.get('openId', None)
    if openid:
        auth = Account.get_by_wxapp(openid)
        if not auth:
            raise Unauthorized('wxapp_not_registered')
        return auth
    raise Unauthorized('invalid_wxapp_code')
    
    
def create_token(request):
    # verify basic token
    approach = request.json.get('auth_approach')
    username = request.json['username']
    password = request.json['password']
    if approach == 'password':
        account = verify_password(username, password)
    elif approach == 'wxapp':
        account = verify_wxapp(username, password, request.args.get('code'))
    if not account:
        return False, {}
    payload = {
        "iss": Config.ISS,
        "iat": int(time.time()),
        "exp": int(time.time()) + 86400 * 7,
        "aud": Config.AUDIENCE,
        "sub": str(account['_id']),
        "nickname": account['nickname'],
        "scopes": ['open']
    }
    token = jwt.encode(payload, 'secret', algorithm='HS256')
    # 由于 account 中 _id 是一个 object 需要转化成字符串
    return True, {'access_token': token, 'account_id': str(account['_id'])}

El código específico se puede ver en Metis: https://github.com/gusibi/Metis .

Note: Si prueba el código, configure primero oauth2_client y use su propia configuración.

No envíe información de configuración privada a github.

Enlace de referencia


Finalmente, gracias novia por tu apoyo.

Bienvenido a seguir (April_Louisa) Invitame a tomar Fanta
Bienvenida atención Invitame a tomar Fanta

Supongo que te gusta

Origin www.cnblogs.com/10manongit/p/12729199.html
Recomendado
Clasificación