获取openid流程
小程序实现登录的流程,是先通过wx.login获取code,然后再用code请求自己服务器,自己服务器拿着code去微信服务器获取openid,然后业务自定义实现登录,微信小程序登录流程图
uniapp实现登录与上述流程是一样的,只是对登录实现了封装,用uni.login替代了wx.login,
uni.login({
provider: 'weixin',
success: function (loginRes) {
//loginRes中有code,拿着code再请求自己服务器以获取openid
console.log(loginRes.authResult);
}
});
开发者服务器拿着code,请求微信服务器auth.code2Session接口
GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code
js_code=JSCODE就是上文说的code,
这个接口就会返回openid、session_key。
- openid
- 用户的身份标识,每个用户在每个公众号或者小程序中都有一个唯一openid,跨公众号/小程序时openid不相同
- session_key
- session_key用来解密微信wx.getUserProfile获取到的微信开放数据中的加密数据(就是encryptedData字段),但因为微信调整了用户信息权限,只能 获取openid、unionid、session_key,不能再获取头像、昵称、性别、地区。
获取openid样例代码
基本流程是,小程序/公众号端使用uni.login
获取到code,传到后台,后台使用appid、appsecret、code获取到openid、unionid、session_key
小程序端
uni.login
可以静默执行,也就是用户无感的情况下执行。
uni.login({ //微信授权
service: 'oauth',
success: function(res) {
//登录的code
console.log(res.code)
},
fail(err) {
uni.showToast({
title: '获取失败',
duration: 3000,
icon: 'none'
})
}
});
后端
class Controller{
@Value("${mp.appid}")
private String appid;
@Value("${mp.appsecret}")
private String appsecret;
@Autowired
private RestTemplate restTemplate;
@PostMapping("getOpenid")
public void getOpenid(String loginCode){
String code2SessionRespStr = restTemplate.getForObject("https://api.weixin.qq.com/sns/jscode2session?appid={APPID}&secret={SECRET}&js_code={JSCODE}&grant_type=authorization_code",
String.class,
appid,
appsecret,
loginCode);
}
WxCode2SessionResp code2SessionResp = JSONObject.parseObject(code2SessionRespStr,WxCode2SessionResp.class);
}
public class WxCode2SessionResp {
private String openid;// string 用户唯一标识
private String session_key;// string 会话密钥
private String unionid;// string 用户在开放平台的唯一标识符,若当前小程序已绑定到微信开放平台帐号下会返回,详见 UnionID 机制说明。
private String errcode;// number 错误码
private String errmsg;// string 错误信息
}
注意,如果是双卡手机,两个手机号的openid是相同的。
小程序获取手机号
微信公众号没有获取手机号的api,似乎可以通过卡券之类的途径间接得到手机号,但总归很麻烦;小程序提供了获取手机号api,流程是前端通过用户授权获取到code,后台通过先获取access_token,再根据access_token、code获取手机号;样例代码
小程序端代码
获取手机号不能静默执行,必须由用户授权,所以有个按钮,由用户点击触发,因为api升级原因,存在低版本不支持情况,所以增加了提示。
<button type="default" open-type="getPhoneNumber"
@getphonenumber="onGetphone">微信授权登录</button>
//获取用户手机号码
onGetphone(e) {
console.log("获取手机号回调参数",e)
if (e.detail.errMsg == 'getPhoneNumber:ok') {
console.log('用户点击了接受');
//获取手机号的code
const {detail:{code:getPhoneCode}} = e
if(!getPhoneCode){
uni.showToast({
title:"微信版本过低,请升级",
duration: 3000,
icon: 'none'
})
return
}
//发送ajax请求,将code提交到后台
}
},
后端代码
class Controller{
@Value("${mp.appid}")
private String appid;
@Value("${mp.appsecret}")
private String appsecret;
@Autowired
private RestTemplate restTemplate;
@PostMapping("getPhone")
public void getPhone(String getPhoneCode){
String wxAccessTokenRespStr = restTemplate
.getForObject("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={appid}&secret={appsecret}",
String.class,
appid,
appsecret);
log.info("获取小程序accessToken返回值是:{}", wxAccessTokenRespStr);
GetWxAccessTokenResp wxAccessTokenResp = JSONObject.parseObject(wxAccessTokenRespStr,GetWxAccessTokenResp.class);
Assert.hasText(wxAccessTokenResp.getAccess_token(), wxAccessTokenResp.getErrmsg());
String access_token = wxAccessTokenResp.getAccess_token();
GetWxPhoneCommand getWxPhoneCommand = new GetWxPhoneCommand();
getWxPhoneCommand.setCode(getPhoneCode);
String getWxPhoneRespStr = restTemplate
.postForObject("https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token={ACCESS_TOKEN}",
getWxPhoneCommand, String.class, access_token);
log.info("获取小程序手机号返回值是:{}", getWxPhoneRespStr);
GetWxPhoneResp getWxPhoneResp = JSONObject.parseObject(getWxPhoneRespStr,GetWxPhoneResp.class);
public class GetWxAccessTokenResp {
private String access_token;// string 获取到的凭证
private String expires_in;// number 凭证有效时间,单位:秒。目前是7200秒之内的值。
private String errCode;// number 错误码
private String errmsg;// string 错误信息
}
public class GetWxPhoneCommand {
private String code;
}
public class GetWxPhoneResp {
private String errcode;// number 错误码
private String errmsg;// string 错误提示信息
private PhoneInfo phoneInfo;// Object 用户手机号信息
public static class PhoneInfo{
private String phoneNumber;// string 用户绑定的手机号(国外手机号会有区号)
private String purePhoneNumber;// string 没有区号的手机号
private String countryCode;// string 区号
private Watermark watermark;// Object 数据水印
}
public static class Watermark{
private String appid;// string 小程序appid
private String timestamp;// number 用户获取手机号操作的时间戳
}
}
问题
在实践中,会遇到javax.crypto.BadPaddingException: pad block corrupted ,这是因为在获取手机号的回调中,调用uni.login,uni.login会冲掉解密手机号用到的session_key,可参考会话密钥 session_key 有效性,这个问题有两个解决办法
- uni.login先于获取手机号执行,比如在onShow()、created()中执行,仍然用login的code作为引子来解密手机号,缺点就是混在一起,耦合了;代码不再举例;
- 手机号code和login的code拆开,各行其是,也就是说不用uni.login的code来解密手机号回调中的encryptedData,login的code只用来获取openid;手机号的code用来获取手机号;此种方式时,可以在获取手机号回调中执行uni.login。代码就是上边的那些稍微整合一些就可以。
总结
获取login的code和获取手机号的code是两回事,不要弄混。
- login的code用来获取openid,uni.login可以静默执行,用户无感知
- 手机号的code用来获取手机号,必须由用户授权
在实践中,可以结合使用,用户授权手机号按钮的回调中执行uni.login,然后手机号的code、login的code一并提交到后端。
参考
- 小程序对encryptedData进行解密报错javax.crypto.BadPaddingException: pad block corrupted
- 微信小程序中获取用户手机号密文数据解密报错问题