
简介这是一份专为uni-app开发者准备的微信H5授权登录解决方案面向需要在小程序/H5端获取用户openId并实现注册登录的初学者与中级开发者。资源将微信网页授权流程封装为可直接调用的工具包含2个核心文件1个js工具函数、1个vue页面示例压缩包仅4KB轻量易懂。已有8158人学习下载备受实战项目开发者关注。通过该资源可掌握组装授权URL、处理回调参数、解析用户openId与用户信息等关键步骤js文件中封装了请求与参数处理逻辑vue示例演示了从授权启动到登录态写入的完整页面流程下载后替换配置即可快速接入项目同时也可作为理解微信H5授权机制的教学笔记适合边看边改、快速落地。1. 微信H5授权与uniapp为什么openId是移动端登录的基石项目上线时甲方提了个需求微信公众号菜单点进来用户不用注册直接用微信身份进入系统。这个需求落到技术上就是一件事——在uniapp打包的H5页面里拿到微信用户的openId再拿它去自家后端换登录token。看起来简单真正动手会发现微信H5授权和uni.login在小程序里的流程完全不同H5端没有现成的code获取方法必须自己完整走一遍微信OAuth2.0授权。下面基于一套已经封装好的utils工具类展开从公众号后台配置、scope选型、授权URL拼接、后端换openId到注册登录打通五个环节按顺序拆开每段代码都能直接复制进现有uniapp项目改造适合正在做公众号H5嵌入、需要免登录进入的团队。2. 授权前置公众号配置、回调域名与scope选型在写任何代码之前先确认三件事否则后面每一步都会出问题公众号必须是已认证的服务号或已认证订阅号页面必须在微信内置浏览器中打开以及公众号后台至少配置了一个网页授权域名。这三条缺一条整个OAuth2.0流程就起不来报错还千奇百怪比如redirect_uri参数错误或者干脆不跳转。2.1 AppID与AppSecret两个凭证各管什么登录公众号后台在「设置与开发-基本配置」页面拿到AppID和AppSecret。AppID是公开的前端拼授权链接时要用AppSecret只能出现在后端用code换openId的请求必须由后端发起。很多demo把AppSecret直接写进uniapp的常量文件里一旦前端代码打包上传到静态服务器等于把公众号密钥公之于众。拿到AppSecret的人可以调微信接口拉取粉丝数据、发模板消息这是比较严重的权限泄漏生产环境不能这么干。如果AppSecret真的泄漏了补救手段是在后台重置重置后旧密钥立即失效新密钥从后端配置中心同步不需要发版前端。这个操作是即时的用户无感知。2.2 网页授权域名与回调域名少配一个都会报错在「设置与开发-公众号设置-功能设置」里配置网页授权域名。微信要求这个域名必须是已ICP备案的完整域名不能是IP地址也不能带http://或https://前缀。配置完成后这个域名下所有页面路径都可以发起网页授权。实际开发里最容易遇到「本地能跑、线上白屏」根源大多是本地用了localhost它不在授权域名单里。我的做法是本地起一个和线上同域名的映射线上用yourdomain.com本地就在hosts里把dev.yourdomain.com指向127.0.0.1nginx反代到uniapp的devServer端口然后把dev.yourdomain.com也加进授权域名。这样本地调试和线上行为一致不会出现「本地随便跳、线上一个错」的割裂感。表网页授权配置项说明配置项位置示例值备注网页授权域名公众号后台-功能设置yourdomain.com不带协议前缀需ICP备案回调地址redirect_uri前端授权链接https://yourdomain.com/h5必须属于授权域名下AppID公众号后台-基本配置wx1234567890abcdef前端使用AppSecret公众号后台-基本配置64位十六进制字符串仅后端使用回调redirect_uri不一定非要单独做一个落地页直接把uniapp H5首页地址作为回调地址是常见做法。比如https://yourdomain.com/h5用户授权完成后微信会跳到https://yourdomain.com/h5/?codexxxstatexxxH5自己在onLoad里解析当前URL的code。这种方式省掉一个中间页打包资源也不需要为授权单独配路由。这里还要提醒一个公众号后台规则同一个网页授权域名在同一时间只能关联一个公众号。新项目申请出现「该域名已被使用」时需要先在原公众号后台解绑再重新绑定这个操作即时生效不需要联系微信审核。2.3 snsapi_base 还是 snsapi_userinfo按业务场景选型微信网页授权有两种scope选型直接影响用户体验和数据维度。snsapi_base是静默授权用户无感微信直接带着code跳回后端拿这个code只能换到openId拿不到昵称头像。大多数登录注册场景这个就够了因为openId就是用户在你们业务体系里的唯一标识。snsapi_userinfo授权时会弹窗用户必须点「允许」才能继续。code换到access_token后多了调用sns/userinfo接口的能力可以拉取微信头像、昵称、性别和地区。需要默认头像和昵称的注册页才值得付出多一次弹窗的代价。表两种scope的差异对比维度snsapi_basesnsapi_userinfo用户感知静默无弹窗弹窗确认返回内容openIdopenId、access_token、refresh_token能否拿用户资料不能能调用sns/userinfo适用场景登录、绑定、免登需要头像昵称的注册页一个容易被忽略的细节snsapi_userinfo模式下拿到的access_token有效期是2小时而openId是永久的。头像昵称只在注册那一刻需要拿到后应存入自己的用户表后续登录直接用openId匹配不需要再走一遍userinfo流程。如果项目将来要嵌入企业微信h5做免登那还需要同时采集unionId因为企业微信环境里的用户标识体系和公众号是两套openId不通用。3. utils.js封装uniapp侧微信授权工具的三个函数微信侧配好之后开始写前端。整套授权在uniapp里就是一个工具文件的几个函数对应项目里的utils/utils.js。我把它拆成三步拼链接、取参数、换openId。三个函数独立暴露任何页面要用直接引入即可。3.1 授权URL拼接getWxAuthorizeUrl的实现与参数说明第一步生成授权链接。这个URL指向open.weixin.qq.com的OAuth2.0授权端点参数必须按微信文档的键名拼一个都不能少。// utils/utils.js const WX_CONFIG { appId: wx1234567890abcdef, redirectUri: https://yourdomain.com/h5, scope: snsapi_base } // 生成每次唯一的state防CSRF function makeState() { return h5_ Date.now() _ Math.random().toString(36).slice(2, 8) } // 第一步拼接微信授权URL export function getWxAuthorizeUrl() { const state makeState() // state先存起来回调时比对防止第三方伪造授权请求 uni.setStorageSync(oauth_state, state) return https://open.weixin.qq.com/connect/oauth2/authorize ?appid WX_CONFIG.appId redirect_uri encodeURIComponent(WX_CONFIG.redirectUri) response_typecode scope WX_CONFIG.scope state state #wechat_redirect }这里三个细节别动错。第一redirect_uri必须用encodeURIComponent编码不编码时微信解析链接会把后续参数截断报redirect_uri参数错误。第二#wechat_redirect是微信H5授权链接的固定后缀没有它微信不跳转。第三state每次生成随机值并写入本地缓存回调时比对URL里的state是否一致这是防止CSRF的标准做法很多项目的遗漏点就在这里。表OAuth2.0授权链接核心参数参数是否必填说明appid是公众号AppIDredirect_uri是授权后跳转地址需URL编码response_type是固定为codescope是snsapi_base或snsapi_userinfostate否但建议自定义参数防CSRF#wechat_redirect是固定后缀H5授权必须带3.2 取code与换openId调用链路的完整拼装用户在微信里打开授权链接后会跳转到redirect_uri并携带code和state。第二步是从当前页面URL里把这两个参数解析出来然后把code发给后端。因为uniapp H5默认是hash路由授权完成后的完整URL是https://yourdomain.com/h5/?codexxxstatexxx#/。微信追加的参数落在#之前路由hash在#之后所以解析window.location.search就能拿到。下面的函数没有直接用URLSearchParams原因是部分低版本安卓内置浏览器的WebView支持不完整手动拆分query字符串兼容性最稳。// 第二步从当前URL解析code与state export function getWxAuthParams() { const query window.location.search.substring(1) const params {} query.split().forEach(item { const [key, value] item.split() if (key) params[key] decodeURIComponent(value || ) }) return params // { code: xxx, state: xxx } } // 第三步用code向后端换取openId export function requestOpenId(code) { return new Promise((resolve, reject) { uni.request({ url: https://yourdomain.com/api/wx/openid, method: POST, data: { code }, success: (res) { if (res.data.code 0) { resolve(res.data.data) // { openId, unionId? } } else { reject(new Error(res.data.msg)) } }, fail: (err) reject(err) }) }) }requestOpenId请求的是自家后端接口不是直接请求微信因为AppSecret不能暴露在前端前端直连微信接口还有跨域限制。后端接口返回统一结构{code: 0, data: {openId}}前端只关心code是否为0。还要注意code是一次性的有效期约5分钟长度约128字节微信文档没有固定长度。前端不要在业务代码里对code做长度硬编码后端收到40029 invalid code时应当作不可重试错误处理而不是直接发起重试。3.3 页面级调用pages/clue中的授权逻辑项目里pages/clue页面是实际使用这套工具的地方。典型场景是公众号菜单跳转进来先判断URL里有没有code有就直接换openId没有就跳授权。// pages/clue/index.vue import { getWxAuthorizeUrl, getWxAuthParams, requestOpenId } from /utils/utils.js export default { onLoad() { const params getWxAuthParams() if (params.code) { const localState uni.getStorageSync(oauth_state) if (localState params.state ! localState) { console.error(state校验失败疑似CSRF攻击) return } this.handleLogin(params.code) } else { // 没有code说明第一次进跳授权 window.location.href getWxAuthorizeUrl() } }, methods: { async handleLogin(code) { try { const data await requestOpenId(code) // data.openId 是当前微信用户在业务体系里的身份标识 uni.setStorageSync(openId, data.openId) } catch (err) { console.error(openId获取失败, err) } } } }state校验有个边界情况要处理好用户手动清空微信缓存后微信侧的静默授权关系还在但本地oauth_state已经没了。此时本地没有oauth_state就别阻断流程跳过一次校验只记录warning保证老用户不被卡在登录页。模板里用if (localState ...)而不是直接比就是这个原因。还要区分一个概念uni.login()返回的是小程序端的code只能在微信小程序环境里换openId。H5页面内调用uni.login不会走网页授权链路拿到的code对公众号H5完全无效。从App或小程序项目转过来做H5的团队最容易在这里绕弯H5端只认从URL里解析出来的这个code。4. 注册与登录openId打通用户体系的完整实现openId拿到后进入核心业务环节怎么把它映射成用户登录态。常规做法是后端用openId反查用户绑定表查不到就新建用户查到就复用老用户然后签发一个自定义token返回前端后续请求带着token走。4.1 后端接口设计code换openId再换token前端把code发给后端后后端要做的事是拿code去请求微信的sns/oauth2/access_token接口拿到openid。这个请求只能由后端发起因为需要AppSecret。下面以Node.js/Express为例// routes/wx.js const express require(express) const axios require(axios) const router express.Router() // POST /api/wx/openid —— 用code换openId router.post(/wx/openid, async (req, res) { const { code } req.body const wxUrl https://api.weixin.qq.com/sns/oauth2/access_token ?appid process.env.WX_APPID secret process.env.WX_SECRET code code grant_typeauthorization_code const { data } await axios.get(wxUrl) if (data.errcode) { return res.json({ code: 1, msg: data.errmsg }) } // openid是最核心的返回unionid需公众号绑定开放平台才有 return res.json({ code: 0, data: { openId: data.openid, unionId: data.unionid || , accessToken: data.access_token, expiresIn: data.expires_in } }) })响应里把access_token和expires_in透传给前端是为了在需要调snsapi_userinfo拿头像昵称时可以由前端或后端缓存后继续使用。注意data.unionid只有公众号绑定微信开放平台后才返回没绑定就是空字符串。openId一次获取后永久有效access_token有效期2小时refresh_token有效期30天设计缓存时要把这些有效期差异考虑清楚。表微信access_token接口返回字段字段说明有效期access_token网页授权接口调用凭证2小时expires_inaccess_token剩余秒数7200refresh_token刷新凭证可续期30天openid用户在当前公众号下的唯一标识永久unionid用户在开放平台下的唯一标识永久4.2 首次授权是注册、再次授权是登录拿到openId后后端业务逻辑就简单了查user_oauth_bind表。没有记录说明首次授权此时插入一条用户记录和一条绑定记录有记录说明是老用户直接走登录更新逻辑。代码示意如下// routes/auth.js —— 注册/登录合一 router.post(/auth/login, async (req, res) { const { openId, unionId } req.body if (!openId) return res.json({ code: 1, msg: 缺少openId }) let bind await db.query(SELECT * FROM user_oauth_bind WHERE open_id ?, [openId]) let userId let isNewUser false if (!bind) { // 首次授权同时创建users记录和绑定记录 const userResult await db.query( INSERT INTO users (nickname, avatar, status) VALUES (?, ?, 1), [微信用户, ] ) userId userResult.insertId await db.query( INSERT INTO user_oauth_bind (user_id, open_id, union_id, channel) VALUES (?, ?, ?, ?), [userId, openId, unionId || , wechat_h5] ) isNewUser true } else { userId bind.user_id // 老用户更新最后登录时间 await db.query(UPDATE users SET last_login_at NOW() WHERE id ?, [userId]) } // 签发自定义token后续请求带token即可 const token jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: 7d }) return res.json({ code: 0, data: { token, userId, isNewUser } }) })这段逻辑用user_oauth_bind中间表而不直接把openId当用户主键是为了以后多端接入做准备。如果以后接入小程序、或公司再开一个公众号同一个真实用户会有多个不同openId但通过user_id关联回同一条users记录用户数据不会裂成几个账号。channel字段记录来源渠道运营要分析公众号H5和App转化率时这个字段是唯一口径。isNewUser返回给前端后由前端决定注册流程是否继续。比如业务要求注册必须绑定手机号前端看到isNewUser true就跳手机号绑定页业务不强制就先进首页用户后续需要手机号时再触发绑定。4.3 前端登录状态管理从授权到进入业务页面前端把两个接口串起来一条调用链完成注册或登录。核心代码在pages/clue里扩展// pages/clue/index.vue 完整登录方法 async handleLogin(code) { try { // 1. code换openId const { openId, unionId } await requestOpenId(code) // 2. openId换token const loginRes await new Promise((resolve, reject) { uni.request({ url: https://yourdomain.com/api/auth/login, method: POST, data: { openId, unionId }, success: resolve, fail: reject }) }) if (loginRes.data.code 0) { const { token, userId, isNewUser } loginRes.data.data uni.setStorageSync(token, token) uni.setStorageSync(userId, userId) uni.setStorageSync(openId, openId) // 3. 按新老用户分流 if (isNewUser) { uni.showToast({ title: 注册成功, icon: none }) } uni.reLaunch({ url: /pages/index }) } } catch (err) { console.error(登录链路失败, err) } }所有关键登录态都通过uni.setStorageSync写入本地。后续发起业务请求时在uni.request拦截器里统一带token请求头后端按token解析用户身份不需要前端每次手动传openId。注意openId在本地存储只是方便调试和展示不能作为后端鉴权凭证服务端只认token。这里解释一下为什么用setStorageSync而不是setStorage。同步方法调用后立刻能读到值异步方法存在时序问题登录成功后马上要跳页用同步方法可以避免「跳转完成但token还没写完」的竞态。本地存储容量上限10MBtoken和openId加起来几十字节不存在容量问题。5. 高频授权问题的排查现场redirect_uri、code复用与授权恢复最后把实际项目里最容易碰到的三个问题列一下每一个都有明确的排查手段和修复方案。5.1 redirect_uri参数错误域名与编码的双重检查这个报错出现频率最高。排查按两条线走先打开公众号后台「网页授权域名」把它和代码里redirect_uri的域名做对比必须是同一个主域名比如后台配了yourdomain.comredirect_uri就不能是www.otherdomain.com再检查授权链接里redirect_uri有没有经过encodeURIComponent。微信对参数拼接很严格redirect_uri里只要有一个或?没编码微信解析时就会截断后续参数。实际项目里这两种情况同时存在的也不少先域名后编码一步步排除。排查时可以把完整授权链接贴到浏览器地址栏用Network面板看最终跳转URL的结构一眼就能看出微信把redirect_uri解析成了什么。5.2 code已被使用一次性凭证的幂等处理微信的code只能用一次有效期5分钟。前端如果因网络抖动导致请求超时后点了一次重试同一个code被提交两次后端第二次请求微信接口时会收到40029 invalid code。修复分前后端两层前端在会话里记录已处理过的code重复点击直接忽略后端也记录最近处理过的code命中缓存时直接返回第一次结果不再请求微信。两层各加一个防重判断这个错误基本能压到零。还要注意微信授权链接每访问一次就生成一个新code旧code立即失效。页面里不要用浏览器前进后退去还原授权页很容易拿到过期code正确做法是重新走一次授权链接获取新code。5.3 用户拒绝授权后的交互恢复只有snsapi_userinfo会触发拒绝授权。用户拒绝后微信会跳回redirect_uri但URL里没有code而是带一个error参数。前端判断逻辑要覆盖这层分支不能只写「有code走登录没code跳授权」否则用户拒绝后会被反复弹授权框体验很差。推荐交互是检测到URL里有error或code为空时显示一行说明文案再放一个自定义按钮用户点击时重新调用getWxAuthorizeUrl()跳授权。要注意微信对重复授权请求有频率限制连续多次拒绝后再点授权可能不再弹窗而是直接返回错误此时提示用户「请过几分钟再试」不要无脑跳转。最后补一个容易被忽略的边界整个授权流程依赖微信内置浏览器。如果用户用系统浏览器打开H5微信会跳到提示页显示「请在微信客户端打开链接」。遇到这种反馈先引导用户从公众号菜单入口进入普通浏览器地址栏访问在微信授权体系里走不通这是微信H5授权与App内WebView授权最大的行为差异排查问题时优先确认这一点。本文还有配套的精品资源点击获取