微信登录体系这几年持续收紧:getUserProfile 收回了、头像昵称获取改填写了、手机号要企业认证了。网上大量教程已经过时。本文基于当前有效的接口体系,实现一套完整的登录授权方案,并覆盖 H5 与 App 端的差异。
一、当前微信登录接口的正确认知
先纠正几个过时认知:
| 接口 | 现状 |
|---|---|
wx.getUserInfo | 已回收,返回匿名数据 |
wx.getUserProfile | 已于 2022 年后收紧,新版本基本不可用 |
| 头像昵称 | 官方推荐用「头像昵称填写能力」(open-type 按钮 + input type=nickname) |
getPhoneNumber | 可用,但需要企业主体小程序,且按次收费 |
wx.login 换 openid | 正常可用,这是静默登录的基础 |
结论:openid 换 token 做静默登录 + 头像昵称用户主动填 + 手机号授权按钮,是当前唯一合规的组合。
二、静默登录:openid 链路
用户打开小程序不需要任何操作就完成注册/登录:
前端 wx.login 获取 code
→ POST /auth/silent-login { code }
→ 后端 code2session 换 openid + session_key
→ 后端按 openid 查/建用户 → 签发 token
→ 前端存 token,静默登录完成// stores/user.js
async silentLogin() {
const [err, res] = await uni.login({ provider: 'weixin' })
if (err) return
const data = await request.post('/auth/silent-login', { code: res.code })
this.token = data.accessToken
this.isNewUser = data.isNewUser // 后端标记:openid 没绑定过手机号
}后端 code2session 注意点:code 只能用一次、5 分钟有效;session_key 千万不能下发到前端(安全隐患,微信明确禁止);unionid 需要绑定开放平台才能拿到,多端账号打通靠它。
三、头像昵称填写:官方推荐方案
微信现在要求用户"主动填写"头像和昵称,配套了两个专用 UI 能力:
头像:button open-type="chooseAvatar"
<template>
<button class="avatar-btn" open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<image class="avatar" :src="avatarUrl || '/static/default-avatar.png'" mode="aspectFill" />
<text class="avatar-tip">点击选择头像</text>
</button>
</template>
<script setup>
import { ref } from 'vue'
const avatarUrl = ref('')
function onChooseAvatar(e) {
// 临时文件路径,需要上传到自己的服务器换取永久 URL
avatarUrl.value = e.detail.avatarUrl
}
</script>关键:e.detail.avatarUrl 是临时路径,小程序重启即失效,必须立刻上传:
async function onChooseAvatar(e) {
const tempPath = e.detail.avatarUrl
uni.showLoading({ title: '上传中' })
try {
const url = await uploadFile(tempPath) // 走自己的上传接口
avatarUrl.value = url
} finally {
uni.hideLoading()
}
}昵称:input type="nickname"
<template>
<view class="form-item">
<text class="label">昵称</text>
<input
v-model="nickname"
type="nickname"
placeholder="请输入昵称"
@blur="onNicknameBlur"
/>
</view>
</template>
<script setup>
import { ref } from 'vue'
const nickname = ref('')
function onNicknameBlur(e) {
// type=nickname 的 input 在部分机型 v-model 同步不及时,blur 时兜底读取
nickname.value = e.detail.value
}
</script>type="nickname" 会唤起微信官方的昵称快捷填写键盘(自动带入微信昵称),这是目前唯一合规的获取昵称方式。
提交完善资料
async function saveProfile() {
if (!avatarUrl.value) return uni.showToast({ title: '请选择头像', icon: 'none' })
if (!nickname.value.trim()) return uni.showToast({ title: '请填写昵称', icon: 'none' })
await request.post('/me/profile', {
avatar: avatarUrl.value,
nickname: nickname.value
})
userStore.fetchProfile()
uni.showToast({ title: '保存成功' })
setTimeout(() => uni.navigateBack(), 800)
}四、手机号授权:企业认证方案
getPhoneNumber 需要 button 触发,且小程序必须完成微信认证(企业主体):
<template>
<button
class="phone-btn"
open-type="getPhoneNumber"
@getphonenumber="onGetPhone"
>
授权手机号登录
</button>
</template>
<script setup>
async function onGetPhone(e) {
const detail = e.detail
// 用户拒绝授权
if (!detail.code) {
return uni.showToast({ title: '您取消了授权', icon: 'none' })
}
// 新版接口:detail.code 交给后端,后端调 getuserphonenumber 换手机号
const data = await request.post('/auth/bind-phone', { code: detail.code })
userStore.userInfo = data.user
uni.showToast({ title: '登录成功' })
}
</script>当前流程(2023 之后的版本):
用户点击授权按钮
→ e.detail.code(动态令牌)
→ POST /auth/bind-phone { code }
→ 后端用 code + access_token 调微信接口换真实手机号
→ 绑定用户,返回更新后的用户信息注意事项:
- 个人主体小程序用不了这个能力,认证费用 300 元/年,手机号验证按次计费(约 0.03 元/次)
- 旧版
encryptedData + iv解密方案还能用但不推荐,code 方案更安全且免维护密钥 - 计费压力大的场景可以改做短信验证码登录(自建),绕开微信计费
短信验证码登录(自建方案)
<template>
<view class="sms-login">
<view class="input-row">
<input v-model="phone" type="number" maxlength="11" placeholder="手机号" />
</view>
<view class="input-row">
<input v-model="smsCode" type="number" maxlength="6" placeholder="验证码" />
<button class="sms-btn" :disabled="countdown > 0" @click="sendSms">
{{ countdown > 0 ? `${countdown}s后重试` : '获取验证码' }}
</button>
</view>
<button class="login-btn" @click="loginBySms">登录</button>
</view>
</template>
<script setup>
import { ref, onUnmounted } from 'vue'
const phone = ref('')
const smsCode = ref('')
const countdown = ref(0)
let timer = null
async function sendSms() {
if (!/^1[3-9]\d{9}$/.test(phone.value)) {
return uni.showToast({ title: '手机号格式错误', icon: 'none' })
}
await request.post('/auth/sms/send', { phone: phone.value })
countdown.value = 60
timer = setInterval(() => {
if (--countdown.value <= 0) clearInterval(timer)
}, 1000)
}
async function loginBySms() {
const data = await request.post('/auth/sms/login', {
phone: phone.value,
code: smsCode.value
})
userStore.token = data.accessToken
userStore.userInfo = data.user
uni.reLaunch({ url: '/pages/index/index' })
}
onUnmounted(() => timer && clearInterval(timer))
</script>后端要点:验证码 5 分钟有效、同一手机号 60 秒内不可重发、验证失败 5 次锁定、按手机号 + IP 双维度限流防刷。
五、多端登录差异
// utils/auth.js —— 收敛各端登录入口
export async function doLogin() {
// #ifdef MP-WEIXIN
const [err, res] = await uni.login({ provider: 'weixin' })
return request.post('/auth/wx-login', { code: res.code })
// #endif
// #ifdef APP-PLUS
// App 端:一键登录(运营商授权)
const [loginErr, loginRes] = await uni.login({ provider: 'univerify' })
return request.post('/auth/univerify-login', {
accessToken: loginRes.authResult.access_token,
openid: loginRes.authResult.openid
})
// #endif
// #ifdef H5
// H5 端:微信公众号网页授权
const appId = 'wx_xxx'
const redirect = encodeURIComponent(location.href)
location.href =
`https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}` +
`&redirect_uri=${redirect}&response_type=code&scope=snsapi_userinfo#wechat_redirect`
// #endif
}H5 回调页解析 code 换 token:
// H5 授权回调页面
onLoad() {
const code = new URLSearchParams(location.search).get('code')
if (code) {
const data = await request.post('/auth/h5-wx-login', { code })
userStore.token = data.accessToken
}
}六、登录拦截的优雅实现
全局拦截而非每页手写判断。方案是封装路由跳转 + 页面 meta 声明:
// pages.json 页面需要登录的加 custom 字段(或维护一个白名单数组)
const LOGIN_REQUIRED = ['pages/cart/cart', 'pages/order/list']
// 重写跳转方法统一拦截
const originalNavigateTo = uni.navigateTo
uni.navigateTo = function(options) {
const path = options.url.split('?')[0].replace(/^\//, '')
const userStore = useUserStore()
if (LOGIN_REQUIRED.includes(path) && !userStore.isLoggedIn) {
return originalNavigateTo({
url: `/pages/login/login?redirect=${encodeURIComponent(options.url)}`
})
}
return originalNavigateTo(options)
}登录成功后回跳:
async function handleLoginSuccess() {
const redirect = decodeURIComponent(
new URLSearchParams(location.search).get('redirect') ||
getCurrentPagesArgs('redirect') || ''
)
uni.reLaunch({ url: redirect || '/pages/index/index' })
}总结
- 静默登录靠 wx.login + code2session,session_key 留在服务端
- 头像用
open-type="chooseAvatar",昵称用type="nickname",临时文件必须立即上传 - 手机号授权需要企业认证 + 计费,自建短信验证码是省钱替代
- 登录入口用条件编译分端收敛,重写 navigateTo 做全局登录拦截
- 常见过时方案自查:getUserProfile、encryptedData 解密、无企业认证却调 getPhoneNumber
登录授权是合规重灾区,本文方案基于当前有效接口,建议每半年对照微信官方文档核对一次。
评论 (0)