先说一下需求吧,小程序微信登录,用户授权获取个人信息。然后保存用户基本信息到系统用户表,同时新增用户账户信息,上传用户头像。 emmm..之所以想写下来是因为自己踩过的坑啊。。就不细说了。 链接: 小程序微信登录官方文档
登录:
之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
用户授权获取个人信息:
注意:此接口有调整,使用该接口将不再出现授权弹窗,请使用 <button open-type="getUserInfo"></button> 引导用户主动进行授权操作
注:当 withCredentials 为 true 时,要求此前有调用过 wx.login 且登录态尚未过期,此时返回的数据会包含 encryptedData, iv 等敏感信息;当 withCredentials 为 false 时,不要求有登录态,返回的数据不包含 encryptedData, iv 等敏感信息。
这是官方给的文档写的。就是前端调用微信接口得到一个code和useInfo,把这两个给后台,后台首先通过code走微信提供的链接获取到用户唯一标识openid 和 会话密钥session_key。
String token = HttpUtil.doPost("n
?appid=" + prop.get(APP_ID)+ "&secret=" + prop.get(API_KEY) +
"&js_code=" + code + "&grant_type=authorization_code", null, "UTF-8");
效验成功返回一个
//正常返回的JSON数据包{ "openid": "OPENID", "session_key": "SESSIONKEY"}
效验失败返回
//错误时返回JSON数据包(示例为Code无效){ "errcode": 40029, "errmsg": "invalid
code"}
效验成功后,然后是根据参数useInfo获取用户的信息。利用上面得到的openId和sessionkey
小程序可以通过各种前端接口获取微信提供的开放数据。 考虑到开发者服务器也需要获取这些开放数据,微信会对这些数据做签名和加密处理。 开发者后台拿到开放数据后可以对数据进行校验签名和解密,来保证数据不被篡改。
为了确保 开放接口 返回用户数据的安全性,微信会对明文数据进行签名。开发者可以根据业务需要对数据包进行签名校验,确保数据的完整性。
JSONObject userInfo = JSONObject.parseObject(userInfo);
// 用户信息
// 签名校验
if (!new String(SecureUtil.sha1X16(userInfo.get(WX_RAWDATA)
+ sessionKey, ENCODING_FORMAT), ENCODING_FORMAT)
.equals(userInfo.getString(WX_SIGNATURE))) {
renderJson(Result.fail("签名校验失败").build());
return;
}
签名效验成功后,解密加密的数据
// 加密数据解密
JSONObject userInfoEncrypted =
desEncrypt(userInfo.getString(WX_ENCRYPTEDDATA), sessionKey,
userInfo.getString(WX_IV));
if (null == userInfoEncrypted) {
renderJson(Result.fail("数字水印校验失败").build());
return;
}
解密完后就是根据自己的需求开发了。
先判断第三方的openid在数据库中是否存在,如果存在则说明用户已经登录过小程序,如果数据库中不存在则用户是首次登录小程序需要将用户信息保存到数据库
// 判断是否已注册
String openId = userInfoEncrypted.getString(WX_USERINFO_OPEN_ID);
MemberUser user = memberUserService.find(new MemberUser().setOpenId(openId));
//已注册
if (Tools.isNotEmpty(user)) {
MemberInfo info = memberInfoService.find(new MemberInfo()
.setUserId(user.getUserId()));
result = assembleResult(user, info, userInfoEncrypted
.getString(WX_USERINFO_NICKNAME), sessionKey);
renderJson(Result.ok().put(result).build());
return;
}
如果未注册过,就进行注册绑定,把openId保存到第三方绑定表,然后主键为用户表的外键。同时新增用户账户数据。把头像上传到自己服务器。
设置微信手机号绑定一样的。
注意:
1、加密解密需要注意,userinfo和绑定手机号需要的phoneinfo都需要效验签名解密获得。效验解密方法官方文档也给出了例子。
2、返回给前端的openId需要加密处理,防止被篡改。
3、注意微信昵称的
完整代码贴到下面,前端代码我就不说了,登录接口需要前端给两个参数:
1、code (登录凭证)
code | String | 用户登录凭证(有效期五分钟)。开发者需要在开发者服务器后台调用 api,使用 code 换取 openid 和 session_key 等信息 |
---|
2、 userInfo(用户信息)
rawData | String | 不包括敏感信息的原始数据字符串,用于计算签名。 |
---|---|---|
signature | String | 使用 sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息,参考文档 signature。 |
encryptedData | String | 包括敏感数据在内的完整用户信息的加密数据,详细见加密数据解密算法 |
iv | String | 加密算法的初始向量,详细见加密数据解密算法 |
UserInfo参数:
nickName | String | 用户昵称 |
---|---|---|
avatarUrl | String | 用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表132*132正方形头像),用户没有头像时该项为空。若用户更换头像,原有头像URL将失效。 |
gender | String | 用户的性别,值为1时是男性,值为2时是女性,值为0时是未知 |
city | String | 用户所在城市 |
province | String | 用户所在省份 |
country | String | 用户所在国家 |
language | String | 用户的语言,简体中文为zh_CN |
完整代码:
//参数:
@Params(value = {@Param(name = "code", dataType = "string",
in = "formData", description = "code"),
@Param(name = "userInfo", dataType = "string",
in = "formData", description = "微信获取用户信息的数据",
required = true)})
// 登录:
public void login() throws Exception {
Map<String, Object> result = new HashMap<String, Object>();
// 获取session_key
Result sessionKeyMap = code2sessionKey(getPara("code"));
String sessionKey = "";
if ((boolean)sessionKeyMap.get(Const.RESULT)) {
sessionKey = String.valueOf(sessionKeyMap.
get(Const.PROMPT_MSG));
} else {
renderJson(sessionKeyMap);
return;
}
JSONObject userInfo = JSONObject.parseObject(getPara("userInfo"));
// 用户信息
// 签名校验
if (!new String(SecureUtil.sha1X16(userInfo.get(WX_RAWDATA)
+ sessionKey, ENCODING_FORMAT), ENCODING_FORMAT)
.equals(userInfo.getString(WX_SIGNATURE))) {
renderJson(Result.fail("签名校验失败").build());
return;
}
// 加密数据解密
JSONObject userInfoEncrypted =
desEncrypt(userInfo.getString(WX_ENCRYPTEDDATA),
sessionKey, userInfo.getString(WX_IV));
if (null == userInfoEncrypted) {
renderJson(Result.fail("数字水印校验失败").build());
return;
}
// 判断是否已注册
String openId = userInfoEncrypted.getString(WX_USERINFO_OPEN_ID);
MerchantThird user = merchantThirdService.
find(new MerchantThird().setOpenId(openId));
if (Tools.isNotEmpty(user)) {
MerchantInfo info = merchantInfoService
.findById(user.getMerchantId());
if (info == null) {
return;
}
SysUser sysUser = sysUserService
.findById(info.getUserId());
result = assembleResult(user, info, sysUser,
userInfoEncrypted.getString(WX_USERINFO_NICKNAME),
sessionKey);
renderJson(Result.ok().put(result).build());
return;
}
result.put("msg", "该用户没有注册为医生");
result.put("token", JWTUtil.createToken(openId, sessionKey));
renderJson(Result.ok().put(result).build());
}
/**
* 微信code换取sessionKey等加密信息
*
* @param code
* @return
*/
private Result code2sessionKey(String code) {
String token = HttpUtil.doPost("https://api.weixin.qq.com/sn
s/jscode2session?appid=" + prop.get(APP_ID)
+ "&secret=" + prop.get(API_KEY) + "&js_code="
+ code + "&grant_type=authorization_code", null,
"UTF-8");
JSONObject jsonObject = JSONObject.parseObject(token);
if (null != jsonObject.get(WX_ERROR_CODE)) {
return Result.fail(jsonObject.getString(WX_ERROR_MSG)
+ "(" + jsonObject.getString(WX_ERROR_CODE) + ")")
.build();
} else {
return Result.ok(jsonObject.getString(WX_SESSION_KEY))
.build();
}
}
/**
* 聚合用户账号信息
*/
private MemberUser assembleMemberUser(JSONObject userInfoEncrypted, Date date) {
return new MemberUser().setUserId(Tools.getUniqueId())
.setCreationTime(date).setUpdateTime(date)
.setStatus(Const.DEFAULT_STATUS)
.setOpenId(userInfoEncrypted.getString(WX_USERINFO_OPEN_ID));
}
/**
* 聚合用户信息
*
* @param userInfoEncrypted 参数集
* @param date 日期
* @param userId
* @return
*/
private MemberInfo assembleMemberInfo(JSONObject userInfoEncrypted,
Date date, Long userId) {
return new MemberInfo().setMemberId(Tools.getUniqueId())
.setName(filterEmoji(userInfoEncrypted.getString(WX_USERINFO_NICKNAME)
,"*")).setType(Const.DEFAULT_STATUS)
.setAvatar(Tools.getUniqueId()).setSex(userInfoEncrypted.getString
("gender").equals("2") ? 1 : 0)
.setCreationTime(date).setUpdateTime(date).setUserId(userId)
.setInvitationCode(String.valueOf(Tools.getRandomNum(10000000,
99999999)));
}
/**
* 微信水印解密校验
*
* @param data 加密字符串
* @param key 密钥
* @param iv 加密偏移量
* @return
* @throws UnsupportedEncodingException
*/
private JSONObject desEncrypt(String data, String key, String iv) throws
UnsupportedEncodingException {
byte[] dataByte =
AesEncryptUtil.decryptOfDiyIV(Base64.decodeBase64(data),
Base64.decodeBase64(key), Base64.decodeBase64(iv));
JSONObject result = JSONObject.parseObject(new String(dataByte, "UTF-8"));
// 水印校验
JSONObject watermark = JSONObject.parseObject(String.valueOf(result.
get(WX_WATERMARK)));
if (!watermark.getString(WX_WATERMARK_APP_ID).equals(prop.get(APP_ID))) {
return null;
}
return result;
}
/**
*
* @param user 返回的用户账户信息
* @param info 返回的token内的信息(仅含memberId和openId)
* @param nickName 返回的昵称
* @param result 返回的额外内容
* @return
*/
private Map<String, Object> assembleResult(MemberUser user, MemberInfo info,
String nickName, String sessionKey) {
Map<String, Object> result = new HashMap<String, Object>(6);
if (Tools.isEmpty(user) || Tools.isEmpty(info)) {
result.put(Const.RESULT, false);
result.put(Const.PROMPT_MSG, "获取参数异常");
}
info.put(Const.OPEN_ID, user.getOpenId());
info.put(Const.SESSION_KEY, sessionKey);
user.remove("openId");
user.setName(nickName);
setAvatarMemberUser(user, info);
result.put("user", user);
result.put("accountInfo", memberAccountService.find(new MemberAccount()
.setMemberId(info.getMemberId())));
result.put("integralInfo", memberIntegralService.find(new MemberIntegral()
.setMemberId(info.getMemberId())));
result.put("token", JWTUtil.createToken(info));
return result;
}