✨ 前言
最近就在给我的网站增加微信扫码登录能力。但经过一番调研发现,微信官方的扫码登录方案要么要营业执照,要么每年交 300 块认证费。我这个简单的小项目犯不着搞这些复杂的东西。
最终经过一番查找,我发现用微信小程序就能完美绕过这些限制。
效果就是网站弹出一个小程序码:
手机微信扫一扫,网页端自动登录:
点赞关注,接下来我们就一起看看如何实现吧~
其他方案的限制
在开始之前,先来看下主流方案的对比表格:
| 方案 | 前置条件 | 费用 | 体验 |
|---|---|---|---|
| 微信开放平台(网站应用) | 企业营业执照 + 开发者资质认证 | 300 元(一次性) | 最标准,扫码直接授权 |
| 公众号扫码登录 | 认证服务号 | 300 元/年 | 扫码关注后登录 |
| 小程序 | 个人实名即可注册 | 0 元 | 扫码进小程序,确认后登录 |
- 其实微信开放平台是最正统的方案,用户扫码后直接在微信里弹授权确认,体验也最丝滑。
- 但它要企业资质,个人开发者压根注册不了
- 公众号方案则需要认证服务号,一年 300,而且还得先有个公司主体
- 而小程序方案只需要个人身份证实名注册,不需要花一分钱就能搞定
小程序不认证的代价是不能被搜索、不能开通支付、也不能被分享。
但咱们就是拿它做个登录跳板,用户只通过扫码进入,完全不需要这些功能。
原理:一码扫完,网页怎么就登录了?
整体流程大概是这样:
简单看下核心步骤:
- ticket 是中转凭证,每次打开登录弹窗生成一个随机 ticket(32 位 hex),存到 Redis,设 5 分钟过期
- 小程序码通过
getwxacodeunlimit接口生成,scene字段里塞的就是 ticket,用户扫码后小程序从scene里取出来 - 小程序端调
wx.login()拿到 code,连同 ticket 一起发给后端,后端用 code 调微信的code2session接口换到 openid,完成用户身份绑定 - 网页每隔 1~2 秒请求后端“这个 ticket 登录了没”,一旦后端标记成功,前端拿到登录态,弹窗关闭
核心就是,通过小程序从微信获取唯一标识,绑定到我们的网站里,实现一个唯一标识(微信号)对应一个用户。
有个细节得说明一下,小程序从微信获取到的唯一标识叫 openid,openid 只在一个应用里唯一。
什么意思呢?
就是说,假如一个用户在我的小程序里的 openid 是 123,那么我的小程序里不会有其他用户的 openid 是 123 了,但是不影响别人的小程序里有用户的 openid 是 123。
动手实现
1)注册个人小程序
去微信公众平台注册一个小程序账号,只需要实名 + 微信扫码验证就可以了:
注册完拿到 AppID 和 AppSecret,后面要用。
后端实现
这里用 Node.js 示例,其他语言同理。
生成 ticket + 小程序码:
// POST /api/auth/qr/createasync function createQrLogin() { const ticket = crypto.randomBytes(16).toString('hex') // 32位 hex // 存 Redis,5 分钟过期 await redis.set(`login:${ticket}`, JSON.stringify({ status: 'pending' }), 'EX', 300) // 调微信接口生成小程序码 const accessToken = await getAccessToken(APP_ID, APP_SECRET) const res = await fetch( `https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token=${accessToken}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ scene: ticket, page: 'pages/login/login', // 小程序里的登录确认页 check_path: false, width: 280, }), } ) const imageBuffer = await res.arrayBuffer() return { ticket, qrcode: Buffer.from(imageBuffer).toString('base64') }}这里有三个关键点:
scene最长只能 32 个可见字符,所以用 16 字节 hex 刚好check_path: false让生成码时不校验页面是否已上线,方便开发阶段调试- 返回的是图片二进制,转 base64 给前端展示
轮询状态接口:
// GET /api/auth/qr/status?ticket=xxxasync function checkStatus(ticket: string) { const data = await redis.get(`login:${ticket}`) if (!data) return { status: 'expired' } const { status, token } = JSON.parse(data) if (status === 'confirmed') { await redis.del(`login:${ticket}`) // 一次性,用完删掉 return { status: 'confirmed', token } } return { status: 'pending' }}确认登录接口(给小程序调的):
// POST /api/auth/mini/confirmasync function confirmLogin(ticket: string, code: string) { // 1. 校验 ticket 存在且未过期 const data = await redis.get(`login:${ticket}`) if (!data) throw new Error('二维码已过期') // 2. 用 code 换 openid const session = await code2Session(APP_ID, APP_SECRET, code) const { openid } = session // 3. 查找或创建用户,生成登录 token const user = await findOrCreateUser(openid) const token = generateToken(user) // 4. 更新 ticket 状态 await redis.set(`login:${ticket}`, JSON.stringify({ status: 'confirmed', token }), 'EX', 60) return { success: true }}code2Session 就是调微信的 https://api.weixin.qq.com/sns/jscode2session,传 appid、secret、js_code,换回 openid。
小程序侧
小程序只需要一个"登录确认页",用户扫码后会自动跳到这个页面。
// pages/login/login.jsPage({ onLoad(options) { // 从小程序码的 scene 里取 ticket const ticket = decodeURIComponent(options.scene || '') if (!ticket) { wx.showToast({ title: '二维码无效', icon: 'error' }) return } this.confirmLogin(ticket) }, async confirmLogin(ticket) { try { // 1. wx.login 拿 code const { code } = await wx.login() // 2. 请求你的后端 confirm 接口 const res = await wx.request({ url: `https://your-domain.com/api/auth/mini/confirm`, method: 'POST', data: { ticket, code }, }) if (res.data.success) { wx.showToast({ title: '登录成功', icon: 'success' }) setTimeout(() => wx.navigateBack(), 1500) } } catch (err) { wx.showToast({ title: '登录失败', icon: 'error' }) } },})页面 UI 可以做得简单一点:显示个 loading 动画 + "正在确认登录..."的文案就行,用户感知上就是扫完码等一秒就好了。
前端:展示二维码 + 轮询
// 伪代码,React 示例function LoginModal() { const [qrcode, setQrcode] = useState('') const [ticket, setTicket] = useState('') useEffect(() => { // 请求后端生成小程序码 fetch('/api/auth/qr/create', { method: 'POST' }) .then(res => res.json()) .then(({ ticket, qrcode }) => { setTicket(ticket) setQrcode(qrcode) }) }, []) useEffect(() => { if (!ticket) return // 每 1.5 秒轮询 const timer = setInterval(async () => { const res = await fetch(`/api/auth/qr/status?ticket=${ticket}`) const data = await res.json() if (data.status === 'confirmed') { clearInterval(timer) // 拿到 token,登录成功! login(data.token) } else if (data.status === 'expired') { clearInterval(timer) // 提示过期,让用户刷新 } }, 1500) return () => clearInterval(timer) }, [ticket]) return ( <div> <img src={`data:image/png;base64,${qrcode}`} /> <p>请用微信扫一扫</p> </div> )}注意事项 & 踩坑
scene 只有 32 字符。不要想着往里塞 JWT 或者 URL,只放一个 ticket ID 就行,其他信息让后端通过 ticket 去 Redis 里查。
小程序必须已发布。getwxacodeunlimit 生成的码指向的是正式版小程序。如果你的登录页还没发布上线,用户扫了会报"页面不存在"。开发阶段可以设 check_path: false 先把码生成出来,但真机测试还是得至少上个体验版。
本地调试怎么搞? 不需要真机扫码。在微信开发者工具里添加一个编译模式,启动页面填 pages/login/login,启动参数填 scene=你的ticket,就能模拟扫码进入的效果。
真机测试走局域网。手机访问不了你电脑的 localhost,小程序里的 API 地址要改成你电脑的局域网 IP(比如 192.168.1.8:3000),同时开发者工具里关掉"域名校验"。
access_token 要缓存。微信的 access_token 有效期 2 小时,别每次生成码都重新获取,用 Redis 缓存一下。
本站资源大多来自网络,如有侵犯权益请联系管理员,我们会第一时间审核删除。站内资源仅供学习测试,未经许可禁止商用,请在24小时内删除。
遇到付费内容?升级终身VIP即可全站免费畅享所有资源,可以联系我的微信进行开通。
大海聊天 QQ 3群:478065589








子比主题美化
精品源码资源 

暂无评论内容