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)