tencent cloud

小程序登录实践教程
最后更新时间:2025-11-07 18:00:44
小程序登录实践教程
最后更新时间: 2025-11-07 18:00:44

前言

在 superapp 的业务生态发展到一定阶段,越来越多的第三方业务以小程序的方式在 superapp 内为 superapp 的用户提供丰富的服务,每个第三方小程序都可能有自己的用户体系,superapp 该如何简化用户登录和保障用户数据的安全性呢?我们为 superapp 打造了一套适用于小程序(或小游戏)登录实现方案,旨在通过建立一个高效且安全的登录体系,让 superapp 用户可以在 superapp 生态系统内快速且无缝地访问各种小程序。以微信小程序为例子,微信小程序登录功能一般会通过 OpenID 或 UnionID 作为唯一标识,与小程序服务的账号体系进行关联打通,完成用户账户体系的构建与设计。
针对上述场景,您可通过以下方案来实现:

流程说明

1. 小程序发起 wx.login 请求。
2. SDK 通过 superapp 获取一个当前已登录的凭证 accountId,然后对 OpenServer 发起获取临时 code 请求。
iOS:通过 getAppUID 代理接口实现,iOS 设置用户 ID
Android:通过 getAccount 代理接口返回,Android 设置用户 ID
3. OpenServer 请求 superapp 后端验证用户 ID(accountId) 的正确性。
4. OpenServer 获取验证结果后生成临时 code 返回 SDK。
5. 小程序前端获取到 code 传到小程序后端服务。
6. 小程序后端通过 jscode2Session 接口换取 openid 和 session_key,并维护用户和 openid 关系。


登录方案

1. superapp 服务端改造

登录流程中需要 superapp 后端服务提供以下的接口来支持登录能力。

2. SDK 版本更新

小程序登录功能在 2.1.0 版本支持,如果您的 SDK 版本低于此版本,请先进行版本升级。

3. 控制台-应用管理配置服务域名

在您的 superapp 中实现小程序统一登录的功能,您需要先在控制台-应用管理-配置管理中,配置 superapp 的服务域名。详情请参见 配置管理

4. 小程序接入登录功能

如果您的小程序是一个纯工具类的应用,不需要在服务端保存任何用户信息,则没有必要接入登录功能。
但在大多数业务场景下,小程序需要获取用户的「唯一用户标识」,用于在服务端数据库中记录用户在小程序内的各种行为数据。当用户更换设备或重新登录后,依然可以从服务端重新拉取之前的数据。典型的应用场景有:订单、购物车、积分、浏览记录等。
针对上述的业务场景,我们建议小程序开发者参照下方的流程来接入实现功能:

4.1 控制台-小程序管理生成密钥

每个小程序都需要在小程序管理-开发管理-密钥管理生成一个密钥,用于接入登录功能时的身份校验。详情请参见 小程序管理-开发管理-密钥管理

4.2 小程序前端

4.2.1 小程序登录
小程序可以通过官方提供的标准化登录能力方便地获取宿主 APP 提供的用户身份标识,快速建立小程序内的用户体系。
登录流程时序图

小程序中调用 wx.login() 获取 临时登录凭证 code ,并回传到小程序后端服务。
小程序后端服务调用 jscode2Session 接口,换取用户唯一标识 OpenID 和会话密钥 session_key。
小程序后端服务可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份和登录态。
注意:
会话密钥 session_key 是对用户数据进行加密签名的密钥。为了应用自身的数据安全,小程序后端服务器不应该把会话密钥下发到小程序,也不应该对外提供这个密钥。
临时登录凭证 code 只能使用一次。
示例代码
WXML
JAVASCRIPT
<button bindtap='login' loading="{{isLoading}}">Authorized login</button>
import Config from './utils/configData';
const app = getsuperapp();
Page({
data: {
isLoading: false,
},
login: function () {
this.setData({
isLoading: true
})
wx.login({
success: (res) => {
console.log('wx.login success===', res)
if (res.code) {
wx.request({
url: `${Config.BASEURL}/getUserInfo`,
method: "POST",
data: {
appid: Config.APPID,
code: res.code
},
success: (res) => {
this.setData({
isLoading: false
})
console.log('wx.request success===', res)
const { code = -1, data = {} } = res?.data || {};
if (code === 200) {
wx.showToast({
title: 'Logged in successfully',
icon: 'success',
duration: 500
})
// Or store via wx.setStorage
app.globalData.userInfo = {
avatarUrl: data.avatarUrl,
account: data.account,
nickName: data.userName,
id: data.id,
token: data.token,
phoneNumber: data.phone,
emailAddress: data.email
}
setTimeout(() => {
wx.navigateBack({
delta: 1
})
}, 500)
} else {
const msg = res?.data?.data?.msg || '/getUserInfo request fail'
const errcode = res?.data?.data?.errcode || code
console.log('/getUserInfo request fail', res)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: `/getUserInfo fail:${msg}[code:${errcode}]`,
showCancel: false
})
}
},
fail: (err) => {
this.setData({
isLoading: false
})
console.log('wx.request fail', err)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
},
})
} else {
this.setData({
isLoading: false
})
console.log('wx.login does not return code', res)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: res.errMsg,
showCancel: false
})
}
},
fail: (err) => {
this.setData({
isLoading: false
})
console.log('wx.login fail===', err)
wx.showModal({
title: 'Login failed',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
}
})
}
})
4.2.2 获取手机号和邮箱地址
该能力旨在帮助小程序开发者向用户发起手机号或邮箱地址申请,并且必须经过用户同意后,开发者才可获得手机号或邮箱地址,进而为用户提供相应服务。
使用说明

获取手机号码
:需要将 button 组件open-type的值设置为getPhoneNumber,当用户点击并同意之后,通过bindgetphonenumber事件获取回调信息。将回调信息中的动态令牌 code 通过自定义接口(例如示例代码中的/getUserPhone)传到小程序后端服务,并在小程序后端服务调用平台OpenServer提供的 getuserphonenumber 接口,消费 code 来换取用户手机号。


获取邮箱地址
:需要将 button 组件open-type的值设置为getEmailAddress,当用户点击并同意之后,通过bindGetEmailAddress事件获取回调信息,将回调信息中的动态令牌 code 通过自定义接口(例如示例代码中的/getUserEmailDirect)传到小程序后端服务,并在小程序后端服务调用平台OpenServer提供的 getemailaddress 接口,消费 code 来换取用户邮箱地址。

注意:
getPhoneNumber 及 getEmailAddress 返回的 code 与 wx.login 返回的 code 作用是不一样的,不能混用。
每个 code 有效期为5分钟,且只能消费一次。
示例代码
WXML
JAVASCRIPT
新选项
<view>
<button
open-type="getPhoneNumber"
bindtap="clickGetPhoneNumber"
bindgetphonenumber="handleGetPhoneNumber">
Get Phone Number
</button>
<button
open-type="getEmailAddress"
bindtap="clickGetEmailAddress"
bindgetemailaddress="handleGetEmailAddress">
Get Email Address
</button>
</view>
import Config from './utils/configData';
var app = getsuperapp();

Page({
data: {
phoneNumber: '',
emailAddress: '',
},
clickGetEmailAddress() {
wx.showLoading();
},

clickGetPhoneNumber() {
wx.showLoading();
},

handleGetPhoneNumber(e) {
console.log('getPhoneNumber success===', e.detail)
const { code, errMsg } = e.detail
if (code) {
wx.request({
url: `${Config.BASEURL}/getUserPhone`,
method: "POST",
data: {
appid: Config.APPID,
code,
token: app.globalData.userInfo.token // Login state
},
success: (res) => {
wx.hideLoading()
console.log('getPhoneNumber request success===', res)
const { code = -1, data = {}, msg } = res?.data || {};
if (code === 200) {
this.setData({
phoneNumber: data?.phoneNumber
})
} else {
const msg = res?.data?.data?.msg || res?.data || '/getPhoneNumber request fail'
const errcode = res?.data?.data?.errcode || code
console.log('/getPhoneNumber request fail', res)
wx.showModal({
title: 'Failed to retrieve phone number',
confirmText: 'Confirm',
content: `/getPhoneNumber fail:${msg}[code:${errcode}]`,
showCancel: false
})
}
},
fail: (err) => {
wx.hideLoading()
console.log('wx.request fail', err)
wx.showModal({
title: 'wx.request fail',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
},
})
} else {
wx.hideLoading()
console.log('getPhoneNumber does not return code', e.detail)
wx.showModal({
title: 'getPhoneNumber fail',
confirmText: 'Confirm',
content: errMsg,
showCancel: false
})
}
},

handleGetEmailAddress(e) {
console.log('getEmailAddress success===', e.detail)
const { code, errMsg } = e.detail
if (code) {
wx.request({
url: `${Config.BASEURL}/getUserEmailDirect`,
method: "POST",
data: {
appid: Config.APPID,
code
},
success: (res) => {
wx.hideLoading()
console.log('getEmailAddress request success===', res)
const { code = -1, data = {}, msg } = res?.data || {};
if (code === 200) {
this.setData({
emailAddress: data?.emailAddress
})
} else {
const msg = res?.data?.data?.msg || res?.data || '/getEmailAddress request fail'
const errcode = res?.data?.data?.errcode || code
console.log('/getEmailAddress request fail', res)
wx.showModal({
title: 'Failed to retrieve email address',
confirmText: 'Confirm',
content: `/getEmailAddress fail:${msg}[code:${errcode}]`,
showCancel: false
})
}
},
fail: (err) => {
wx.hideLoading()
console.log('wx.request fail', err)
wx.showModal({
title: 'wx.request fail',
confirmText: 'Confirm',
content: err.errMsg,
showCancel: false
})
},
})
} else {
wx.hideLoading()
console.log('getEmailAddress does not return code', e.detail)
wx.showModal({
title: 'getEmailAddress fail',
confirmText: 'Confirm',
content: errMsg,
showCancel: false
})
}
}
})

4.2.3 获取头像和昵称
当小程序需要让用户完善个人资料时,可以通过平台提供的头像昵称填写能力快速完善。
使用说明

头像选择
:需要将 button 组件 open-type 的值设置为chooseAvatar,当用户选择需要使用的头像之后,可以通过bindchooseavatar事件回调获取到头像信息的临时路径。


昵称填写
:需要将 input 组件 type 的值设置为nickname,当用户在此 input 进行输入时,键盘上方会展示宿主APP昵称,用户点击昵称后可以自动填充到 input 输入框中,通过 input 的bindchange事件可以获取到最新的 value 值。

注意:
在开发者工具上,input 组件是用 web 组件模拟的,因此部分情况下并不能很好的还原真机的表现,建议开发者在使用到原生组件时尽量在真机上进行调试。
示例代码
WXML
JAVASCRIPT
WXSS
<view class="userInfo-container">
<view class="settingItem">
<text class="caption">Profile Picture</text>
<view class="avatar-empty"></view>
<button class="headerButton" open-type="chooseAvatar" bind:chooseavatar="onChooseAvatar">
<image src="{{userHead}}" class="userHead" mode="aspectFill" />
</button>
</view>
<view class="settingItem">
<text class="caption">Nickname</text>
<input
value="{{nickName}}"
type="nickname"
class="value"
placeholder="Please enter a nickname"
bindchange="nickNameChange" />
</view>
</view>
import Config from './utils/configData';
var app = getsuperapp();

Page({
data: {
userHead: '',
userHeadBase64: '',
nickName: '',
},
onChooseAvatar(e) {
console.log('onChooseAvatar===', e)
const { avatarUrl } = e.detail
if(avatarUrl.includes('/tmp')) {
const fs = wx.getFileSystemManager();
fs.readFile({
filePath: avatarUrl,
encoding: 'base64',
success: (data) => {
this.setData({
userHeadBase64: 'data:image/png;base64,' + data.data,
userHead: 'data:image/png;base64,' + data.data
})
},
fail: (err) => {
console.error('readFile error===', err);
}
});
}
if(avatarUrl) {
this.setData({
userHead: avatarUrl,
})
}
},

nickNameChange(e) {
console.log('nickNameChange===', e.detail.value)
this.setData({
nickName: e.detail.value
})
}
})
Page {
width: 100%;
min-height: 100vh;
background: #f2f2f2;
display: flex;
flex-direction: column;
}

.userInfo-container {
display: flex;
flex-direction: column;
}

.settingItem {
display: flex;
width: 100%;
box-sizing: border-box;
flex-direction: row;
background: white;
border-bottom: #dedede solid 1px;
padding: 26rpx;
align-items: center;
}

.avatar-empty {
flex: 1;
}

.headerButton {
padding: 15rpx;
width: 46px !important;
height: 46px !important;
border-radius: 8px;
}

.userHead {
width: 100%;
height: 100%;
pointer-events: none;
}

.settingItem .caption, .settingItem .value {
font-size: 30rpx;
color: #333;
}

.settingItem .value {
flex: 1;
text-align: right;
}

4.3 小程序服务端

小程序后台需要实现上面小程序调用的 `${Config.BASEURL}/getUserInfo` 接口,该接口的作用是通过 code 换取一个唯一的用户标识,并且建立小程序自己的登录态。
作为小程序后台,需要调用 OpenServer 提供的 jscode2session 方法,获取一个唯一 OpenID, OpenID 可以用来确认唯一身份,并且每个用户对于某一个小程序来说是不会改变的。

后台获取到 OpenID 以后应该返回必要的用户信息和登录态 session(该 session 可由小程序后台自定义过期时间),通常 openid 不要返回给前端小程序。

下面给出调用 jscode2session 的样例 (仅示例)
注意:
调用地址请从控制台获取,详情请参见 OpenServer
GET https://xxx.xxx.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
另外小程序后台还需要实现 getAccessToken 接口,token 有效期为7200s,开发者需要进行妥善保存, access_token 通常保存在后台,
access_token 的有效期目前为 2 个小时,需定时刷新,重复获取将导致上次获取的 access_token 失效;建议开发者使用中控服务器统一获取和刷新 access_token,其他业务逻辑服务器所使用的 access_token 均来自于该中控服务器,不应该各自去刷新,否则容易造成冲突,导致 access_token 覆盖而影响业务;
access_token 的有效期通过返回的 expires_in 来传达,目前是7200秒之内的值,中控服务器需要根据这个有效时间提前去刷新。在刷新过程中,中控服务器可对外继续输出的老 access_token,此时多端后台会保证在5分钟内,新老 access_token 都可用,这保证了第三方业务的平滑过渡;
access_token 的有效时间可能会在未来有调整,所以中控服务器不仅需要内部定时主动刷新,还需要提供被动刷新 access_token 的接口,这样便于业务服务器在API调用获知 access_token 已超时的情况下,可以触发 access_token 的刷新流程。

方案优势

快速集成:superapp 后台对接产品后台,无需自行开发小程序的登录鉴权功能,仅需做少量适配工作。
用户数据统一:发布到 superapp 内的小程序都使用同一套登录机制,确保了 superapp 和小程序用户数据的一致性和准确性。
提升用户体验:superapp 用户可以使用同一套凭证在不同的小程序中登录,提高了用户体验和满意度。
本页内容是否解决了您的问题?
您也可以 联系销售 提交工单 以寻求帮助。

文档反馈