tencent cloud

腾讯云超级应用服务

动态与公告
【2025年1月2日】关于腾讯云小程序平台更名为腾讯云超级应用服务的公告
控制台更新动态
Android SDK 更新动态
iOS SDK 更新动态
Flutter 更新动态
IDE 更新动态
基础库更新动态
产品简介
产品概述
产品优势
应用场景
购买指南
计费概述
按量计费(后付费)
续费指引
停服说明
快速入门
套餐管理
概述
控制台账号管理
存储配置
加速配置
品牌化配置
平台功能
控制台登录
用户和权限体系
小程序管理
小游戏管理
应用管理
商业化
平台管理
用户管理
团队管理
运营管理
安全中心
代码接入指引
Demo 及 SDK 获取
Android
iOS
Flutter
App 服务端接入指南
GUID 生成规则
小程序开发指南
小程序介绍与开发环境
小程序代码组成
指南
框架
组件
API
服务端
JS SDK
基础库
IDE 使用指南
小游戏开发指南
指南
API
服务端
实践教程
小程序登录实践教程
小程序订阅消息实践教程
支付相关实践教程
广告接入实践教程
小游戏订阅消息实践教程
相关协议
数据处理和安全协议

小程序登录实践教程

PDF
聚焦模式
字号
最后更新时间: 2025-12-25 14:51:57

前言

在 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 用户可以使用同一套凭证在不同的小程序中登录,提高了用户体验和满意度。

帮助和支持

本页内容是否解决了您的问题?

填写满意度调查问卷,共创更好文档体验。

文档反馈