概述
最近笔者参与维护的一个应用系统,由于客户的问题,域名备案出了问题,无法使用了。相关的手机客户端系统受到了影响,使用的微信认证方式也失效了。
这个系统原来的实现方式比较奇特,微信认证这个模块,是依靠在另一个应用上实现的,这个应用也没有再继续了,现在只是作为这个系统的认证模块使用。这样,所有的相关认证的方式,都需要做调整。简单而言,就是需要在本系统复现微信认证流程。笔者实现了这个调整,觉得这个过程中有一些比较有趣的地方,可以值得分享一下,同时也作为一个工作笔记进行要点的记录。
原理
笔者原来对微信认证相关的实现,只是有一个理论和初步上的认知,并没有实际完整的实现和操作过,这次有了实现的机会,觉得其实原理并不复杂,而且如果能够充分理解,再配合一下网络安全和编程方面的知识,实现起来也比较简单顺畅。
先说原理。
笔者总结,从应用的流程来看,大致如下:
1 用户需要先关注一个微信公众号,关注后,关于此公众号,用户会得到一个标识,就是openid。当然,这个标识并不是显式的,用户无法看到,但程序会使用它来标识当前微信用户在当前公众号上的身份
2 随后,用户微信公众号界面中,看到一个菜单项目,它实际上会连接到一个页面,即应用入口和授权页面,从这里可以发起应用的用户授权和应用,这时候,逻辑上而言openid也是用户在应用中的账号(或者映射)
3 用户点击菜单项目,会在微信(浏览器)中打开这个页面,如果当前用户尚未在应用中登录(可以是session判断),则会启动(微信)认证过程,这个过程大致分为两个阶段
4 第一阶段,入口页面将按照预先的设定,重定向到一个微信服务提供的地址,这个重定向的参数包括了公众号的标识(APPID),和应用设定的返回地址
5 微信服务在进行相关处理后,会再次重定向浏览器返回到返回地址,但这个过程是由微信服务处理的,它会对应用进行检查,如应用公众号是否有效,当前微信用户是否关注等等,由于这个环境是微信服务的环境,它会知晓当前关联的微信账号,然后生成一个相关的authcode(认证代码),并附加到返回地址中
6 浏览器将回到返回地址,但带有authcode,由于返回地址是由应用系统提供和处理的,它会在URL中,获得这个authcode,这时第一阶段就算是基本完成了。简单而言第一阶段的目的就是将用户导引到微信服务,并获取一个authcode
7 第二阶段开始,应用服务获取authcode之后,可以在后台发起一个认证请求(HTTP GET),请求地址同样由微信服务提供,请求信息主要就是预先设置好的APPID、APPSEC(公众号密钥)和AuthCode。这个操作在应用服务后端执行,从用户看起来就是重定向请求挂起和等待处理
8 微信认证服务系统收到这个请求,对APPID和authcode进行验证,确认此请求确实是关联的公众号发起的,然后就可以基于authcode内容响应当前微信用户在公众号的openid
9 应用服务获得微信服务的响应,其中带有当前微信用户在公众号的openid,就可以以此作为用户标识进行后续应用了。至此第二阶段结束。简单而言就是应用服务使用authcode在应用后台对微信服务进行请求,微信服务对应用服务进行验证后(APPID/APPSEC),基于authcode响应用户openid。
10 应用服务获取当前用户的openid之后,要如何操作,就是应用实现自己的问题了,其实和微信本身是没有关系的。比如,具体的操作可以是基于一个映射关系,关联实际的应用的用户;或者再次使用openid进行应用中进行二次认证;或者再次作为参数重定向到另一个应用中(笔者的应用设定就是如此);这些都取决于业务需求和场景。
至此,微信认证的大致原理就是这样。其实,这个认证过程基本上也遵循常用的认证技术就是OAuth2。其原理框架如下图:

可能读者会觉得这个图画的实在不怎么样?!那就对了,因为这是claude生成的,虽然确实不怎么样,但大体的关系和意思确实表达出来了。读者也可以试试,chatGTP、Grok和元宝应该更加惨不忍睹。
为什么要这样设计和实现?因为OAuth2的设计目标为了解决不同应用之间认证和互相的问题的,要考虑到相关的互操作和安全问题,这个认证机制的特性包括:
- 用户无需向第三方应用提供密码,从而保护账户安全
- 授权码具有时效性和一次性,可以防止重放攻击
- 访问令牌可限制权限范围,实现了最小权限原则
- 支持令牌刷新和撤销,便于权限管理
从这些角度来看,OAuth2的原理开放清晰,安全性高,完全基于HTTP协议,实现简单方便,应用广泛。已经基本上已经成为互联网行业应用认证集成的事实标准。所以,类似的其他各种平台系统的认证,基础思想基本上也是一样的。
理解了原理之后,下面就可以笔者的实践过程为例,来具体了解一下,在真实环境中,是如何进行实现的。
准备工作
如果是从完全零开始微信认证集成的开发工作的话,在开始之前,需要一些前期的准备。
应用服务
在笔者的应用环境中,和微信认证有关的应用方面的调整和开发主要包括:
- 认证入口地址
就是在服务号应用菜单项目,对应的那个地址。用户可以在关注公众号后,方便的通过公众号菜单点击,进行微信认证后,进入业务应用程序。
- 业务应用和地址
笔者的应用程序本身是一个中立的MobileWeb应用,并不依赖微信的环境,只是为了方便用户使用,借用微信的自动认证过程,简化用户登录过程。用户本身是有账号的,但可选在微信认证后进行关联,以后都可以通过微信环境来开展应用。
所以,应用的地址也是一个独立的地址。但需要有一个机制,在用户完成微信认证后,来接收这个认证信息(这里是就是简单的openid)。现在的实现方式,就是在一个应用URL的路径上,简单的加入一个openid的参数,然后在应用端的Web应用中,增加相关的参数解析、再验证和账号关联等方面的操作。
但对于认证过程而言,就是简单的在获取Openid后,将此作为参数加入到业务应用提供的地址,并且引导用户浏览器进行重定向操作。
由OAuth的原理可知,在逻辑上,认证入口和业务应用确实是可以分离并且应该分离的。但在实际操作中,基于应用部署方便的考虑,笔者实际上是将认证入口作为业务应用的一个模块进行部署的,表现为这个入口就是应用程序下面的一个路径。
公众号/服务号
显然,要开展微信应用,需要去微信公众平台去申请公众号或者服务号。具体过程和详细内容不在这里讨论。我们只讨论,在笔者的应用中,和微信认证集成相关的设置主要有下面几个地方:
- 服务号菜单项目
服务号菜单项目,让用户可以方便的通过菜单来访问应用系统,而非像普通应用Web那样手动输入地址。管理员可以配置这些菜单和应用地址的关联关系。具体位置和设置是:
互动管理-自定义菜单-跳转网页-网页链接(图)

在这里配置认证和程序的入口,名称就是出现在菜单上的标题;类型是"跳转网页";并且提供入口地址。
- 网页授权
网页授权其实是一个安全控制策略。它可以控制认证过程中,相关操作,应当被限制在这些域名空间当中。在应用认证集成中的很多错误,比如"Request_URI"等,都和这个设置是否正确相关。
网页授权设置的具体位置是:
设置与开发-账号设置-功能设置-网页授权域名(图)

点击网络域名栏中的"设置",可以打开具体设置页面(图):

这里要注意,设置这个授权域名时,并不是简单的编辑域名信息就可以了,实际上微信服务是要对这个域名的有效性和安全性进行验证的。所以在正式编辑时,需要进行一个额外的操作。就是需要用户下载一个验证文件(xxx.txt)。然后将这个文件,放置在域名应用的服务器上,让微信服务可以以"域名+验证文件名"的方式,来访问这个文件中的内容(纯文本,其实就是签名或者随机信息)。这个操作可以证明用户确实拥有该域名,因为可以修改域名下的内容。一般情况下,用户也可以修改DNS记录来实现这个,但这种方式显然更有可操作性。
微信网页授权的内容可以是一个域名,也可以是域名+路径。使用路径是为了更加灵活。笔者的环境中就是这样。笔者的业务应用没有拥有一个完整域名,而是一个域名下的路径,通过网关进行代理和转换。
为了避免反复,用户有必要在保存前,先自己验证一下,就是使用浏览器来访问这个文件,看是否正常访问和获取其中的内容。
在笔者的实际环境中,由于是使用一个nginx作为前端代理,同时可以提供Web文件服务,这方面并没有遇到太大的问题。但如果是一个纯的后端程序的话(比如一个nodejs开发的API接口),稍微麻烦一点,可能需要做一些调整,来模拟访问Web文件的方式。
最后,按照中国互联网管理的规定,这些域名应当先进行备案,才能作为有效域名。
- APP参数
应用的微信认证程序,需要使用APP参数来作为请求信息,让微信认证服务知晓当前的请求标识。APP参数的获取和设置位置在:
设置与开发-开发接口管理-基本配置-开发者ID/开发者密码(图):

APP参数用于微信服务对业务应用进行验证。主要包括APPID和APPSEC。APPID是一个固定的信息;基于安全的考虑,微信平台不会保存开发者密码的原始内容,只有在生成的时候,展示给开发者进行记录。
获取这些信息之后,开发者应当将其配置在其认证集成的程序代码当中。
在做好这些准备工作之后,开发者就可以进入实际的编码和配置阶段了。由于笔者的业务系统是原始基于PHP的,所以这里先讨论一下如何在PHP应用中来实现。
PHP版本实现
笔者的PHP应用,并不是纯粹的PHP代码,而是基于CodeIgnite框架。所以,实现方式基本上是扩展了一个标准的CI控制器,来处理一个路径请求(Wechat)。先看一下相关的参考实现代码:
wechat.php
<?php
/**
* WeChat login and direct controller
*/
const WX_APPID = 'xxx';
const WX_APPSEC = 'yyy';
const ENTRY_URL = 'http://entry.url'; // 认证入口地址
const APP_URL = 'http://app.url'; // 业务应用地址
class Wechat extends CI_Controller {
public function __construct() {
parent::__construct(false);
$this->load->library('curl');
}
public function index(){
$code=$this->input->get('code');
if (empty($code)) { // get code
$url="https://open.weixin.qq.com/connect/oauth2/authorize?appid=".WX_APPID."&redirect_uri=".urlencode(ENTRY_URL."/wechat/")."&response_type=code&scope=snsapi_userinfo&state=ENTRY#wechat_redirect";
//如果用户同意授权,页面将跳转至 redirect_uri/?code=CODE&state=STATE
redirect($url);
exit;
} else {
//获取openid
$openid = $this->getOpenId($code);
//携带openid跳转到第三方应用系统
redirect(APP_URL.'?openid='.$openid);
}
}
//作用:获取用户OPENID和网页access_token
private function getOpenId($code=''){
////获取OPENID和网页授权access_token凭证
$url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=".WX_APPID."&secret=".WX_APPSEC."&code=".$code.'&grant_type=authorization_code';
$output = $this->curl->_simple_call('get', $url);
$b = json_decode($output);
$openid=$b->openid;
return $openid;
}
}
稍微解释一下:
- 此文件直接放置在CI项目的Controllers文件夹中,即可生效
- 基于控制器和类名称,外部访问路径为 http://entry_url/wechat (在授权页面名字空间之下)
- 简单起见,除框架(CI)和curl之外,没有外部依赖和代码
- 简单起见,也没有其他的方法和路径,而是直接使用默认方法(index)
- 使用是否包括code参数,来区分认证阶段
- 在笔者的环境中,使用认证结果的方式是,获取openid之后,重定向到目标应用的URL
- 认证应用和目标应用可以不是同一个域名空间或者应用环境
- 移植时,如果环境相同,只需要修改基础参数即可
至此,笔者应用场景中的微信认证集成就已经完成了。但基于这个原理,如果考虑到,如果将微信认证这个功能抽象出来,作为一个标准服务的话,应当部署成为一个独立的应用。
笔者在这方面也做了简单的探索,就是使用nodejs实现了另外一个js的版本,而且是可以独立工作的。
JS版本实现
下面就是依照相同的原理和流程,实现的JS代码版本,而且在另一个环境中进行了部署,达到了相同的效果:
wechat.js
// 引用
const
http = require('http'),
https = require('https'),
{ URL } = require('url');
// 配置
const CONF = {
port : 8011,
host : "127.0.0.1",
appid : "XXX",
appsec : 'YYY',
entry_url : 'https://service.url',
app_url : 'http://app.url', // final app page
auth_url1 : "https://open.weixin.qq.com/connect/oauth2/authorize?appid=__APPID__&redirect_uri=__URL__&response_type=code&scope=sns$
auth_url2 : "https://api.weixin.qq.com/sns/oauth2/access_token?appid=__APPID__&secret=__APPSEC__&code=__CODE__&grant_type=authoriz$
};
// 封装http get promise
const simpleGet = (url)=> new Promise((r,v)=>{
https.get(url, (res) => {
let data = ''; // 接收数据
res
.on('data', (chunk) => { data += chunk; })
.on('end', () => {
let or = JSON.parse(data);
r(or?.openid);
})
.on('error', (err) => {
console.log("Error:", err.message);
r(null);
});
});
});
// 主处理方法
const handleWeb = async(req,res)=>{
console.log(req.url);
const code = new URL(req.url,"http://localhost").searchParams.get("code");
if (!code) { // get code
const url1 = CONF.auth_url1
.replace("__APPID__", CONF.appid)
.replace("__URL__", encodeURIComponent(CONF.entry_url));
res.writeHead(301, { Location: url1 }).end();
} else { // get openid
const url2 = CONF.auth_url2
.replace("__APPID__", CONF.appid)
.replace("__APPSEC__", CONF.appsec)
.replace("__CODE__", code);
const openid = await simpleGet(url2);
if (openid) { // everything ok !
res.writeHead(302, { Location: CONF.app_url+"?openid="+openid }).end();
} else {
res
.writeHead(200, {'Content-Type': 'text/plain; charset=utf-8' })
.end("OpenID Error");
}
}
}
// 创建并启动 HTTP 服务器
http.createServer(handleWeb)
.listen(CONF.port,CONF.host, () => {
console.log(`服务器已启动:http://${CONF.host}:${CONF.port}`);
}
);
简单解释一下:
- 为了方便修改和移植,使用单一的js文件,也无项目配置和任何外部依赖。
- 此文件可以直接由nodejs程序,文件可以独立执行
- 程序执行后,创建一个Web应用并侦听配置好的地址和端口
- 笔者的环境是通过nginx进行发布的,所以侦听本地地址和特定端口(和网关部署在一起),提高安全性
- 实际外部访问路径,由网关控制和映射,而且在程序中不做处理
- 基于兼容性的考虑,没有使用fetch方法,而是简单封装了simpleGet和JSON解析
一些细节和遗留问题
虽然在实际工作中的问题算是解决了,但笔者认为还是有一些其他值得探讨的问题的:
- 认证后安全
笔者的场景中的处理方式是,认证完成之后,使用openid作为参数,跳转到另一个应用系统。这里面稍微有点安全的问题。首先就是openid是暴露在URL当中的(特别是笔者的应用由于条件限制,还没有使用https协议)。所以,这部分应该对openid参数进行加密,使用验证系统的公钥进行签名,并且设置超时控制,来确保这个认证信息传递过程和后续认证信息使用的安全。
- 认证阶段分离
笔者的示例当中,两个认证阶段其实是写在一起的,这当然很方便。但实际上这两个部分逻辑上可以分离。入口在一个应用地址,微信认证完成后,可以返回另一个地址来处理openid的问题。
- 认证服务化
基于认证阶段的分离,就更容易实现微信认证的服务化。就是同样的认证方式,为不同的业务应用提供服务。入口可以配置在不同的业务应用当中,但进行微信验证时,统一使用相同的方式开展第二阶段的认证。
这种部署方式的好处是,各个业务系统不用自己单独开发微信认证的实现,只需要简单的配置和处理返回的openid信息即可。从而脱离和公众号管理和配置具体相关的工作,也不用记录公众号应用的敏感信息。从而提高系统安全和集成效率。
小结
本文基于笔者在实际工作中遇到的一次微信认证迁移的问题,探讨了在Web应用中,如何和微信认证集成的相关问题,包括基础原理和流程,微信公众号的设置和配置,并提供了参考PHP和JS代码。