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:
Este proceso se divide en dos partes:
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.
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
withCredentials
es 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:
/ oauth / token Obtenga el token del servidor a través de la información de verificación proporcionada por el applet
/ 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.
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:
Suficientemente largo Se recomienda tener una combinación de 2 ^ 128
Evite usar srand (hora actual) y luego rand (), pero use el mecanismo de números aleatorios reales provisto por el sistema operativo.
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:
Autorización de usuario en applet
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 sanic
el marco + swagger_py_codegen
para generar resto-api.
La base de datos utiliza MongoDB, que python-weixin
realiza 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
Authorization
información.Authorization
Antes 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 |
---|---|