SaaS 做爲一種服務,須要爲不一樣的客戶定製不一樣的域名以知足客戶定製化的需求。而微信登陸時須要填寫一個回調地址,單一的回調地址是難以處理多客戶域名的業務需求的,通過不一樣的 SaaS 項目的實踐,總結出了下面的方式。html
微信登陸的核心代碼依然採用 psa 這個庫 https://github.com/python-soc...。python
閱讀微信公衆平臺文檔,能夠看到,當同一個微信公衆號須要在多個服務間使用時,微信的建議是提供一臺中控服務器,防止access_token的重複刷新,這個坑確實踩到過。git
https://tools.ietf.org/html/r...github
中控機爲同一引導用戶登陸的微信登陸服務器,其中此機器作的爲 oauth 2.0 截圖部分的 A、B,引導用戶受權,微信回調到此中控機,拿到code。
中控機經過state參數,解出customerid,根據customer配置找到回調地址。回調是將state,code帶去回調的客戶域名。服務器
customer表須要記錄微信的appid,appsecret,這樣即便客戶須要定製本身的微信公衆號,中控機也能夠saas化。微信
因爲微信的state參數長度有限,所以提供一張redirecturl表記錄回調地址,登陸時只須要將redirecturl_id帶入state中便可。redirecturl記錄的爲回調客戶域名+psa complete地址的完整路由。app
state爲oauth 2.0中容許的回調參數,state的構成爲: 客戶id,回調地址id,其餘須要回調參數
微信公衆平臺
中控機經過customer獲取對應的appid,secret。微信回調到cherrypick後,拿着code,state跳轉到對應的客戶域名。url
def _auth(request, backend): cid = request.GET['cid'] # TODO: DoesNotExist customer = Customer.objects.get(id=cid) appid, appsecret = customer.get_key_and_secret() log.info('login cid:%s, key:%s', cid, appid) def get_key_and_secret(): return appid, appsecret request.backend.get_key_and_secret = get_key_and_secret return do_auth(request.backend) @never_cache @psa('our_social:cherrypick') def auth(request, backend, key=''): return _auth(request, backend) @never_cache @psa() def cherrypick(request, backend): code = request.GET.get('code', '') state = request.GET.get('state', '') redirect_url_id = state.split(',')[0] redirect_url = RedirectURL.objects.get(id=redirect_url_id).url redirect_url = '{}?code={}&state={}'.format(redirect_url, code, state) log.info('cherrypick, redirect_url: %s, state: %s', redirect_url, state) return redirect(redirect_url)
SaaS 服務器處理 oauth 2.0 C、D以後的步驟spa
@psa('our_social:complete') def complete(request, backend, *args, **kwargs): """Authentication complete view""" logout(request) state = request.GET.get('state', '') ...... state解析出cid等參數 customer = Customer.objects.get(id=cid) appid, appsecret = product.get_key_and_secret() request._customer = customer 覆蓋backend的方法 def get_key_and_secret(): log.info('login complete use appid: %s %s', appid, state) request.backend.get_key_and_secret = get_key_and_secret return do_complete(request.backend, _do_login, request.user, redirect_name=REDIRECT_FIELD_NAME, request=request, *args, **kwargs)