微信小程序获取手机号授权完整实现

 

一.app.js配置

App({
  onLaunch: function () {
    
  },
  onShow: function () {
    
  },
  onHide: function () {
    //    console.log(getCurrentPages())
  },
  onError: function (msg) {
    //console.log(msg)
  },

  globalData: {
    userInfo: null,
    appid: "",
    appsecret: "",
    phoneNumber: '',
    getSessionKeyUrl: 'http://localhost:8888/test/getsessionkey',
    getPhoneUrl: 'http://localhost:8888/test/getphone',
    orderHomeUrl: 'http://localhost:8888/login?phone=',
    driverLoginUrl: 'http://localhost:8888/driver/login'
  }
});

二.app.json配置

{
  "pages": [
    "pages/login/login",
    "pages/main/main",
    "pages/driverlogin/driverlogin"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#F8F8F8",
    "navigationBarTitleText": "小程序",
    "navigationBarTextStyle": "black"
  },
  "sitemapLocation": "sitemap.json"
}

三.微信授权

1.页面

<view wx:if="{{isShow}}" >
    <view class="container" style="padding:0rpx">
        <image src='./login.png' style="z-index:-99;width:100%;height:100%;" mode="widthFix"></image>
        <view style="margin-top:10rpx; width: 98%;" >
            <button class="show" type="primary" lang="zh_CN" open-type='getPhoneNumber'  bindgetphonenumber="getPhoneNumber">{{wechat}}</button>
        </view>
    </view>
</view> 
<view class="container" wx:else></view>

2.页面样式

.show{
  display: block;
  border-radius: 8rpx;
  margin: 20rpx 20rpx 20rpx 20rpx;
  font-size: 35rpx;
}

.container{
  position: fixed;  /*关键属性,设置为fixed*/
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
}

3.json

{
  "usingComponents": {}
}

4.js

const app = getApp();
Page({
  data: {
    // 判断小程序的API,回调,参数,组件等是否在当前版本可用。
    canIUse: wx.canIUse('button.open-type.getPhoneNumber'),
    wechat: '微信快捷登录',
    isShow: false
  },
  onLoad: function() {
    // 从缓存中取手机号
    console.log("获取手机号!")
    try {
      var value = wx.getStorageSync('phoneNumber')
      if (value) { // 说明已登录 跳转 页面
        console.log("获取缓存:"+value)
        wx.navigateTo({
          url: '../main?param=' + value
        })
      }else{// 未登录 显示 微信授权页面
        this.setData({
          isShow: true
        })
      }
    } catch (e) {

    }
    // 解决第一次获取手机号失败问题
    wx.login({
      success: res => {
        if(res.code){
          console.log("code->", res.code)
        }
      }
    })
  },
  // 0.获取手机号授权
  getPhoneNumber: function(e) {
    // 用户拒绝授权
    if(e.detail.errMsg == "getPhoneNumber:fail user deny") {
      wx.showToast({
        icon: "none",
        title: '请允许获取手机号,否则功能不可用!',
      })
      return
    }
   
    /// 用户允许授权
    console.log("iv->", e.detail.iv); //包括敏感数据在内的完整用户信息的加密数据,需要解密
    console.log("encryptedData->", e.detail.encryptedData); //加密算法的初始向量,解密需要用到

    /// 获取手机号
    // 1.获取临时登录凭证code
    wx.login({
      success: res => {
        if(res.code){
          this.code = res.code;
          console.log("code->", res.code)
          this.getSessionKey(res.code, e.detail.encryptedData, e.detail.iv);
        }
      }
    })
    },
  // 2.访问登录凭证校验接口获取session_key(后续改成后台实现)
  getSessionKey: function(js_code, encryptedData, iv) {
    wx.request({
      url: app.globalData.getSessionKeyUrl,
      data: {
          'jscode': js_code,
          'sign': 'sign'
      },
      method: 'GET', 
      header: {
          'content-type': 'application/json'
      }, // 设置请求的 header
      success: function(data) {
        console.log("session_key->", data.data)
        if(data.data==undefined){
          wx.showToast({
            icon: "none",
            title: 'session_key获取失败,请重新登录!',
          })
          return
        }
        // 3. 解密获取手机号
        wx.request({
          url: app.globalData.getPhoneUrl,
          data: {
              'encryptedData': encodeURIComponent(encryptedData),//需要进行编码
              'iv': iv,
              'sessionKey': data.data,
              'sign': 'sign',
          },
          method: 'GET', 
          header: {
              'content-type': 'application/json'
          }, // 设置请求的 header
          success: function(data2) {
              console.log(data2.data.phoneNumber)
              if(data2.statusCode == 200) { 
                if(data2.data.phoneNumber==undefined){
                  // 获取手机号失败 跳转到 常规 用户登录页面(通过webview)
                  wx.navigateTo({
                    url: '../driverlogin/driverlogin'
                  })
                  return
                }
                // 存储数据到缓存
                wx.setStorage({
                  key:"phoneNumber",
                  data:data2.data.phoneNumber
                })
                // 4.跳转web-view页面
                wx.navigateTo({
                  url: '../main?param=' + data2.data.phoneNumber
                }) // 4
              }
          },
          fail: function(err) {
              console.log(err);
              wx.showToast({
                icon: "none",
                title: '获获取手机号失败,请重试!',
              })
          }
        })// 3
      },
      fail: function(err) {
          console.log(err);
          wx.showToast({
            icon: "none",
            title: 'session_key获取失败,请重新登录!',
          })
          return
      }
    })
  }// 2
})

 

4.后端实现

1)获取sessionkey
@GetMapping("getsessionkey")
@ResponseBody
public synchronized Object getSessionKey(String jscode,String sign) {
    MyResult res = new MyResult();
    if(StringUtils.isBlank(checkcode)) {
        res.setCode(-1);
        res.setMsg("鉴权失败!");
        return res;
    }
    
    if(!checkcode.equals("sign")) {
        res.setCode(-1);
        res.setMsg("鉴权失败!");
        return res;
    }
    
    if(StringUtils.isBlank(sign)||checkcode.equals("undefined")) {
        res.setCode(-1);
        res.setMsg("无效参数!");
        return res;
    }
    
    OkHttpClient client = new OkHttpClient().newBuilder()
              .build();
    String requestStr = miniProgramConfig.getSessionkeyurl()
              +"?appid="+miniProgramConfig.getAppid()
            +"&secret="+miniProgramConfig.getSecret()
              +"&js_code="+jscode
              +"&grant_type=authorization_code";
    Request request = new Request.Builder()
      .url(requestStr)
      .method("GET", null)
      .build();
    try {
        Response response = client.newCall(request).execute();
        String responseStr = response.body().string();
        JSONObject jsonObject = JSONObject.parseObject(responseStr);
        // 获取到session_key
        String session_key = jsonObject.getString("session_key");
        
        if(StringUtils.isBlank(session_key)) {
            res.setCode(-1);
            res.setMsg("获取sessionkey失败!");
            return res;
        }
        return session_key;
    } catch (IOException e) {
        logger.error("encryptedData,decode失败!", e);
        res.setCode(-1);
        res.setMsg("获取sessionkey失败!");
        return res;
    }
}
2)获取解密手机号
@GetMapping("getphone")
    @ResponseBody
    public synchronized Object getPhone(String encryptedData, String iv, String sessionKey,String sign) {
        MyResult res = new MyResult();
        if(StringUtils.isBlank(sign)) {
            res.setCode(-1);
            res.setMsg("鉴权失败!");
            return res;
        }
        
        if(!sign.equals("sign")) {
            res.setCode(-1);
            res.setMsg("鉴权失败!");
            return res;
        }
        
        if(StringUtils.isBlank(encryptedData)
                ||encryptedData.equals("undefined")
                ||StringUtils.isBlank(iv)
                ||iv.equals("undefined")
                ||StringUtils.isBlank(sessionKey)
                ||sessionKey.equals("undefined")) {
            res.setCode(-1);
            res.setMsg("参数错误!");
            return res;
        }
        
        // 解码
        try {
            encryptedData = URLDecoder.decode(encryptedData,"UTF-8");
        } catch (UnsupportedEncodingException e) {
            res.setCode(-1);
            res.setMsg("encryptedData,decode失败!");
            logger.error("encryptedData,decode失败!", e);
            return res;
        }
        return WechatDecryptDataUtil.decryptData(encryptedData, sessionKey, iv);
        
    }
3)解密工具类
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.encoders.Base64;

import com.zit.tis.wechat.miniproject.MiniProgramController;

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.security.Security;

/**
 * 微信工具类
 */
public class WechatDecryptDataUtil {
    private static Logger logger = LogManager.getLogger(WechatDecryptDataUtil.class);

    public static void main(String[] args) {
        String result = decryptData(
                "111==",
                "222==",
                "333=="
        );
        System.out.println("result = " + result);
    }

    public synchronized static String decryptData(String encryptDataB64, String sessionKeyB64, String ivB64) {
        String res = null;
        try {
            res = new String(
                    decryptOfDiyIV(
                            Base64.decode(encryptDataB64),
                            Base64.decode(sessionKeyB64),
                            Base64.decode(ivB64)
                    )
            );
        } catch (Exception e) {
            logger.error("encryptDataB64:"+encryptDataB64+"\n"+"sessionKeyB64:"+sessionKeyB64+"\n"+"ivB64:"+ivB64);
        }
        
        return res;
    }

    private static final String KEY_ALGORITHM = "AES";
    private static final String ALGORITHM_STR = "AES/CBC/PKCS7Padding";
    private static Key key;
    private static Cipher cipher;

    private static void init(byte[] keyBytes) {
        // 如果密钥不足16位,那么就补足.  这个if 中的内容很重要
        int base = 16;
        if (keyBytes.length % base != 0) {
            int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0);
            byte[] temp = new byte[groups * base];
            Arrays.fill(temp, (byte) 0);
            System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length);
            keyBytes = temp;
        }
        // 初始化
        Security.addProvider(new BouncyCastleProvider());
        // 转化成JAVA的密钥格式
        key = new SecretKeySpec(keyBytes, KEY_ALGORITHM);
        try {
            // 初始化cipher
            cipher = Cipher.getInstance(ALGORITHM_STR, "BC");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 解密方法
     *
     * @param encryptedData 要解密的字符串
     * @param keyBytes      解密密钥
     * @param ivs           自定义对称解密算法初始向量 iv
     * @return 解密后的字节数组
     */
    private static byte[] decryptOfDiyIV(byte[] encryptedData, byte[] keyBytes, byte[] ivs) {
        byte[] encryptedText = null;
        init(keyBytes);
        try {
            cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivs));
            encryptedText = cipher.doFinal(encryptedData);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return encryptedText;
    }

}

 

posted @ 2022-06-03 20:15  三人成虎  阅读(1322)  评论(0编辑  收藏  举报