ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

微信扫码登录完整教程:从开放平台申请到前后端联调

微信扫码登录完整教程:从开放平台申请到前后端联调 做PC网站的人基本都逃不过微信扫码登录这一关。很多朋友在后台问我扫码登录到底怎么接为什么用开放平台而不是公众号回调地址到底怎么配Vue前端怎么把二维码弹出来这一篇就专门把这些事说透。如果你已经看过我之前写的准备篇应该知道PC端微信扫码登录的本质让用户用手机微信扫一扫电脑屏幕上的二维码授权后PC端拿到一个临时凭证code再拿这个code去微信服务器换用户的身份标识从而建立你自己的登录态。这一步替代了“输入账号密码”省去了用户记忆密码的负担也减少了被撞库的风险对转化率有明显的正向作用。今天这篇重点放在工程实现上包括开放平台申请、Vue前端拉起二维码、后端换取openid、回调联调以及我在真实项目里踩过的那些坑。1. 先搞清楚微信扫码登录的运行逻辑1.1 扫码登录到底在“登录”什么很多人一开始容易懵扫码之后是登录微信吗并不是。微信扫码登录里微信只负责两件事第一确认“你是这个微信号的主人”第二把微信号关联的唯一标识openid和部分用户资料给到你的网站。第三方网站拿到openid后在自己的用户表里找到或创建对应账号生成自己的session或者token这才是真正的“登录”。所以整个流程拆开看就是四步前端展示二维码微信服务器轮询二维码状态用户扫码并授权微信前端页面跳转到你的回调地址并携带一个code后端用这个code去微信接口换token、拉用户信息。这个code是一次性的有效期大概5分钟且只能使用一次。理解了这个模型后后面所有代码都顺理成章。1.2 接入前必须备好的三个参数不管前端还是后端都绕不开三个参数AppID、AppSecret、回调域名。这三个参数在微信开放平台创建“网站应用”后获得。AppID是应用的公开标识前端拉起二维码时要用它。AppSecret是后端换取token的密钥必须保存在服务器上绝对不能出现在浏览器端代码里否则任何人拿到它都能冒充你的应用去调微信接口。回调域名是微信授权后跳转回来的地址白名单只有在这个域名下的redirect_uri才能成功回调。这三个参数缺一个扫码登录都跑不起来。还有一个容易忽略的点微信网站应用扫码登录要求你的网站必须通过ICP备案。个人开发者也能申请开放平台但网站应用需要审核审核周期大概1到7个工作日。要是公司主体申请还需要营业执照等材料。2. 开放平台申请与网站应用配置实操2.1 创建网站应用的完整流程打开微信开放平台官网用已认证的开发者账号登录。如果你没有开放平台账号先用邮箱注册然后进行开发者认证。个人开发者和企业开发者都可以认证但企业号的接口权限更全审核也更快。认证通过后进入“管理中心 - 网站应用 - 创建网站应用”。创建时需要填写应用名称、简介、图标以及最重要的“授权回调域”。注意这里填的是域名不要带https://也不要带路径。举例你的回调地址是 https://api.example.com/auth/wechat/callback那授权回调域只要填写 api.example.com。而实际的redirect_uri参数中再带全路径即可微信会把两部分拼接起来校验。提交审核后通常一个工作日左右就能收到审核结果。审核通过后在应用详情页就能看到AppID和AppSecret。AppSecret只显示一次建议立即保存到服务器的环境变量或配置中心不要写在代码仓库里更不要传到GitHub。2.2 回调域名的校验文件比想象中更重要在网站应用详情页微信会要求你配置“授权回调域”并下载一个校验文件比如 MP_verify_xxxxxx.txt把这个文件放到域名根目录下确保可以通过 https://api.example.com/MP_verify_xxxxxx.txt 访问到然后点击验证。很多项目出事就出在这一步文件放到了子目录或者因为前端框架的路由拦截导致txt文件返回了HTML验证必然失败。我一般建议直接用Nginx区域配置单独处理校验文件不要走应用服务器。配置大致如下location /MP_verify_xxxxxx.txt { default_type text/plain; alias /data/webroot/MP_verify_xxxxxx.txt; }验证通过后这个校验文件不要删。后续如果你修改授权回调域微信还会要求重新校验。万一误删了重新下载放回去即可不会影响线上登录。3. Vue前端如何拉起微信扫码二维码3.1 引入微信官方JS文件微信官方提供了一个PC端扫码登录的JS组件全称是“微信开放平台-网站应用-扫码登录”。在Vue项目的index.html中直接引入官方脚本script srchttps://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js/script这个脚本会向全局挂载一个WxLogin构造函数。使用时你只需要传入配置项它就会在指定容器内渲染出一个带二维码的iframe。这个iframe是微信自己控制的你无法修改内部样式只能通过外层容器控制位置和大小。需要注意这个JS文件偶尔会因为网络原因加载失败。我为保险起见是在页面组件的 mounted 钩子里按需加载避免影响首屏性能。具体实现后面会给出。3.2 核心代码与参数逐个拆解先看一段我在真实项目里用过的Vue 2组件代码逻辑同样适用于Vue 3只需把生命周期和this写法对应替换template div div idwechat-login-container/div /div /template script export default { name: WechatLogin, data() { return { appId: 替换为你的AppID, redirectUri: encodeURIComponent(https://api.example.com/auth/wechat/callback), scope: snsapi_login, state: , }; }, created() { // 生成随机state防止CSRF this.state Math.random().toString(36).slice(2) Date.now(); }, mounted() { this.loadWxScript() .then(() { new window.WxLogin({ self_redirect: false, id: wechat-login-container, appid: this.appId, scope: this.scope, redirect_uri: this.redirectUri, state: this.state, style: , href: , }); }) .catch(err { this.loadFailed true; }); }, methods: { loadWxScript() { return new Promise((resolve, reject) { if (window.WxLogin) { resolve(); return; } const script document.createElement(script); script.src https://res.wx.qq.com/connect/zh_CN/htmledition/js/wxLogin.js; script.onload resolve; script.onerror reject; document.head.appendChild(script); }); }, }, }; /script这段代码里有几个关键参数值得单独说。self_redirect官方文档解释为“ true 表示手机扫码后点击确认登录时直接在当前页面跳转到redirect_urifalse 则会在新窗口打开redirect_uri”。实际项目里PC端应当设为 false因为如果设为 true你需要在当前页面自己接收code处理流程复杂且容易丢状态。用新窗口打开回调页后端种cookie或返回token更方便。scope网站应用扫码登录固定传snsapi_login不需要请求用户授权额外资料微信会弹出一个确认登录的页面。如果你需要获取昵称头像也必须用这个scope然后通过用户信息接口拉取。state这是防CSRF的关键。前端生成一个随机字符串回调时微信会原样带回后端必须校验这个值和发起登录时保存的一致。推荐把state存到cookie或者sessionStorage里回调时比对。有些项目嫌麻烦就写死一个固定值这在安全上是不可接受的后面我会专门讲这个坑。href这个参数可以传入一个自定义CSS文件的URL用来调整二维码弹层的样式。不过实际效果有限而且会引入外部样式依赖我没在正式项目里用过。3.3 二维码过期与状态回跳处理微信官方生成的二维码有效期是5分钟。如果用户迟迟不扫码二维码会显示“二维码已失效”。此时没有官方回调通知前端只能靠轮询或用户手动刷新。我习惯在组件里加一个5分钟的定时器到期后重新new一个WxLogin实例。注意重新渲染前先清空容器内容并销毁旧实例否则会叠加多个iframe。const timer setTimeout(() { document.getElementById(wechat-login-container).innerHTML ; new window.WxLogin({ ... }); }, 5 * 60 * 1000);扫码后新窗口跳回调地址原页面二维码会显示“已扫描”。这个状态也是微信在iframe内自己控制的你不需要额外处理。真正需要前端关心的是回调页拿到code之后怎么回到原页面。我在项目里用了一个过渡页回调页把code传给后端后端校验通过后返回token前端再通过location.replace(https://www.example.com/login?tokenxxx)回到原页面这样能让用户感知“登录成功了”。4. 后端回调与登录态对接4.1 回调接口的设计思路回调地址https://api.example.com/auth/wechat/callback接收微信跳转带来的两个参数code和state。这个接口必须是无状态的因为它可能被微信服务器或用户浏览器直接访问。我的做法是写一个专门的Controller逻辑分四步第一步校验state是否和发起登录时的值一致第二步用code去微信接口换access_token和openid第三步用openid查本地用户表没查到就创建第四步生成自己系统的token并重定向到前端登录成功页。校验state时因为前端把state存在了sessionStorage而回调页是同一个域名下的新窗口可以直接通过window.opener.sessionStorage.getItem(wechat_state)读取。但更稳妥的做法是后端在拉取二维码前下发state并缓存在Redis里key是statevalue是过期时间或用户临时代号。回调时后端自己比对这样前端就算被劫持也无法伪造。4.2 用code换取access_token和openid微信的接口地址是https://api.weixin.qq.com/sns/oauth2/access_token?appidAPPIDsecretSECRETcodeCODEgrant_typeauthorization_code我用自己的服务端语言以Node.js为例发起请求const axios require(axios); async function getAccessToken(code) { const url https://api.weixin.qq.com/sns/oauth2/access_token; const { data } await axios.get(url, { params: { appid: config.wechat.appId, secret: config.wechat.appSecret, code, grant_type: authorization_code, }, }); if (data.errcode) { throw new Error(微信换取token失败: ${data.errcode}-${data.errmsg}); } return data; // { access_token, expires_in, refresh_token, openid, scope } }注意这里返回的access_token是用户级token和公众号里的全局access_token不是同一个东西。用户级access_token的有效期是7200秒如果你只需要openid其实这个token用完就可以丢弃不用保存。接着调用用户信息接口https://api.weixin.qq.com/sns/userinfo?access_tokenACCESS_TOKENopenidOPENIDlangzh_CN返回的字段包括openid、nickname、headimgurl、unionid等。在开放平台下如果你有多个应用网站、公众号、小程序绑定了同一个开放平台账号它们的unionid是唯一的可以用来打通各端的账号体系。个人建议优先用unionid作为用户唯一标识但前提是你确实需要多端统一如果只做PC网站直接用openid就够了。4.3 建立本地登录态并关联用户拿到openid和用户信息后就开始处理你自己的业务登录逻辑。先查询用户表select * from user where wechat_openid ?。如果不存在就创建一个新用户并保存昵称、头像。这里注意用户首次扫码时可能不想注册很多网站会在创建账号后弹一个“绑定手机号”的页面把微信登录和手机号绑定。这个属于业务设计不在这里展开。创建或找到用户后最重要的一步是签发自己的token。推荐使用JWT把userId和会话有效期放进去也可以把token存Redis。为了安全建议生成一个随机的sessionId作为key而不是直接把JWT给前端后就不管了。因为JWT无法主动失效万一用户要退出登录而前端只删了本地token服务端其实还认这个token存在安全隐患。我习惯用Redis实现登录态const sessionId uuid.v4(); await redis.setex(session:${sessionId}, 7 * 24 * 3600, JSON.stringify({ userId, openid })); // 返回给前端 res.redirect(https://www.example.com/login?token${sessionId});前端拿到sessionId后写进localStorage之后所有请求的header中带上Authorization: Bearer token。后端中间件统一解析并校验。这样退出登录时只要调用接口删除Redis里的key即可能彻底失效。5. 常见问题与排查技巧实录5.1 redirect_uri参数错误与编码问题这是新手最容易踩的坑。微信报错信息大概是“redirect_uri参数错误”。说到底只有两个原因一是你传入的redirect_uri没有做URL编码二是回调域名和你提交的授权回调域不一致。redirect_uri在拉起二维码时必须先通过encodeURIComponent处理。因为微信内部会把这个参数拼在它的授权页面URL里如果其中有?或请求串就会错乱。例如const redirectUri encodeURIComponent(https://api.example.com/auth/wechat/callback?fromhome);另一个原因是“授权回调域”只填域名但回调地址挂在子路径下比如api.example.com/auth这没问题但如果你的回调地址用了www.example.com而授权回调域填了api.example.com就会出现参数错误。排查时一眼就能看出来。5.2 二维码显示不出来或样式错乱最常见的问题是容器高度不够。微信的iframe默认会撑开约300x400的尺寸如果你外层div设置的高度小于400二维码就会被截断。可以给容器设置明确的宽高#wechat-login-container { width: 300px; height: 400px; margin: 0 auto; }其次是WxLogin脚本还没有加载完成就执行了new WxLogin()。如果你直接在mounted里同步执行就很可能报WxLogin is not defined。我前面用Promise按需加载能完美规避这个问题。另外如果你在组件销毁时没有清理定时器并且用户在二维码页面内做了刷新偶尔会出现两个二维码重叠。处理办法是在beforeDestroy时清除定时器并清空容器。5.3 扫码后回调慢或失败用户在手机上确认后微信会跳转回调地址这个过程一般在1到3秒。如果超过10秒还没跳转可能是微信服务器到你的服务器网络有问题或者你的回调接口代理配置有问题。排查时可以看后端日志确认是否收到了GET /auth/wechat/callback?codexxxstatexxx。如果一直没有收到大概率是授权回调域配置不对或者你在微信开放平台填的回调地址和你前端传的不一致。如果收到了但页面报错多半是后端拿code换token的环节触发了微信的限流。微信对code换token的接口有频率限制一般按秒为维度正常业务远达不到但如果你的回调接口被人恶意刷就会触发临时封禁此时接口会返回errcode: 45011之类的限制错误。5.4 本地开发环境如何调试微信登录微信授权的回调地址必须是你备案过的域名所以本地localhost无法直接回调。我推荐用内网穿透工具把本地服务暴露到公网但不想引导使用不合规工具。更稳妥的方案在测试环境部署一个临时域名专门用于微信登录联调。本地开发时前端把回调地址指向测试环境测试环境的后端再代理回本地代码。也就是说微信只认测试域名但实际处理逻辑能打到你的本地服务。或者更简单在后端加一个“模拟微信回调”的调试接口本地直接用这个接口伪造code换取token的流程只要保证最终建立的登录态一致即可。联调微信的真实授权流程放在测试环境验证本地专注业务逻辑。6. 安全性加固与上线前必查清单6.1 别无视state参数的防CSRF作用扫码登录有一个经典攻击方式攻击者诱导用户访问一个带有微信登录的网站然后通过某种方式让用户扫码授权攻击者再利用这个授权code去绑定自己账号从而盗用用户身份。加了随机的state后后端比对state和会话中的值不一致就能直接拒绝。这是最基本的安全底线。不要使用前端可预测的state比如固定日期、递增数字。应当使用密码学安全的随机数。前端生成即可但后端必须存储和校验。如果你前后端分离可以让前端先调用POST /api/login/wechat/state后端生成state并存入Redis然后返回给前端拉起二维码时带上。回调时后端直接查Redis比对最安全。6.2 登录态有效期与多端互踢微信的token有效期是7200秒但你自己签发的登录态取决于业务需求。一般网站会选择7天到30天。用Redis存session时给每个session设置过期时间同时在每次请求的中间件中做一次滑动续期比较麻烦我一般固定7天过期到期重新登录。多端互踢是根据业务决定的。有些产品允许同一账号多处登录有些则要求用户只能在一处登录。如果要求互踢只需要在用户表加一个current_session_id登录时更新每次请求校验session是否等于current_session_id不是就踢出。这比遍历所有session高效得多。6.3 上线前检查清单列一份我每次上线前都要过的清单直接抄第一确认AppSecret没有出现在前端代码、Git仓库、日志里。可以在GitHub搜索一下你的AppSecret如果泄露立即在开放平台重置。第二确认回调地址全部是HTTPS且证书没有问题微信回调时如果遇到证书链不完整也会失败。第三确认授权回调域没有被前端的SPA路由拦截。某些Vue项目的history模式会把MP_verify_*.txt也当成前端路由导致校验文件无法访问。第四确认线上Nginx对回调解接口不设重定向、不设CDN缓存。第五确认后端对code只能使用一次不管换取token成功还是失败再次使用同一code必须拒绝。第六确认用户信息接口的异常处理如果拉取用户信息失败不要阻断登录至少要保证openid能建立会话。6.4 微信登录后续还能扩展什么接入扫码登录只是第一步。后续可以考虑这几个方向第一用unionid打通公众号、小程序、App的用户体系这时候开放平台的作用就体现出来了你只需要在开放平台绑定你的公众号和小程序各端的用户就能自动识别为同一人。第二在扫码登录页加一个“微信绑定手机号”的引导把微信身份和手机号绑定后即使微信账号异常用户也能通过手机号找回账号。第三引入设备指纹和风控策略针对频繁扫码、异地登录等行为做二次验证。我还做过一个比较实用的优化在扫码二维码周围展示用户当前设备信息比如IP归属地、浏览器版本让用户扫码前先确认环境无误再点击授权。这个对提升安全感很有帮助实现也不复杂前端从后端拉一次即可。我在实际项目中把微信扫码登录从0到1完整落地过两遍第一遍踩了不少坑第二遍把流程固化成了配置项。如果你是按这篇文章一步步走的应该能避开我当年至少需一周才能摸索出的那些问题。记住最核心的三件事授权回调域正确、code只能一次、state必须校验。把这三件事做好微信扫码登录就成功了大半。后面如果遇到具体报错欢迎带着错误码来交流。
返回列表