zoukankan      html  css  js  c++  java
  • 微信小程序java登录授权解密获取unionId(填坑)

    官方流程图:

    第一步:获取code

    说明:

    小程序调用wx.login() 获取 临时登录凭证code ,并回传到开发者服务器。

    开发者服务器以code换取 用户唯一标识openid 和 会话密钥session_key。

    之后开发者服务器可以根据用户标识来生成自定义登录态,用于后续业务逻辑中前后端交互时识别用户身份。

    //app.js
    App({
    onLaunch: function() {
    wx.login({
    success: function(res) {
    if (res.code) {
    //发起网络请求
    wx.request({
    url: 'https://test.com/onLogin',
    data: {
    code: res.code
    }
    })
    } else {
    console.log('登录失败!' + res.errMsg)
    }
    }
    });
    }
    })

    关于unionId,这里需要说明一下,如果应用只限于小程序内则不需要unionId,直接通过openId可以确定用户身份,但是如果需要跨应用 如:网页应用,app应用时则需要使用到unionId作为身份标识。

    UnionID获取途径

    绑定了开发者帐号的小程序,可以通过下面3种途径获取UnionID。

    调用接口wx.getUserInfo,从解密数据中获取UnionID。注意本接口需要用户授权,请开发者妥善处理用户拒绝授权后的情况。

    如果开发者帐号下存在同主体的公众号,并且该用户已经关注了该公众号。开发者可以直接通过wx.login获取到该用户UnionID,无须用户再次授权。

    如果开发者帐号下存在同主体的公众号或移动应用,并且该用户已经授权登录过该公众号或移动应用。开发者也可以直接通过wx.login获取到该用户UnionID,无须用户再次授权。

    说明完了,我们继续下一步,主要需要区分获得授权,和未获得授权情况

    第二步:通过code换取sessionKey等个人信息

    1、未获得授权

    注意:这里unionId如果满足上面2、3所说获取条件,则会在这一步里返回,如果不满足则需要调用wx.getUserInfo来获取,这个方法需要获得用户授权。

    public class Test {

    private static final String APPID = "";// 微信应用唯一标识
    private static final String SECRET = "";

    public void main(String code) {

    JSONObject jsonObject = code2sessionKey(code);

    String openId = jsonObject.getString("openid");// 用户唯一标识

    String session_key = jsonObject.getString("session_key");// 密钥

    // 满足UnionID下发条件的情况下,返回
    String unionId = jsonObject.getString("unionid");

    }

    /**
    * 发送请求用code换取sessionKey和相关信息
    *
    * @param code
    * @return
    */
    public static JSONObject code2sessionKey(String code) {
    String stringToken = String.format(
    "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
    APPID, SECRET, code);
    String response = HttpUtils.httpsRequestToString(stringToken, "GET", null);
    return JSON.parseObject(response);
    }

    /**
    * 发送https请求
    *
    * @param path
    * @param method
    * @param body
    * @return
    */
    public static String httpsRequestToString(String path, String method, String body) {
    if (path == null || method == null) {
    return null;
    }
    String response = null;
    InputStream inputStream = null;
    InputStreamReader inputStreamReader = null;
    BufferedReader bufferedReader = null;
    HttpsURLConnection conn = null;
    try {
    // 创建SSLConrext对象,并使用我们指定的信任管理器初始化
    SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
    TrustManager[] tm = { new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
    return null;
    }

    } };
    sslContext.init(null, tm, new java.security.SecureRandom());

    // 从上面对象中得到SSLSocketFactory
    SSLSocketFactory ssf = sslContext.getSocketFactory();

    URL url = new URL(path);
    conn = (HttpsURLConnection) url.openConnection();
    conn.setSSLSocketFactory(ssf);

    conn.setDoOutput(true);
    conn.setDoInput(true);
    conn.setUseCaches(false);

    // 设置请求方式(get|post)
    conn.setRequestMethod(method);

    // 有数据提交时
    if (null != body) {
    OutputStream outputStream = conn.getOutputStream();
    outputStream.write(body.getBytes("UTF-8"));
    outputStream.close();
    }

    // 将返回的输入流转换成字符串
    inputStream = conn.getInputStream();
    inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
    bufferedReader = new BufferedReader(inputStreamReader);
    String str = null;
    StringBuffer buffer = new StringBuffer();
    while ((str = bufferedReader.readLine()) != null) {
    buffer.append(str);
    }

    response = buffer.toString();
    } catch (Exception e) {

    } finally {
    if (conn != null) {
    conn.disconnect();
    }
    try {
    bufferedReader.close();
    inputStreamReader.close();
    inputStream.close();
    } catch (IOException execption) {

    }
    }
    return response;
    }
    }

    2、获得授权情况下

    当前台获得了用户的授权后,我们就可以获得用户的个人信息以及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 ) 得到字符串,用于校验用户信息,参考文档 signature。
    encryptedData String 包括敏感数据在内的完整用户信息的加密数据,详细见加密数据解密算法
    iv String 加密算法的初始向量,详细见加密数据解密算法

    示例代码:

    <!--wxml-->
    <!-- 如果只是展示用户头像昵称,可以使用 <open-data /> 组件 -->
    <open-data type="userAvatarUrl"></open-data>
    <open-data type="userNickName"></open-data>
    <!-- 需要使用 button 来授权登录 -->
    <button wx:if="{{canIUse}}" open-type="getUserInfo" bindgetuserinfo="bindGetUserInfo">授权登录</button>
    <view wx:else>请升级微信版本</view>
    //js
    Page({
    data: {
    canIUse: wx.canIUse('button.open-type.getUserInfo')
    },
    onLoad: function() {
    // 查看是否授权
    wx.getSetting({
    success: function(res){
    if (res.authSetting['scope.userInfo']) {
    // 已经授权,可以直接调用 getUserInfo 获取头像昵称
    wx.getUserInfo({
    success: function(res) {
    console.log(res.userInfo)
    }
    })
    }
    }
    })
    },
    bindGetUserInfo: function(e) {
    console.log(e.detail.userInfo)
    }
    })
    前台调用getUserInfo后,可以将获得的code值,encryptedData加密数据包,iv初始向量提示发给后台

    后台代码如下:

    public class Test {

    private static final String APPID = "";// 微信应用唯一标识
    private static final String SECRET = "";

    public void main(String code,String encryptedData,String iv) {

    JSONObject jsonObject = code2sessionKey(code);

    String openId = jsonObject.getString("openid");// 用户唯一标识

    String session_key = jsonObject.getString("session_key");// 密钥

    // 解密encryptedData,获取unionId相关信息
    JSONObject json = decryptionUserInfo(encryptedData, session_key, iv);
    }

    /**
    * 发送请求用code换取sessionKey和相关信息
    *
    * @param code
    * @return
    */
    public static JSONObject code2sessionKey(String code) {
    String stringToken = String.format(
    "https://api.weixin.qq.com/sns/jscode2session?appid=%s&secret=%s&js_code=%s&grant_type=authorization_code",
    APPID, SECRET, code);
    String response = HttpUtils.httpsRequestToString(stringToken, "GET", null);
    return JSON.parseObject(response);
    }
    /**
    * 小程序解密用户数据
    *
    * @param encryptedData
    * @param sessionKey
    * @param iv
    * @return
    */
    public static JSONObject decryptionUserInfo(String encryptedData, String sessionKey, String iv) {
    // 被加密的数据
    byte[] dataByte = Base64.decode(encryptedData);
    // 加密秘钥
    byte[] keyByte = Base64.decode(sessionKey);
    // 偏移量
    byte[] ivByte = Base64.decode(iv);

    try {
    // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要
    int base = 16;
    if (keyByte.length % base != 0) {
    int groups = keyByte.length / base + (keyByte.length % base != 0 ? 1 : 0);
    byte[] temp = new byte[groups * base];
    Arrays.fill(temp, (byte) 0);
    System.arraycopy(keyByte, 0, temp, 0, keyByte.length);
    keyByte = temp;
    }
    // 初始化
    Security.addProvider(new BouncyCastleProvider());
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7Padding", "BC");
    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, "UTF-8");
    return JSONObject.parseObject(result);

    }
    } catch (Exception e) {
    e.printStackTrace();
    }
    return null;
    }
    /**
    * 发送https请求
    *
    * @param path
    * @param method
    * @param body
    * @return
    */
    public static String httpsRequestToString(String path, String method, String body) {
    if (path == null || method == null) {
    return null;
    }
    String response = null;
    InputStream inputStream = null;
    InputStreamReader inputStreamReader = null;
    BufferedReader bufferedReader = null;
    HttpsURLConnection conn = null;
    try {
    // 创建SSLConrext对象,并使用我们指定的信任管理器初始化
    SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
    TrustManager[] tm = { new X509TrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    @Override
    public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    }

    @Override
    public X509Certificate[] getAcceptedIssuers() {
    return null;
    }

    } };
    sslContext.init(null, tm, new java.security.SecureRandom());

    // 从上面对象中得到SSLSocketFactory
    SSLSocketFactory ssf = sslContext.getSocketFactory();

    URL url = new URL(path);
    conn = (HttpsURLConnection) url.openConnection();
    conn.setSSLSocketFactory(ssf);

    conn.setDoOutput(true);
    conn.setDoInput(true);
    conn.setUseCaches(false);

    // 设置请求方式(get|post)
    conn.setRequestMethod(method);

    // 有数据提交时
    if (null != body) {
    OutputStream outputStream = conn.getOutputStream();
    outputStream.write(body.getBytes("UTF-8"));
    outputStream.close();
    }

    // 将返回的输入流转换成字符串
    inputStream = conn.getInputStream();
    inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
    bufferedReader = new BufferedReader(inputStreamReader);
    String str = null;
    StringBuffer buffer = new StringBuffer();
    while ((str = bufferedReader.readLine()) != null) {
    buffer.append(str);
    }

    response = buffer.toString();
    } catch (Exception e) {

    } finally {
    if (conn != null) {
    conn.disconnect();
    }
    try {
    bufferedReader.close();
    inputStreamReader.close();
    inputStream.close();
    } catch (IOException execption) {

    }
    }
    return response;
    }
    }
    与未授权情况下,多了一步解密数据包的动作,解密后就能获得我们需要的数据啦,所以在处理登录时一定要考虑好用户授权情况。

    附:encryptedData 解密后 json 结构

    {
    "openId": "OPENID",
    "nickName": "NICKNAME",
    "gender": GENDER,
    "city": "CITY",
    "province": "PROVINCE",
    "country": "COUNTRY",
    "avatarUrl": "AVATARURL",
    "unionId": "UNIONID",
    "watermark":
    {
    "appid":"APPID",
    "timestamp":TIMESTAMP
    }
    }

    (填坑..) 注意:上述返回的结果中如果还是没有unionId,这个时候需要检查是否完成开发者资质认证...

    微信开放平台绑定小程序流程

    前提:微信开放平台帐号必须已完成开发者资质认证

    开发者资质认证流程:

    登录微信开放平台(open.weixin.qq.com) – 帐号中心 – 开发者资质认证

    绑定流程:

    登录微信开放平台(open.weixin.qq.com)—管理中心—公众帐号—绑定公众帐号



    原文链接:https://blog.csdn.net/durianll/article/details/81388355

  • 相关阅读:
    每天一个linux命令(6):mv命令
    每天一个linux命令(5):rm 命令
    每天一个linux命令(4):mkdir命令
    每天一个linux命令(3):pwd命令
    每天一个linux命令(2):cd命令
    每天一个linux命令(1):ls命令
    Linux下svn命令详解
    Linux下SVN安装配置
    SVN命令使用详解
    分布式Web服务器架构
  • 原文地址:https://www.cnblogs.com/onesea/p/15048824.html
Copyright © 2011-2022 走看看