How WeChat Mini Programs Obtain User Information

Self introduction

I am IT Guoguo Diary, please search for IT Guoguo Diary on WeChat official account. I am
an ordinary technical geek who regularly shares technical articles. Welcome to like, follow and repost. Please take care of me.
  • What is the basic information of WeChat Mini Program users?

  • In addition to basic information, WeChat also provides openId and unionId. What are their functions and differences?

  • In the process of obtaining user information, how to know whether the plaintext data has been tampered with?

  • How to use session_key?

With these questions in mind, we start today's study.

Get basic user information

The applet provides a wx.getUserProfile(OBJECT) method to obtain user information. The previous old version used the wx.getUserProfile(OBJECT) method, which is now officially deprecated.

User information is divided into basic user information, user openId, and UnionId. The basic information is plaintext, while openId and UnionId are encrypted data. Both types of data are returned by the wx.getUserProfile(OBJECT) method.

User basic information includes the following 7 items:

  • avatarUrl The URL address of the user's WeChat avatar.

  • city ​​city.

  • country country.

  • gender Gender, 1 means male, 2 means female, 0 means unknown.

  • language locale.

  • nickName Nickname.

  • province: province.

In the applet, the user's basic information can be easily obtained, and they are in plain text and not encrypted. But openId and UnionId are encrypted. What are openId and UnionId? OpenId and UnionId can be understood as the ID number of the user in the WeChat application.

Their difference is:

  • openId only represents the id number of the user under a certain WeChat application;

  • The UnionId is cross-application, and the UnionId is unique for the same user in multiple applications of the same developer.

The description of UnionId in the official document is very clear: If the developer has multiple mobile applications, website applications and public accounts (including applets), the uniqueness of the user can be distinguished by UnionId, because as long as the mobile application under the same WeChat open platform account For applications, website applications, and public accounts (including applets), the user's UnionId is unique. In other words, the same user has the same UnionId in different applications under the same WeChat open platform.

Therefore, openId cannot cross applications. If you want to unify user identities among multiple applications, please use UnionId. It should be noted here that to use UnionId in the applet, you first need to go to the WeChat open platform to bind the applet. Please refer to the official documentation:

https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/union-id.html

WeChat Open Platform Binding Mini Program Process

Log in to the WeChat Open Platform —Management Center—Mini Programs—Binding Mini Programs

WeChat open platform binding applet

Let's look at the use of the wx.getUserProfile(OBJECT) method. The OBJECT parameter of wx.getUserProfile has 3 callback functions: success, fail and complete. Among these 3 callback functions, the focus is on success. The return value of the success method has the following five values:

  • userInfo Basic user information object, does not contain sensitive information such as openId.

  • rawData is a basic information string that does not contain sensitive information, and is usually used to calculate signatures to prevent tampering of user information returned from WeChat.

  • signature Use sha1(rawData+sessionkey) to get a string for verifying user information.

  • encryptedData Encrypted data of complete user information including sensitive data.

  • iv The initial vector of the encryption algorithm.

Now, we get the user's plaintext information in the code. code show as below:

_getUserInfo: function() {
	var userInfoStorage= wx.getStorageSync(user);
	if (!userInfoStorage) {
		//如果缓存中没有用户信息,那么获取用户信息
		var that = this;
		wx.login({
			success: function (){
				wx.getUserProfile({
					success: function (res){
						that.globalData.userInfo = res.userInfo
						//将用户的基本信息保存到缓存中
						wx.setStorageSync('user',res.userInfo)
					},
					fail: function (res){
						console.log(res);
					}
				})
			}
		})
	}
	else {
		//如果缓存中已经存在用户的基本信息,那么将信息保存到全局变量中
		this.globalData.userInfo = userInfoStorage;
	}
}

Explain the above code. In order to avoid loading the basic information of the user in the WeChat server every time the Mini Program is started, we save the user information in the cache. In this way, every time you start the applet, first check whether there is data in the cache, if not, call wx.getUserProfile to obtain user information and save it in the cache and the global variable userInfo; if yes, directly save the user information in the global variable userInfo.

Careful friends may notice that we do not directly call the wx.getUserProfile(OBJECT) method, but call wx.login first, and then continue to call wx.getUserProfile after wx.login is successfully called. The reason why wx.getUserProfile is called after wx.login is successfully called is because the official document clearly states that wx.login needs to be called first to obtain user information . At the same time, wx.login is called first in the official sample project. As for the function of wx.login, please read down patiently. You only need to follow the example and follow suit.

In fact, if you only want to get the basic plaintext information of the user, you can also get the user information by directly calling wx.getUserProfile instead of calling wx.login . Therefore, please decide whether to call wx.login before calling wx.getUserProfile.

When the above code is run for the first time and wx.getUserProfile is called, the applet will pop up a prompt window as shown in the figure, allowing the user to choose whether to authorize to obtain the user's basic information.

User authorization to obtain information

After the user clicks "Allow", the success callback function of wx.getUserProfile will be executed. If the user clicks "Deny", the fail callback function will be executed.

Some developers may never pop up this authorization window when testing. Because if the current project does not have an appId, the user information is simulated by WeChat, and there is no real process of obtaining user information, so this window will not appear, and the development tool will have a warning prompt, as shown in the figure.

When there is no appId, it is the user data simulated by the development tool

当然,这个用户数据也是真实的用户数据,因为你在使用开发工具时必须扫描二维码登录,这样开发工具就能知道你的微信身份,自然可以模拟返回你的用户数据。但模拟数据相比于真实的用户数据缺少3个属性,即signature、encryptedData和iv。也就是说,模拟数据只有明文信息而没有加密信息。

将用户信息保存到缓存中有一个缺点,就是没办法实时更新用户信息(比如用户更改了自己的微信资料),但这是具体业务的问题,需要开发者在实际项目编写中灵活处理。

用户信息校验

上文已经介绍了如何调用微信登录接口以及获取用户信息。另外,在我以往的文章中也介绍了如何通过登录接口返回的code获取到session_key。之前提了一个问题,session_key是做什么用的呢?下面就讲解一下session_key的作用以及它的用法。

先来看看wx.getUserProfile返回的数据:

  • userInfo 用户基本信息对象,不包含openId等敏感信息。

  • rawData 不包含敏感信息的基本信息字符串,通常用来计算签名,防止从微信返回的用户信息被篡改。

  • signature 使用sha1(rawData+sessionkey)得到字符串,用于校验用户信息。

  • encryptedData 包括敏感数据在内的完整用户信息的加密数据。

  • iv 加密算法的初始向量。

我们使用了userInfo对象,包括userInfo和rawData在内的明文数据都可能存在被篡改的风险。如何知道明文数据是否被篡改了呢?

这个时候rawData和singature就可以发挥作用了。rawData和signature用于校验用户数据到底有没有被篡改过(没有绝对安全的网络,数据极有可能被抓包或者通过其他方式篡改)。通常来说,想要实现这个校验必须在服务器编码才能进行。这需要小程序将获取的rawData和signature一并提交到服务器,由服务器完成校验工作。

校验的基本原理是:rawData是用户原始明文数据,signature是使用sha1(rawData+sessionkey)得到的字符串。理论上讲,如果数据没有被篡改,那么signature等于sha1(rawData+sessionkey);如果rawData或者signature被修改了,那么signature必然不再等于sha1(rawData+sessionkey)。

是否存在signature和rawData同时被修改的情况呢?理论上是不可能的,因为session_key并不在网络上传输,篡改者不知道这个变量,被篡改且校验通过的概率很小。

有可能从signature中推算出session_key吗?理论上讲,这是不可能的。因为sha1算法是不可逆的,无法在已知rawData和signature的情况下推算出session_key。不知道session_key就无法通过同时修改rawData和signature达到“欺骗校验的目的”。如果知道了session_key,只需要修改rawData并重新用session_key计算一下新的sha1(rawData+session_key)就又可以让新的rawData等于新的sha1(rawData+session_key)了。这样,开发者就无法知道rawData是被修改过的。

这也是为什么官方文档一再强调,不要在网络上传输session_key,而应该将其保存在服务器上使用,以降低session_key被泄露的风险。

session_key有点类似于我们在数据库中保存用户密码时所使用的“盐(salt)”。在数据库保存用户密码时,并不是直接将用户的密码以明文的方式存放在数据库表中,通常都会使用SHA-1或者MD5算法将用户密码和salt随机字符串拼接在一起,重新计算一下再存入数据库中。被重新使用SHA-1或MD5算法计算的用户密码谁都不知道是什么,开发者也只能比对每次登录时输入的密码和数据库保存的密码是否一致,判断是否为合法用户,却无法知道密码到底是什么。

用户数据校验流程图

需要说明的是,我们明确说明服务器是没有保存session_key的。因为我们需要拿到session_key才能进行用户数据校验,所以在上述流程图再一次重复了用户的登录流程。在真实的流程中,用户登录在session_key的有效时间内只应该执行一次,session_key也应当被保存在服务器中。其实小程序只需要使用wx.request将rawData和signature发送到服务器即可,服务器无须使用code换取session_key,直接做SHA-1签名比对即可。如图所示为服务器已保存session_key的用户数据校验流程。

如果服务器已保存了session_key的用户数据校验流程

对比服务器没保存session_key和保存了session_key的数据校验流程图,很明显可以看出,服务器保存session_key后整个流程变得更加简单,完全不需要再与微信服务器交互。

服务器管理session_key的过程非常复杂,为了降低代码的复杂度,我们没有在服务器中保存session_key。我们后续的所有开放API调用流程都将重复完整的流程,因为单独完整的流程更加有利于开发者理解每个开放API。下面看下具体代码:

前端代码

wx.login({
	success: function (loginRes){
		wx.getUserProfile({
			success: function (userRes){
				wx.request({
                    url: "http://localhost:8080/wxopen/wxcheckuserinfo"
                    data: {
                        code: loginRes.code,
                        signature: userRes.signature,
                        rawData: userRes.rawData
                    },
                    success: function(res){
                        console.log(res.data);
                    }
                })
			}
		})
	}
})

首先,登录并拿到code码,然后调用wx.getUserProfile接口到rawData和signature,再使用wx.request将这3个参数发送到服务器中,服务器会进行数据校验工作并返回校验结果。下面是服务器接口wxcheckuserinfo的编码。

Map<String,Object> map = new HashMap<>(7);
map.put("appid",wxAppId);
map.put("secret",wxSecret);
map.put("js_code", param.getCode());
map.put("grant_type",grantType);
WxCode2SessionRet result = null;
try{
    String url = "https://api.weixin.qq.com/sns/jscode2session";
    String info = HttpUtil.get(url, map);
    result = JSON.parseObject(info, WxCode2SessionRet.class);
} catch (Exception e){
    log.error("code2session失败", e);
    return null;
}
try {
    String signature2 = DigestUtils.sha1Hex(encryptedData + result.getSession_key());
    if (!signature.equals(signature2)) {
        return R.error().message("签名校验失败");
    }
} catch (Exception e) {
    throw new RuntimeException("用户信息校验失败");
}

WxCode2SessionRet.java

@Data
public class WxCode2SessionRet implements Serializable {
    private String openid;
    private String session_key;
    private String unionid;
    private String errcode;
    private String errmsg;
}

在以上代码中,首先使用code调用微信服务器换取session_key,随后使用session_key和signarue校验用户发送过来的rawData,最后返回校验结果。

建议开发者在客户端使用用户明文数据时使用rawData,而不要使用userInfo。因为数据验证的是rawData有没有被篡改,而不是验证userInfo是否被篡改。至于微信能否确保userInfo和rawData的一致性,这个不得而知。建议开发者使用rawData作为用户的基本信息。

至此,微信小程序获取用户信息、校验用户信息的过程就已经讲完了,下一篇文章我会介绍一下怎么解析用户的加密信息。

总结

  • 用户基本信息有以下7个,他们属于明文

avatarUrl 用户微信头像的URL地址。
city 城市。
country 国家。
gender 性别,1表示男,2表示女,0表示未知。
language 语言区域。
nickName 昵称。
province:省份。

  • 用户加密信息有openId和UnionId,它们是用户在微信应用中的id号

他们的区别是:

1. openId只代表用户在某个微信应用下的id号;
2. 而UnionId是跨应用的,同一用户在同一开发者的多个应用里,UnionId是唯一的。

  • 如何知道明文数据是否被篡改了?通过校验用户加密信息rawData和signature是否一致。signature是使用sha1(rawData+sessionkey)得到的字符串。如果数据没有被篡改,那么signature等于sha1(rawData+sessionkey);如果数据被篡改,则不等于。

  • session_key有什么作用?session_key有点类似于我们在数据库中保存用户密码时所使用的“盐(salt)”。

请联系我

我是IT果果日记,微信公众号请搜索 IT果果日记
一个普通的技术宅,定期分享技术文章,欢迎点赞、关注和转发,请多关照。

微信公众号 IT果果日记

https://gitee.com/chenzhaoplus

https://github.com/chenzhaoplus

https://blog.csdn.net/cz285933169?spm=1010.2135.3001.5421

Guess you like

Origin blog.csdn.net/cz285933169/article/details/128978495