在小程序开发中,获取微信用户绑定的手机号功能,详细可查看官方文档:微信官方文档 · 小程序 获取手机号
获取微信用户绑定的手机号,需先调用wx.login接口。
因为需要用户主动触发才能发起获取手机号接口,所以该功能不由 API 来调用,需用 button 组件的点击来触发。
注意:目前该接口针对非个人开发者,且完成了认证的小程序开放(不包含海外主体)。需谨慎使用,若用户举报较多或被发现在不必要场景下使用,微信有权永久回收该小程序的该接口权限。
需要将 button 组件 open-type 的值设置为 getPhoneNumber,当用户点击并同意之后,可以通过 bindgetphonenumber 事件回调获取到微信服务器返回的加密数据, 然后在第三方服务端结合 session_key 以及 app_id 进行解密获取手机号。
<button open-type="getPhoneNumber" @getphonenumber="getPhoneNumber">获取手机号
methods: {
getPhoneNumber(e){
if(e.detail.errMsg == "getPhoneNumber:ok"){
console.log('用户同意提供手机号');
console.log(JSON.stringify(e.detail.encryptedData));
console.log(JSON.stringify(e.detail.iv));
var encryptedData = e.detail.encryptedData;
var iv = e.detail.iv;
}
}
},
在回调中调用 wx.login 登录,可能会刷新登录态。此时服务器使用 code 换取的 sessionKey 不是加密时使用的 sessionKey,导致解密失败。建议开发者提前进行 login;或者在回调中先使用 checkSession 进行登录态检查,避免 login 刷新登录态。
以上基本都是官网内容,接下来咱们分析一下获取流程。
其中有疑问的点是,第2步、第6步中,后端用到的session_key。sessionKey是用户的标识,也就是说每个用户同一时间点击进入的时候是不一致的。而且每个用户在wx.login之后,也是会刷新sessionKey的。
所以,后端在第2步根据code获取sessionKey的时候,将code和sessionKey存到了缓存中。也让前端将用户的code标识存在客户端缓存中。
第5步调用的时候也将缓存中的code传递给后端,后端通过当前code去缓存里取出sessionKey来解密。
微信开放社区 小程序登录session_key的有效期问题 2018年
注意:这里即使是微信开放社区,但是也是2018年的回复,所以是否为3天这里不做证明与验证,至于过期时间需按照公司实际业务需求确定:如有用户量较大,服务器内存不足,redis占用过大等问题。可适当调整,因为每个用户退出小程序后,过段时间在进入时,可能需要再次授权获取code值,所以也会导致之前的code失效,redis存入过期数据。而且微信官方一直强调,不提供session_key的过期时间,微信不会把 session_key 的有效期告知开发者。我们会根据用户使用小程序的行为对 session_key 进行续期。用户越频繁使用小程序,session_key 有效期越长。
如果缓存中有code值
如果缓存中没有code值
签名校验以及数据加解密涉及用户的会话密钥 session_key。 开发者应该事先通过 wx.login 登录流程获取会话密钥 session_key 并保存在服务器。为了数据不被篡改,开发者不应该把 session_key 传到小程序客户端等服务器外的环境。
开发者如果遇到因为 session_key 不正确而校验签名失败或解密失败,请关注下面几个与 session_key 有关的注意事项。
需要提前导入的maven依赖
<dependency>
<groupId>org.bouncycastlegroupId>
<artifactId>bcprov-jdk16artifactId>
<version>1.46version>
dependency>
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang.StringUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.validation.constraints.NotNull;
import java.security.Security;
import java.security.spec.AlgorithmParameterSpec;
@Slf4j
public class AESUtils {
// 加密模式
private static final String ALGORITHM = "AES/CBC/PKCS7Padding";
private static final String CHARSET_NAME = "UTF-8";
private static final String AES_NAME = "AES";
//解决java.security.NoSuchAlgorithmException: Cannot find any provider supporting AES/CBC/PKCS7Padding
static {
Security.addProvider(new BouncyCastleProvider());
}
/**
* 解密
*
* @param content 目标密文
* @param key 秘钥
* @param iv 偏移量
* @return
*/
public static String decrypt(@NotNull String content, @NotNull String key, @NotNull String iv) {
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
byte[] sessionKey = java.util.Base64.getDecoder().decode(key);
SecretKeySpec keySpec = new SecretKeySpec(sessionKey, AES_NAME);
byte[] ivByte = java.util.Base64.getDecoder().decode(iv);
AlgorithmParameterSpec paramSpec = new IvParameterSpec(ivByte);
cipher.init(Cipher.DECRYPT_MODE, keySpec, paramSpec);
return new String(cipher.doFinal(Base64.decodeBase64(content)), CHARSET_NAME);
} catch (Exception e) {
log.error("解密失败:{}", e);
e.printStackTrace();
}
return StringUtils.EMPTY;
}
}
测试:
public static void main(String[] args) {
String sessionKey = "zF6lhhRqTdWJ8sb45RTxsw==";
String encryptedData = "JXZ5dxBn7EqgRWTbqt50rxrN69Y9okDdL0YzvrSwNjKA9blYJagZbhovcwbhFy8vVaqjVVEjIl451JOCXIB2fpNpq0sbIxV+B28pKWLA8y2jn7R1iTE7O7k/tW1yVDMZwqRQyTw9lV/qlISw+HX887DeVWCfem6lx8jZ/C+kshJdig4Li06AIA9A9smToZYI";
String iv = "CO5eq/F5TTv9SuwiMLDNaA==";
String decrypt = AESUtils.decrypt(encryptedData, sessionKey, iv);
System.out.println(decrypt);
}
将json转成map:
Gson gson = new Gson();
Map<String,Object> stringList = gson.fromJson(decrypt, new TypeToken<Map<String,Object>>() {}.getType());
stringList.forEach((k,v)-> System.out.println(k+":"+v));
gson需注意时间戳处理
或者
ObjectMapper objectMapper = new ObjectMapper();
Map<String, Object> userMap = objectMapper.readValue(decrypt, Map.class);
userMap.forEach((k,v)-> System.out.println(k+":"+v));