由于使用者的增加,特别是“答疑家”的使用,被提出了一些新的需求。于是最近又对 https://github.com/Jeff-Tian/keycloak-services-social-weixin 增加了普通的 PC 端扫码登录、和手机微信登录两种方式。如《【继续更新】尝试在 Keycloak 里打通整个微信生态 - Jeff Tian的文章 - 知乎 》所述。总之,配置更复杂了,这是为什么呢?主要是因为对接微信登录的方式有多种,目前我借助这个项目,摸清了 3 种,这里再统一梳理一下。

登录原理概览

这里先对原理做个概述,后面会详细介绍。一般来说,要接入微信或者任何第三方登录,都是推荐 OAuth 2.0/OIDC 标准协议的。这样的标准协议本身内容很多,但局限在基于浏览器(无论是手机 Web 还是 PC Web)的开放登录来说,就很简单,固定了一个套路,即:

  • 先构建第三方网站的授权链接,让用户在浏览器里打开并确认授权
  • 通过用户的授权,第三方网站颁发一个授权码,通过浏览器重定向传递给接入方
  • 接入入凭借这个授权码,和自己在第三方平台上的备案凭据(appid + appsecret),通过后端服务器向第三方服务器换取用户的令牌
  • 后续通过令牌调用第三方服务,比如获取用户资料等

更详细的流程可以参考:

构建授权链接

对于微信生态,它有两套体系(严格来说是至少两套),分别是开放平台和公众平台。在构建授权链接这一步,取决于你接入的体系,会有对应的链接。PC Web 接入的是开放平台,而手机 Web 接入的是公众平台(当然,手机 Web 其实也可以接入开放平台,但是不推荐!)。
比如对于 PC 来说,一般来说,第三方网站的授权链接页面,会展示说哪个接入方想要获取你的什么数据,你同意吗?并展示一个同意或者拒绝的按钮在页面上。而对于微信,它在授权链接网页页面展示的却是一个二维码,用户是否同意,要通过手机扫完这个二维码后,在手机上才能看到。虽然比较特殊,但仍然是一个标准的 OAuth 2.0/OIDC。
对于 PC Web 来说,一个典型的授权链接是: shell https://open.weixin.qq.com/connect/qrconnect?scope=snsapi_login&state=d3Yvfou3pdgp-UNVZ-i7DTDEbv4rZTWx6Wh7lmxzyvk.98VO-haMdj4.c0L0bnybTEatKpqInU02nQ&response_type=code&appid=wxc09e145146844e43&redirect_uri=http%3A%2F%2Flocalhost%3A8080%2Frealms%2Fmaster%2Fbroker%2Fweixin%2Fendpoint

前面为什么说,手机 Web 不能接入开放平台呢?因为在手机 Web 页上展示一个二维码,让用户来扫码确认,操作上不方便。因此手机端一般使用公众平台,对于这种情况,用户可以直接在微信里打开该链接,并进行授权(显式的或者隐式的),然后微信会颁发授权码再跳转到接入方,也是一个标准的 OAuth 2.0/OIDC 流程。一个典型的手机端授权链接是: shell https://open.weixin.qq.com/connect/oauth2/authorize?scope=...&state=...&response_type=code&appid=...&redirect_uri=...

注意这两个链接不太一样,因为是两套体系。不过,其主机名都是 open.weixin.qq.com,因为这都是微信的开放能力。

授权码传递回接入方

这是为什么在第一步的构建授权链接里,需要传递一个 redirect_uri 的原因。

使用授权码换取令牌

接入方在拿到了授权码之后,就可以通过授权码加上自己在微信的公众平台或者开放平台里备案凭据(appid + appsecret)换取用户的令牌了。

使用用户令牌换取用户信息

接入方通过前一步拿到的用户令牌,就可以通过如下接口获取到用户信息了: shell https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID⟨=zh_CN

注意,这一步,不管你接入哪套体系,都建议你用上述 URL 获取用户信息。授权链接的不同,已经让人很头大了,在了换取用户信息环节,能统一就统一吧。
以上接口,还有一个好处,它只检验令牌是否有效,而不检验令牌的获取方式 ,然后只需要有 openid,就能获取该 openid 对应用户的信息。通过这一点,可以用来自定义登录方式,比如公众号关注即登录。

自定义微信登录方式(以关注即登录举例)

由于没有标准的束缚,所以可以自由发挥,但这也带来了开发量,详见后面的叙述。这里讨论一下和标准流程的区别。
最大的区别是没有了授权码传递环节,也没有构建授权链接的环节。总之它通过某种方式拿到了用户的 openid(即通过微信服务发来的扫码消息中解析到的 openid)。
另外,它通过在公众平台里备案的客户端凭据,换取到了客户端级别的令牌(注意,前面描述的标准 OAuth 2.0/OIDC 流程拿到的是用户级别的令牌)。
通过令牌和 openid,就足以获取到用户信息了,于是可以为其创建接入方的会话了: shell https://api.weixin.qq.com/sns/userinfo?access_token=ACCESS_TOKEN&openid=OPENID⟨=zh_CN

下面来详述一下这三种登录方式。

三种微信登录

微信公众号关注即登录

这是 https://github.com/Jeff-Tian/keycloak-services-social-weixin 最早实现的一种登录方式,也是比较不常见的一种。第一次实现它时,只是为了好玩,因为那时候并不多见。不过很多年过去了,目前通过这个方案来登录的网站越来越多。
最早看到各大网站的 PC 端微信扫码登录,觉得有些别扭。因为我那时对微信生态完全陌生,就纯粹从用户的角度,在扫码登录时总感觉有些别扭。那时候,一般网站的右下角都会放置一个该网站的公众号二维码,而在登录时,也会被要求扫一个二维码。但是使用下来发现,即使登录了该网站,还是要单独再扫一次码才能关注该网站的公众号。不知道别人是什么感受,反正这种迷惑行为让我十分费解。
当时(2018 年)的直觉就是,应该将关注公众号和扫码登录结合起来,于是就开始了解微信开发相关的东西。并且,由于搜索了一下登录相关的开源库,才得知有 Keycloak,将 Keycloak 和微信接合起来搜索,就找到了 jyqq163/keycloak-services-social-weixin 这个库。不过,试用下来发现该库也是那种普通的扫码登录,于是只能硬着头皮阅读微信的开发文档,并将该库 fork 下来,做了修改以实现了微信公众号关注即登录功能,详见《基于 keycloak 的关注公众号即登录功能的设计与实现 - Jeff Tian的文章 - 知乎 》。

原理

这样实现的微信登录,原理是利用了微信公众平台的带参二维码功能。这里的重点有两个:

关键

由于要实现扫码感知,就需要一种机制接收微信服务发过来的 xml 消息。微信的公众号后台,有一个基本配置,在这里,有一个服务器配置,就是用来配置接收微信服务的消息用的:
image.png
比如你用了https://github.com/Jeff-Tian/keycloak-services-social-weixin,并部署到了 https://keycloak.jiwai.win,为了让它收到微信消息,就需要将微信消息发到如下 URL:
https://keycloak.jiwai.win/realms/Brickverse/QrCodeResourceProviderFactory/mp-qr-status
你当然可以直接在服务器地址 URL 里配置上述地址,但是不推荐。因为上述地址是用来接收所有微信服务通知的,而这些通知不仅仅包含带参二维码的扫码消息,所以推荐配置一个专门的服务,然后在该服务下可以配置二次转发地址,这样就更加灵活了。
一个具体的例子,答疑家使用了nginx 服务器统一接收所有的微信通知,将在 nginx 中通过设置将消息中带有“Ticket”的带参二维码消息二次转发到以上的 Keycloak URL,这里分享出来供大家参考: nginx #微擎server server{ listen 50091; #server_name _ index index.html index.htm index.php default.html default.htm default.php; root /home/wwwroot/wx.example.com/pros;

include enable-php-pathinfo.conf;

location ~ .*.(gif|jpg|jpeg|png|bmp|swf)$
{
    expires      30d;
}

location ~ .*.(js|css)?$
{
    expires      12h;
}

location ~ /.well-known {
    allow all;
}

}

#路由server server{ listen 50090; index index.html index.htm index.php default.html default.htm default.php;

location ^~ /api.php {
    set $backend ;
    rewrite_by_lua_block {
        ngx.req.read_body()
        local body = ngx.req.get_body_data() -- 读取请求body
        if string.find(body, Ticket) then
            -- 找到Ticket字符串
            ngx.var.backend = https://auth.example.com/realms/DaYiJia/QrCodeResourceProviderFactory/mp-qr-scan-status
        else
            ngx.var.backend = http://127.0.0.1:50091
        end
    }
    proxy_pass         $backend;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
    proxy_set_header   content-type     application/xml;  #必须加上,否则keycloak返回错误信息
}

location / {
    proxy_pass http://127.0.0.1:50091/; #默认转发到微擎server
    proxy_set_header   Host      $host;
    proxy_set_header   X-Real-IP        $remote_addr;
    proxy_set_header   X-Forwarded-For  $proxy_add_x_forwarded_for;
}

location /hello_lua {
    # lua测试
    default_type text/plain;
    content_by_lua ngx.say(hello, lua);
}

access_log /home/wwwlogs/wx.example.com.log main;
error_log  /home/wwwlogs/wx.example.com.error.log error;

add_header Content-Security-Policy upgrade-insecure-requests;

}

同样地,如果使用 Authing.cn 的服务,那么需要对应地将微信消息转发给 authing.cn 的服务器。

手机微信登录

即在手机端微信里,点开某网站链接后,进行的微信授权登录。

原理

这种微信登录方式,仍然使用了微信的公众平台,不过这时使用了 OAuth 2.0 / OIDC 的协议,从而可以很方便的集成,比起公众号关注即登录,要简单很多。

PC 端扫码登录/开放平台登录

这就是所谓的普通扫码登录,要注意它和前两种方式有一个很大的区别,那就是不再依赖公众平台。它使用的是微信开放平台,要接入的话,有另一套独立的申请和认证流程。不过,好在一旦接入,它和手机微信登录一样,也是遵循 OAuth 2.0/OIDC 标准的,因此对接非常简单,基本上只需要对标准的 OIDC 库做一些 URL 配置即可。

总结

梳理了微信的 3 种登录接入方式,其中,公众号关注即登录最复杂,因为它是基于对微信的能力所做的一种灵活运用,微信官方文档并没有直接提及该登录方式。而另外两种登录方式分别应用于手机 Web 和 PC Web,虽然都是基于 OAuth 2.0/OIDC 标准,因此对接相对简单,但是要注意它们分别依赖公众平台和开放平台,彼此独立。
从费用上讲,你可以这样认为:如果只做手机 Web,那么一年需要 300 块的认证费用,花在公众平台上;如果只做 PC Web,那么一年也是需要 300 块的认证费用,花在开放平台上。但如果既想在手机 Web 上接入微信登录,同时又在 PC 端上接入,那么一般来说就需要一年 600 元的认证费用,同时认证公众平台和开放平台。如果想在手机 Web 和 PC Web 上都接入微信登录,但只想认证一个平台,即每年 300 元,那么可以不接入开放平台,而仅仅只接入公众平台,这时在 PC Web 上的微信登录就得另辟奚径,比如通过公众号的带参二维码来实现,这就需要更大的开发成本。不过通过借助https://github.com/Jeff-Tian/keycloak-services-social-weixin 或者 Authing.cn,就不需要开发了,而只需要将微信消息转发到你部署的 Keycloak 或者 authing.cn 服务器即可。
当然,通过实现自定义的微信登录,带来的不仅仅每年节省区区 300 元的成本,而是可以将粉丝和网站用户实现自动化的一一对应,这方面的好处远比 300 元要大得多。