之前没事写了一个微信小程序玩,刚起步就碰壁,因为要获取用户授权,然后请求用户的个人信息,由于微信官方api的更改,wx.getUserInfo()
方法无法在无授权的情况下直接使用,而且只能获取到基本的一些微信用户的信息,不包含我们需要的openId
以及unionId
。几经折腾,才把这个第一步给迈过去,来记录一下。
小程序-登录操作
1. 登录流程
想要获取用户的登录信息,首先要先知道小程序的登录流程是什么,下面是官方给出的流程图。
登录流程图
1.1 第一步:获取code
1、小程序调用wx.login() 获取临时登录凭证code,并回传到开发者服务器。
2、开发者服务器以code换取用户唯一标识openid和会话密钥session_key。
3、之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。
关于unionId,这里需要说明一下,如果应用只限于小程序内则不需要unionId,直接通过openId可以确定用户身份,但是如果需要跨应用,如:网页应用,app应用时则需要使用到unionId作为身份标识。
UnionID获取途径:绑定了开发者帐号的小程序,可以通过下面3种途径获取UnionID。
1、调用接口wx.getUserInfo,从解密数据中获取UnionID。注意本接口需要用户授权,请开发者妥善处理用户拒绝授权后的情况。
2、如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号。开发者可以直接通过wx.login获取到该用户UnionID,无须用户再次授权。
3、如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过wx.login获取到该用户UnionID,无须用户再次授权。
1.2 第二步:通过code换取个人信息
当前台获得了用户的授权后,我们就可以获得用户的个人信息以及unionId。
前台接口:**wx.getUserInfo(Object)**。
注意:此接口现在经果调整之后,使用该接口将不再出现授权弹窗,需要使用<button open-type="getUserInfo"></button>
引导用户主动进行授权操作。详情见、查看官方文档。
Object参数说明:
参数名 |
类型 |
必填 |
说明 |
最低版本 |
withCredentials |
Boolean |
否 |
是否带上登录信息 |
1.1.0 |
lang |
String |
否 |
指定返回用户信息的语言,zh_CN:简体中文,zh_TW:繁体中文,en:英文。默认en |
1.3.0 |
timeout |
Number |
否 |
超时时间,单位ms |
1.9.90 |
success |
Function |
否 |
接口调用成功的回调函数 |
|
fail |
Function |
否 |
接口调用失败的回调函数 |
|
complete |
Function |
否 |
接口调用结束的回调函数(调用成功、失败都会执行) |
|
注:当 withCredentials 为 true 时,要求此前有调用过 wx.login 且登录态尚未过期,此时返回的数据会包含 encryptedData, iv 等敏感信息;当 withCredentials 为 false 时,不要求有登录态,返回的数据不包含 encryptedData, iv 等敏感信息。
success返回参数说明:
参数 |
类型 |
说明 |
userInfo |
Object |
用户信息对象,不包括openid等敏感信息 |
rawData |
String |
不包括敏感信息的原始数据字符串,用于计算签名 |
signature |
String |
使用sha1( rawData + sessionkey ) 得到字符串,用于校验用户信息。 |
encryptedData |
String |
包括敏感数据在内的完整用户信息的加密数据,详细见[用户数据的签名验证和加解密 |
iv |
String |
加密算法的初始向量,详见 用户数据的签名验证和加解密 |
2. 代码解析
2.1 微信端代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
|
login: function (e) { var that = this wx.login({ success: function (r) { var code = r.code; if (code) { wx.getSetting({ success: function (re) { if (re.authSetting['scope.userInfo']) { that.register(code) } else { wx.redirectTo({ url:'XXX/XXX', }) } } }) } else { console.log("获取用户登录状态失败!" + r.errMsg) } }, fail: function () { console.log("登录失败") } }) },
register: function (code) { wx.getUserInfo({ success: function (res) { wx.request({ url: 'XXX/login.do', method: 'POST', header: { 'content-type': 'application/x-www-form-urlencoded' }, data: { encryptedData: res.encryptedData, iv: res.iv, code: code }, success: function (data) { if (data.data.status == 1) { app.globalData.checkLogin = true var userInfo_ = data.data.userInfo; app.globalData.userInfo = userInfo_; app.globalData.openId = userInfo_.openId console.log("用户信息:", userInfo_); if (app.checkLoginReadyCallback) { app.checkLoginReadyCallback(data); } } else { console.log("解密失败") } }, fail: function () { console.log("系统错误") } }) }, fail: function () { console.log("获取用户信息失败") } }) },
|
2.2 服务器端Java代码
控制层:WXLoginController .java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115
| package controller;
import mapper.UserMapper; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import util.HttpRequest; import util.RegisterUser; import util.AesCbcUtil;
import java.util.HashMap; import java.util.Map;
@Controller public class WXLoginController {
@Autowired private UserMapper userMapper;
@RequestMapping(value = "/login.do") @ResponseBody public Map decodeUserInfo(String encryptedData,String iv,String code){ Map map = new HashMap();
if(code == null || code.length() == 0) { map.put("status",0); map.put("msg","code不能为空"); return map; }
String wxspAppid = "XXXXXXXXX"; String wxspSecret = "XXXXXXXXX"; String grant_type = "authorization_code";
String params = "appid="+wxspAppid + "&secret="+wxspSecret + "&js_code="+code + "&grant_type="+grant_type; String url = "https://api.weixin.qq.com/sns/jscode2session"; String sr = HttpRequest.sendGet(url,params);
JSONObject json = new JSONObject(sr);
String session_key = json.get("session_key").toString();
String openid = (String)json.get("openid");
try{ String result = AesCbcUtil.decrypt(encryptedData,session_key,iv,"UTF-8"); if(null != result && result.length() > 0) { map.put("status",1); map.put("msg","解密成功"); JSONObject userInfoJSON = new JSONObject(result); Map userInfo = new HashMap(); userInfo.put("openId",userInfoJSON.get("openId")); userInfo.put("nickName",userInfoJSON.get("nickName")); userInfo.put("gender",userInfoJSON.get("gender")); userInfo.put("city",userInfoJSON.get("city")); userInfo.put("province",userInfoJSON.get("province")); userInfo.put("country",userInfoJSON.get("country")); userInfo.put("avatarUrl",userInfoJSON.get("avatarUrl"));
RegisterUser.Register(userMapper,userInfoJSON);
if(!userInfoJSON.isNull("unionId")){ userInfo.put("unionID",userInfoJSON.get("unionId")); } map.put("userInfo",userInfo);
}else{ map.put("status",0); map.put("msg","解密失败"); } }catch (Exception e){ e.printStackTrace(); } return map; } }
|
网络请求工具类:HttpRequest.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123
| package util;
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.URL; import java.net.URLConnection; import java.util.List; import java.util.Map;
public class HttpRequest {
public static String sendGet(String url,String param){ String result = ""; BufferedReader in = null; try{ String urlNameString = url + "?" + param; URL realUrl = new URL(urlNameString); URLConnection connection = realUrl.openConnection(); connection.setRequestProperty("accept","*/*"); connection.setRequestProperty("connection","Keep-Alive"); connection.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)");
connection.connect(); Map<String, List<String>> map = connection.getHeaderFields(); in = new BufferedReader(new InputStreamReader(connection.getInputStream())); String line; while ((line = in.readLine()) != null){ result += line; } }catch (Exception e){ System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } finally { try{ if(in != null){ in.close(); } }catch (Exception e){ e.printStackTrace(); } } return result; }
public static String sendPost(String url,String param){ PrintWriter out = null; BufferedReader in = null; String result = ""; try{ URL realURl = new URL(url); URLConnection conn = realURl.openConnection(); conn.setRequestProperty("accept","*/*"); conn.setRequestProperty("connection","Keep-Alive"); conn.setRequestProperty("user-agent","Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); conn.setDoOutput(true); conn.setDoInput(true); out = new PrintWriter((conn.getOutputStream())); out.print(param); out.flush(); in = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line; while((line = in.readLine()) != null){ result += line; } }catch (Exception e){ System.out.println("发送POST请求出现异常!" + e); e.printStackTrace(); } finally { try{ if(out != null){ out.close(); } if(in != null){ in.close(); } }catch (IOException ex){ ex.printStackTrace(); } } return result; }
}
|
AES解密工具类:AesCbcUtil.java
注意:重点标识的这个jar包commons.codec.jar,需要根据自己的jdk版本做对应的引入,我的是1.8的jdk,引入的是1.6的版本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
| package util;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.AlgorithmParameters; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; import java.security.Security; import java.security.spec.InvalidParameterSpecException; import java.security.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class AesCbcUtil {
static { Security.addProvider(new BouncyCastleProvider()); }
public static String decrypt(String data, String key, String iv, String encodingFormat) throws Exception
byte[] dataByte = Base64.decodeBase64(data); byte[] keyByte = Base64.decodeBase64(key); byte[] ivByte = Base64.decodeBase64(iv);
try { Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding");
SecretKeySpec spec = new SecretKeySpec(keyByte, "AES");
AlgorithmParameters parameters = AlgorithmParameters.getInstance("AES"); parameters.init(new IvParameterSpec(ivByte));
cipher.init(Cipher.DECRYPT_MODE, spec, parameters);
byte[] resultByte = cipher.doFinal(dataByte); if (null != resultByte && resultByte.length > 0) { String result = new String(resultByte, encodingFormat); return result; } return null; } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (InvalidParameterSpecException e) { e.printStackTrace(); } catch (InvalidKeyException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); }
return null; } }
|
3. 总结
以上就是关于小程序登录过程的解析,最主要的就是通过用户授权之后获取用户的加密信息,通过加密数据、加密算法初始向量和登录凭证在后台进行解密,从而获得用户的完成信息,之后在进行开发者的逻辑操作对用户的个人信息进行操作和处理。上面提到的还有一点就是wx.getUserInfo(Object)
方法不会在自动弹出授权窗口了,需要开发者自定义登录按钮来引导用户进行登录授权。