SaaS化实践——如何用一个微信公众号登录多个不同的域名

背景

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)

你可能感兴趣的:(python,django,微信公众号,微信登录,oauth2.0)