微信与企业微信获取手机号实现指南
核对日期:2026-07-29
示例技术栈:微信原生小程序、H5、Java 8、Spring Boot 2.2.x
1. 方案选择
微信登录、公众号授权和企业微信客户授权并不等于手机号授权。应先根据用户类型选择方案。
| 用户类型 | 推荐方案 | 能否直接获取手机号 |
|---|---|---|
| 普通微信用户、消费者 | 微信小程序 getPhoneNumber |
可以,需要用户点击授权 |
| 企业内部员工 | 企业微信 snsapi_privateinfo |
可以,需要管理员和员工授权 |
| 企业微信外部联系人、微信客户 | 小程序授权或短信验证 | 企业微信接口不能直接获取 |
| 公众号网页、普通微信 H5 | 小程序授权或短信验证 | 网页 OAuth 不返回手机号 |
推荐结论:
企业微信外部联系人的限制
企业微信客户详情中的 remark_mobiles 是企业成员手工备注的号码,不是微信验证后授权返回的手机号,不保证存在或准确,也不能作为正式的手机号认证结果。
2. 微信小程序获取手机号
2.1 实现流程
用户点击手机号授权按钮
-> 小程序获得一次性 code
-> 前端把 code 发送给业务后端
-> 后端获取并缓存小程序 access_token
-> 后端调用 getuserphonenumber
-> 微信返回手机号
-> 后端绑定当前登录用户并加密保存
关键限制:
- 小程序需要是非个人主体,并完成微信认证。
- 用户必须主动点击授权按钮,不能静默获取。
code有效期为 5 分钟,只能使用一次。- 该
code与wx.login()返回的登录code不同,不能混用。 - 新流程不再需要前端传递
encryptedData和iv。 - 当前官方标准价格为成功调用一次 0.03 元,实际以公众平台为准。
2.2 小程序前端关键代码
页面按钮:
<button
open-type="getPhoneNumber"
bindgetphonenumber="onGetPhoneNumber"
loading="{{submitting}}"
disabled="{{submitting}}"
>
使用微信手机号
</button>
页面逻辑:
Page({
data: {
submitting: false
},
/**
* 接收微信返回的一次性手机号动态令牌。
*/
onGetPhoneNumber: function (event) {
const detail = event.detail || {};
if (detail.errMsg !== 'getPhoneNumber:ok' || !detail.code) {
wx.showToast({
title: detail.errno === 1400001
? '手机号授权额度不足'
: '你已取消手机号授权',
icon: 'none'
});
return;
}
this.exchangePhoneCode(detail.code);
},
/**
* 将 code 交给业务后端消费,前端不直接调用微信服务端接口。
*/
exchangePhoneCode: function (code) {
this.setData({ submitting: true });
wx.request({
url: 'https://api.example.com/api/wechat/phone',
method: 'POST',
header: {
'Content-Type': 'application/json',
// 应先通过 wx.login 建立业务登录态,再携带业务 Token 绑定手机号。
'Authorization': 'Bearer ' + wx.getStorageSync('userToken')
},
data: { code: code },
success: function (response) {
if (response.statusCode === 200) {
wx.showToast({ title: '绑定成功', icon: 'success' });
return;
}
wx.showToast({ title: '绑定失败,请重新授权', icon: 'none' });
},
fail: function () {
wx.showToast({ title: '网络异常,请重新授权', icon: 'none' });
},
complete: () => {
this.setData({ submitting: false });
}
});
}
});
手机号授权和登录是两个流程
wx.login() 用于识别小程序用户并建立业务登录态,getPhoneNumber 用于获取手机号。后端应把手机号绑定到已经登录的业务用户,不能只凭手机号 code 创建不受控的账号绑定关系。
2.3 后端配置
application.yml:
密钥只能通过环境变量或密钥管理服务注入,禁止写入小程序代码或提交到 Git。
2.4 Spring Boot 后端关键代码
下面只展示关键调用链。accessTokenService 负责获取并缓存小程序 access_token,生产环境应在 Token 到期前约 5 分钟刷新。
package com.example.wechat;
import java.util.Collections;
import javax.validation.Valid;
import javax.validation.constraints.NotBlank;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* 微信小程序手机号绑定接口。
*/
@RestController
@RequestMapping("/api/wechat")
public class WechatPhoneController {
private static final String PHONE_API =
"https://api.weixin.qq.com/wxa/business/getuserphonenumber"
+ "?access_token={accessToken}";
private final RestTemplate restTemplate;
private final WechatAccessTokenService accessTokenService;
private final MobileBindingService mobileBindingService;
public WechatPhoneController(
RestTemplate restTemplate,
WechatAccessTokenService accessTokenService,
MobileBindingService mobileBindingService
) {
this.restTemplate = restTemplate;
this.accessTokenService = accessTokenService;
this.mobileBindingService = mobileBindingService;
}
/**
* 消费微信一次性 code,获取手机号并绑定当前登录用户。
*
* @param request 小程序手机号授权请求
* @return 脱敏后的手机号
*/
@PostMapping("/phone")
public ResponseEntity<PhoneResult> bindPhone(
@Valid @RequestBody PhoneCodeRequest request
) {
String accessToken = accessTokenService.getMiniProgramAccessToken();
JsonNode response = restTemplate.postForObject(
PHONE_API,
Collections.singletonMap("code", request.getCode()),
JsonNode.class,
accessToken
);
validateWechatResponse(response);
String phone = response.path("phone_info").path("phoneNumber").asText();
String countryCode = response.path("phone_info").path("countryCode").asText();
// currentUserId 应从已校验的登录态中取得,不能由前端直接提交。
String currentUserId = CurrentUserContext.requireUserId();
mobileBindingService.bindEncrypted(currentUserId, phone, countryCode);
return ResponseEntity.ok(new PhoneResult(maskPhone(phone)));
}
private void validateWechatResponse(JsonNode response) {
if (response == null || response.path("errcode").asInt(-1) != 0) {
throw new IllegalStateException("微信手机号授权失败");
}
if (response.path("phone_info").path("phoneNumber").asText().isEmpty()) {
throw new IllegalStateException("微信未返回手机号");
}
}
private String maskPhone(String phone) {
if (phone == null || phone.length() < 7) {
return "****";
}
return phone.substring(0, 3)
+ "****"
+ phone.substring(phone.length() - 4);
}
/**
* 小程序提交的手机号授权参数。
*/
public static class PhoneCodeRequest {
@NotBlank
private String code;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
}
/**
* 返回给小程序的脱敏手机号。
*/
public static class PhoneResult {
private final String maskedPhone;
public PhoneResult(String maskedPhone) {
this.maskedPhone = maskedPhone;
}
public String getMaskedPhone() {
return maskedPhone;
}
}
}
小程序 access_token 获取地址:
GET https://api.weixin.qq.com/cgi-bin/token
?grant_type=client_credential
&appid=APPID
&secret=APPSECRET
返回的 access_token 通常包含 expires_in,后端必须缓存,不要在每次手机号请求时重新获取。多实例部署时应使用 Redis 和分布式锁统一刷新。
3. 企业微信获取内部员工手机号
3.1 开通条件
- 在企业微信管理后台创建自建应用。
- 设置应用可见范围,目标员工必须位于可见范围内。
- 配置网页授权可信域名和 OAuth 回调地址。
- 在应用详情中允许获取手机号敏感字段。
- OAuth 使用
snsapi_privateinfo,并且必须传agentid。 - 员工在授权页面确认提供敏感信息。
3.2 实现流程
H5 请求后端生成企业微信授权地址
-> 员工确认 snsapi_privateinfo 授权
-> 企业微信回调 code + state
-> 后端通过 auth/getuserinfo 获取 userid + user_ticket
-> 后端通过 auth/getuserdetail 获取 mobile
-> 根据 userid 建立登录态或绑定员工账号
3.3 H5 前端关键代码
发起企业微信授权:
<button id="wecomAuthorize" type="button">使用企业微信手机号</button>
<script>
document.getElementById('wecomAuthorize').addEventListener('click', async function () {
const response = await fetch(
'/api/wecom/authorization-url?returnUrl=' + encodeURIComponent('/profile'),
{ credentials: 'include' }
);
const result = await response.json();
window.location.assign(result.authorizationUrl);
});
</script>
OAuth 回调页读取 code 和 state,再交给后端:
<script>
async function completeWeComAuthorization() {
const params = new URLSearchParams(window.location.search);
const code = params.get('code');
const state = params.get('state');
if (!code || !state) {
throw new Error('企业微信授权参数不完整');
}
const response = await fetch('/api/wecom/phone', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ code: code, state: state })
});
const result = await response.json();
if (!response.ok) {
throw new Error(result.message || '企业微信手机号授权失败');
}
// 后端应在换取手机号后建立业务会话,前端只负责跳回业务页面。
window.location.replace(result.returnUrl || '/');
}
completeWeComAuthorization();
</script>
3.4 后端生成授权地址
企业微信授权地址格式:
https://open.weixin.qq.com/connect/oauth2/authorize
?appid=CORPID
&redirect_uri=URL_ENCODED_CALLBACK
&response_type=code
&scope=snsapi_privateinfo
&state=STATE
&agentid=AGENTID
#wechat_redirect
Spring Boot 关键代码:
/**
* 生成企业微信敏感信息授权地址。
*/
public String buildAuthorizationUrl(String state) {
return UriComponentsBuilder
.fromHttpUrl("https://open.weixin.qq.com/connect/oauth2/authorize")
.queryParam("appid", corpId)
.queryParam("redirect_uri", oauthCallbackUrl)
.queryParam("response_type", "code")
.queryParam("scope", "snsapi_privateinfo")
.queryParam("state", state)
.queryParam("agentid", agentId)
.fragment("wechat_redirect")
.build()
.encode()
.toUriString();
}
state 必须满足:
- 使用安全随机数生成。
- 与当前浏览器会话及授权后的站内地址关联。
- 建议 5 至 10 分钟过期。
- 回调成功后立即删除,只允许使用一次。
- 多实例部署时保存在 Redis 中。
returnUrl只允许站内相对路径,防止开放重定向。
3.5 后端换取企业员工手机号
下面展示三次关键调用:获取企业微信 access_token、获取 user_ticket、获取手机号。
package com.example.wecom;
import java.util.Collections;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* 企业微信内部成员敏感信息获取服务。
*/
@Service
public class WeComPhoneService {
private final RestTemplate restTemplate;
private final WeComAccessTokenService accessTokenService;
public WeComPhoneService(
RestTemplate restTemplate,
WeComAccessTokenService accessTokenService
) {
this.restTemplate = restTemplate;
this.accessTokenService = accessTokenService;
}
/**
* 使用企业微信 OAuth code 获取内部员工手机号。
*
* @param code 企业微信 OAuth 回调 code
* @return 企业微信员工身份与手机号
*/
public EmployeePhone getEmployeePhone(String code) {
String accessToken = accessTokenService.getAccessToken();
JsonNode userInfo = restTemplate.getForObject(
"https://qyapi.weixin.qq.com/cgi-bin/auth/getuserinfo"
+ "?access_token={accessToken}&code={code}",
JsonNode.class,
accessToken,
code
);
validateResponse(userInfo, "获取企业微信用户身份失败");
String userId = userInfo.path("userid").asText();
if (userId.isEmpty()) {
// 非企业成员通常只返回 openid 或 external_userid。
throw new IllegalStateException("当前用户不是企业内部成员");
}
String userTicket = userInfo.path("user_ticket").asText();
if (userTicket.isEmpty()) {
throw new IllegalStateException(
"未获得user_ticket,请检查snsapi_privateinfo、agentid和应用可见范围"
);
}
JsonNode detail = restTemplate.postForObject(
"https://qyapi.weixin.qq.com/cgi-bin/auth/getuserdetail"
+ "?access_token={accessToken}",
Collections.singletonMap("user_ticket", userTicket),
JsonNode.class,
accessToken
);
validateResponse(detail, "获取企业微信敏感信息失败");
String mobile = detail.path("mobile").asText();
if (mobile.isEmpty()) {
throw new IllegalStateException("企业微信未返回员工手机号");
}
return new EmployeePhone(userId, mobile);
}
private void validateResponse(JsonNode response, String message) {
if (response == null || response.path("errcode").asInt(-1) != 0) {
throw new IllegalStateException(message);
}
}
/**
* 企业微信内部员工手机号结果。
*/
public static class EmployeePhone {
private final String userId;
private final String mobile;
public EmployeePhone(String userId, String mobile) {
this.userId = userId;
this.mobile = mobile;
}
public String getUserId() {
return userId;
}
public String getMobile() {
return mobile;
}
}
}
企业微信 access_token 获取接口:
使用的是自建应用自身的 Secret。返回的 Token 同样需要集中缓存并提前刷新。
4. 企业微信外部联系人怎么办
企业微信 OAuth 对普通微信客户通常只返回:
它不会返回真实手机号,应选择以下方式之一。
方式一:跳转小程序
方式二:短信验证
短信接口应限制同一手机号、IP 和设备的发送频率,并设置验证码有效期、最大验证次数和成功后立即失效规则。
5. 上线检查清单
微信小程序
- 小程序为非个人主体并完成认证。
- 已声明手机号用途和隐私政策。
- 后端域名使用 HTTPS,并加入小程序
request合法域名。 - 手机号组件额度充足。
-
wx.logincode 与手机号 code 分开处理。 - code 消费失败后让用户重新授权,不重复使用旧 code。
企业微信
- 已创建自建应用并设置员工可见范围。
- 已配置网页授权可信域名和回调地址。
- OAuth 使用
snsapi_privateinfo并传入agentid。 - 管理员已允许应用获取手机号敏感字段。
- 员工授权后能够返回
user_ticket。
服务端安全
- Secret 只存放在服务端环境变量或密钥管理服务。
- access_token 使用 Redis 等集中式缓存。
- OAuth state 随机、短时有效并一次性消费。
- 手机号加密存储,接口和页面只返回脱敏值。
- 日志不记录手机号、code、user_ticket、Secret 和 access_token。
- 手机号绑定接口必须校验当前业务登录态并进行限流。
- 不使用手机号作为永久身份主键。
6. 常见问题
| 现象 | 常见原因 | 处理方式 |
|---|---|---|
小程序没有返回 code |
用户拒绝、额度不足、账号不符合条件 | 检查 errMsg、errno、认证主体和额度 |
微信返回 40029 |
code 无效、过期或已使用 | 让用户重新点击授权,不要重试旧 code |
微信返回 40013 |
code 与 access_token 所属 AppID 不一致 | 检查环境配置和多小程序 Token 隔离 |
企业微信没有 user_ticket |
scope 错误、缺少 agentid、员工不在可见范围 | 使用 snsapi_privateinfo 并检查后台配置 |
企业微信没有 mobile |
管理员未开放手机号或员工未同意 | 检查敏感字段配置和授权页面 |
企业微信只返回 openid |
当前访问者不是企业内部成员 | 按外部联系人方案处理 |
7. 官方文档
上线前再次核对
微信与企业微信会持续调整账号资质、敏感字段权限、收费规则和接口安全要求。上线前应再次检查官方文档和对应管理后台的实际提示。