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