WeChatアプレットを開発する場合、アプレットにアクセスするための承認済みログインは、ユーザー登録とログインの手順をすばやく実現できます。これは、ユーザーシステムをすばやく確立するための重要な手順です。この記事では、python + sanic + WeChatアプレットを紹介し、フルスタックソリューションにすばやく登録してログインします。
WeChatアプレットのログインシーケンス図は次のとおりです。
このプロセスは2つの部分に分かれています。
アプレットは、wx.login()APIを使用してコードを取得し、wx.getUserInfo()APIを呼び出して、encryptedDataおよびivを取得してから、3つの情報をサードパーティのサーバーに送信します。
コード、encryptedData、およびivを取得した後、サードパーティサーバーはコードを使用してsession_keyと交換し、encryptedDataおよびivを使用してsession_keyを復号化してサーバー側のユーザー情報を取得します。ユーザー情報に基づいてjwtデータを返し、ログインを完了します。
まず、アプレットが提供するAPIを見てみましょう。
アプレットログインAPI
ログインを承認するプロセスで使用されるAPIは次のとおりです。
wx.login
wx.getUserInfo
wx.chekSession
これはオプションであり、ここでは使用されません。
wx.login(オブジェクト)
このログインを呼び出すには、ユーザーの一意の識別子(openid)やこのログインのセッションキー(session_key)など、ユーザーのログインステータス情報と引き換えにログイン資格情報(コード)を取得します。
インターフェース呼び出しが成功した場合、返される結果は次のとおりです。
パラメータ名 | タイプ | 解説 |
---|---|---|
errMsg | ストリング | 通話結果 |
コード | ストリング | ユーザーがログインを許可された後、コールバックコンテンツはコード(5分間有効)を提供します。開発者は、コードを開発者サーバーのバックグラウンドに送信し、コードを使用してsession_key apiと交換し、コードをopenidおよびsession_keyで置き換える必要があります |
session_keyと引き換えにコード
開発者サーバーは、ログイン認証情報コードを使用して、session_keyとopenidを取得します。ここで、session_keyはユーザーデータを暗号化および署名するためのキーです。独自のアプリケーションセキュリティのために、session_keyをネットワーク上で送信しないでください。したがって、このステップはサーバー側で実装する必要があります。
wx.getUserInfo
このインターフェイスは、ユーザー情報を取得するために使用されます。
withCredentials
真である、以前にこの時点で返されたデータははEncryptedData、IVおよびその他の機密情報が含まれていますが、ログイン状態wx.login必要はなく、まだ期限切れと呼ばれている。withCredentialsがfalseの場合、ログイン状態を必要としない、返されたデータは含まれませんencryptedData、ivおよびその他の機密情報。
インターフェースが成功した場合、戻りパラメーターは次のとおりです。
パラメータ名 | タイプ | 解説 |
---|---|---|
ユーザー情報 | OBJECT | ユーザー情報オブジェクト。openidなどの機密情報は含まれません |
rawData | ストリング | 機密情報を除いた元のデータ文字列は、署名の計算に使用されます。 |
署名 | ストリング | ユーザー情報の確認に使用される文字列を取得するには、sha1(rawData + sessionkey)を使用します。ドキュメントの署名を参照してください。 |
encryptedData | ストリング | 機密データを含む完全なユーザー情報の暗号化データ。詳細については、暗号化データ復号アルゴリズムを参照してください。 |
iv | ストリング | 暗号化アルゴリズムの初期ベクトル。詳細については、暗号化データ復号化アルゴリズムを参照してください |
encryptedData
復号化後は以下のjson構造になります詳細は暗号化データ復号化アルゴリズムをご覧ください
{
"openId": "OPENID",
"nickName": "NICKNAME",
"gender": GENDER,
"city": "CITY",
"province": "PROVINCE",
"country": "COUNTRY",
"avatarUrl": "AVATARURL",
"unionId": "UNIONID",
"watermark":
{
"appid":"APPID",
"timestamp":TIMESTAMP
}
}
DecryptedDataにはsession_keyとivが必要なので、サーバーに承認検証を送信するプロセスで、coded、encryptedData、ivを一緒に送信する必要があります。
サーバーが提供するAPI
サーバー側の承認では、次の2つのAPIを提供する必要があります。
/ oauth / tokenアプレットによって提供された検証情報を介してサーバー自身のトークンを取得します
/ accounts / wxappログインしたユーザーが未登録のユーザーである場合は、このインターフェースを使用して新しいユーザーとして登録します。
サードパーティのトークン(/ oauth /トークン)と引き換えに
認証を開始すると、アプレットはjwtと引き換えにこのAPIを呼び出します。ユーザーが登録されていない場合は401を返し、ユーザーが間違ったパラメーターを送信した場合は403を返します。
インターフェースがjwtを正常に取得すると、戻りパラメーターは次のようになります。
パラメータ名 | タイプ | 解説 |
---|---|---|
アカウントID | ストリング | 現在許可されているユーザーのユーザーID |
アクセストークン | ストリング | jwt(ログインプロセスのサードパーティsession_key |
token_type | ストリング | トークンタイプ(固定ベアラー) |
アプレットが承認された後、このインターフェースを最初に呼び出す必要があります。その結果、ユーザーが登録されていない場合は、新しいユーザー登録インターフェースを呼び出して、新しいユーザーを最初に登録する必要があります。登録が成功すると、jwtの代わりにこのインターフェースが呼び出されます。
新規ユーザー登録(/ accounts / wxapp)
新しいユーザーを登録するとき、サーバーは現在のユーザーのopenidを保存する必要があるため、認証インターフェースと同様に、リクエストに必要なパラメーターはcode、encryptedData、およびivです。
登録が完了すると、ユーザーIDと登録時刻が返されます。この時点で、インターフェースを呼び出して、次回のログインのためのサードパーティのトークンと引き換えに、トークンを再度取得する必要があります。
実装プロセス
インターフェイスを定義したら、フロントエンドとバックエンドの認証ログインプロセス全体を見てみましょう。
このプロセスは、ステップC(セッションと引き換えにコードを使用)の後、session_keyを取得し、次にsession_keyを復号化してユーザーデータを取得する必要があることに注意する必要があります。
次に、openidを使用してユーザーが登録されているかどうかを確認します。ユーザーがすでに登録されている場合は、jwtを生成してアプレットに戻ります。
ユーザーが登録されていない場合は、401が返され、ユーザーが登録されていません。
jwt(3rd_session)
サードパーティサーバーとアプレット間のログインステータスをチェックするために使用されます。セキュリティを確保するために、jwtは次の条件を満たす必要があります。
十分長い。2 ^ 128の組み合わせをお勧めします
srand(現在時刻)を使用してからrand()を使用することは避けてください。ただし、オペレーティングシステムが提供する実際の乱数メカニズムを使用してください。
一定の有効時間を設定し、
もちろん、携帯電話番号を使用してアプレットにログインすることもできますが、これは別の機能であるため、ここでは説明しません。
コードの実装
そんなに多くを言ったので、次にコードを見てみましょう。
アプレットコード
コードロジックは次のとおりです。
アプレットでのユーザー認証
アプレットは認証メッセージをサーバーに送信します。サーバーはユーザーが登録されているかどうかを確認します。登録がjwtを返し、ユーザーが登録されていない場合は、ユーザーに登録解除するように求めます。次に、アプレットは登録インターフェースにユーザーの登録を再度要求します。登録が成功したら、この手順を繰り返します。
簡単にするために、アプレットの起動時に認証が要求されます。コードは次のように実装されます。
//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
}
})
サーバーコード
サービスが使用するsanic
フレーム+をswagger_py_codegen
、残り-APIを生成します。
データベースはMongoDBを使用します。これpython-weixin
は、ログインプロセス中にsession_keyおよびencryptedData復号化のコード交換の機能を実現するため、python WeChat SDKとしてpython-weixinが使用されます。
無効な要求をフィルタリングするには、サーバーはヘッダーにまたは許可トークン取得の際に持参し、ユーザが必要となる
Authorization
情報を。Authorization
ログインする前に、基本認証が使用されます(基本ハッシュキー)。ハッシュキーは、BASE64処理のclient_id + client_secretです。これは、要求されたクライアントが正当かどうかを確認するためにのみ使用されます。ただし、Basicは基本的にプレーンテキストと同等であり、厳密な承認検証には使用できません。
swaggerを使用して生成されたコードの構造は次のとおりです。
コードが長すぎるため、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'])}
特定のコードは、Metis:https://github.com/gusibi/Metisで表示できます。
Note
:コードを試す場合は、最初にoauth2_clientを設定し、独自の構成を使用してください。プライベート構成情報をgithubに送信しないでください。
参照リンク
最後に、ガールフレンドのサポートに感謝します。
フォローへようこそ(April_Louisa) | ファンタを飲むように招待して |
---|---|