背景SaaS 作为一种服务,需要为不同的客户定制不同的域名以满足客户定制化的需求。而微信登录时需要填写一个回调地址,单一的回调地址是难以处理多客户域名的业务需求的,经过不同的 SaaS 项目的实践,总结出了下面的方式。微信登录的核心代码依然采用 psa 这个库 https://github.com/python-soc…。微信说明阅读微信公众平台文档,可以看到,当同一个微信公众号需要在多个服务间使用时,微信的建议是提供一台中控服务器,防止access_token的重复刷新,这个坑确实踩到过。oauth 2.0https://tools.ietf.org/html/r…核心概念、表结构中控机中控机为同一引导用户登录的微信登录服务器,其中此机器做的为 oauth 2.0 截图部分的 A、B,引导用户授权,微信回调到此中控机,拿到code。中控机通过state参数,解除customerid,根据customer配置找到回调地址。回调是将state,code带去回调的客户域名。customercustomer表需要记录微信的appid,appsecret,这样即使客户需要定制自己的微信公众号,中控机也可以saas化。redirecturl由于微信的state参数长度有限,因此提供一张redirecturl表记录回调地址,登录时只需要将redirecturl_id带入state中即可。redirecturl记录的为回调客户域名+psa compelate地址的完整路由。statestate为oauth 2.0中允许的回调参数,state的构成为: 客户id,回调地址id,其他需要回调参数核心流程核心代码中控机通过customer获取对应的appid,secret。微信回调到cherrypick后,拿着code,state跳转到对应的客户域名。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之后的步骤@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)