微信授權—網頁+APP


@

前言

微信網頁登錄授權、APP登錄授權、JS-SDK接口調用

溫馨提示:實踐某項功能請至少讀完准備工作,可以避免很多的坑

官方文檔

H5網頁授權

https://developers.weixin.qq.com

APP授權

https://developers.weixin.qq.com/doc/oplatform/Mobile_App/WeChat_Login/Development_Guide.html

小程序授權

[https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html](https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/authorize.html)

JS-SDK說明文檔

https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.htmlhttps://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html)

准備工作

  • 測試:

    • natapp 內網穿透工具

      開發階段可用此工具獲取域名

    • 申請測試賬號

      測試賬號擁有微信幾乎所有接口的能力,可以先通過測試賬號提前驗證

    • 獲取測試號信息

      image-20211023123859614

    • 賬號驗證(接口配置)

      填寫接口配置信息,此信息需要你有自己的服務器資源,填寫的URL需要正確響應微信發送的Token驗證,請閱讀消息接口使用指南

      字段 說明
      URL 開發者用來接收微信消息和事件的接口URL(可以外網訪問自己服務器IP:端口號也可以)
      TOKEN 自己隨意填寫,用作生成簽名(該Token會和接口URL中包含的Token進行比對,從而驗證安全性)

      驗證接口實現:

      @ApiOperation("微信公眾號認證入口")
      @GetMapping(value = "/wechat/api/wxServerValdation")
      public String wxServerValdation(String signature, String timestamp, String nonce, String echostr){
          if (Objects.isNull(signature)|| Objects.isNull(timestamp) || Objects.isNull(nonce) || Objects.isNull(echostr)){
              return "fail";
          }
          ArrayList<String> list= new ArrayList<>();
          list.add(nonce);
          list.add(timestamp);
          //這是第5步中你設置的Token
          list.add(WxMpConfig.token);
          Collections.sort(list);
          String sha1Singnature = DigestUtils.sha1Hex(list.get(0)+list.get(1)+list.get(2));
          if (sha1Singnature.equals(signature)){
              return echostr;
          }else {
              return "fail";
          }
      }
      

      此時的URL可以設置為 :http://IP:端口號/wechat/api/wxServerValdation

      也可以填寫為域名+/wechat/api/wxServerValdation

      驗證字段說明

      參數 描述
      signature 微信加密簽名,signature結合了開發者填寫的token參數和請求中的timestamp參數、nonce參數。
      timestamp 時間戳
      nonce 隨機數
      echostr 隨機字符串

      開發者通過檢驗signature對請求進行校驗(下面有校驗方式)。若確認此次GET請求來自微信服務器,請原樣返回echostr參數內容,則接入生效,成為開發者成功,否則接入失敗。加密/校驗流程如下:

      1)將token、timestamp、nonce三個參數進行字典序排序

      2)將三個參數字符串拼接成一個字符串進行sha1加密

      3)開發者獲得加密后的字符串可與signature對比,標識該請求來源於微信

      驗證工具:

      http://mp.weixin.qq.com/debug/

    • JS接口安全域名

      設置JS接口安全域后,通過關注該測試號,開發者即可在該域名下調用微信開放的JS接口,請閱讀微信JSSDK開發文檔

      請注意,這里填寫的是域名(是一個字符串),而不是URL,因此請勿加 http:// 等協議頭;

    • 網頁授權獲取用戶基本信息

      微信根據此域名檢查回調地址域名

      請注意,這里填寫的是域名(是一個字符串),而不是URL,因此請勿加 http:// 等協議頭;

  • 正式:

    • 申請賬號
      • 正式的微信賬號分為訂閱號、服務號、企業號,認證都需要300元,但是使用授權和接口功能必須認證。

      • 但是訂閱號不能直接獲取到用戶的OpenID,所以盡量申請一個服務號。(這點微信也是不會提醒用戶的,但是當你看了一下測試賬號是訂閱號,就直接申請了一個訂閱號結果就不言而喻了,接口處就會提醒用戶,只有服務號才能使用授權接口。)

      • 不過訂閱號也是有一個漏洞的,如果你是在公眾號內使用,是可以迂回獲取到用戶信OpenID的。可以參考下面的文章:https://blog.csdn.net/vbirdbest/article/details/51217478

    • 賬號認證
    • 接口配置

      接口配置主要有一下三點,其中IP白名單要加入所有訪問微信接口服務器的IP

      • 基本配置

      • 服務器配置

      • 功能設置

流程講解

  • 總體流程

    接入微信公眾平台開發,開發者需要按照如下步驟完成:

    1、填寫服務器配置

    2、驗證服務器地址的有效性

    3、依據接口文檔實現業務邏輯

  • 網頁授權

    • 功能

      網頁授權獲取用戶基本信息:通過該接口,可以獲取用戶的基本信息(獲取用戶的OpenID是無需用戶同意的,獲取用戶的基本信息則需用戶同意)

    • 注意
      • 關於網頁授權access_token和普通access_token的區別

        1、微信網頁授權是通過OAuth2.0機制實現的,在用戶授權給公眾號后,公眾號可以獲取到一個網頁授權特有的接口調用憑證(網頁授權access_token),通過網頁授權access_token可以進行授權后接口調用,如獲取用戶基本信息;

        2、其他微信接口,需要通過基礎支持中的“獲取access_token”接口來獲取到的普通access_token調用。

    • 流程

      網頁授權流程分為四步:

      1、引導用戶進入授權頁面同意授權,獲取code(拉起授權頁面)

      2、通過code換取網頁授權access_token(與基礎支持中的access_token不同)

      3、如果需要,開發者可以刷新網頁授權access_token,避免過期

      4、通過網頁授權access_token和openid獲取用戶基本信息(支持UnionID機制)

      5 附:檢驗授權憑證(access_token)是否有效

  • APP授權

    • 第三方發起微信授權登錄請求,微信用戶允許授權第三方應用后,微信會拉起應用或重定向到第三方網站,並且帶上授權臨時票據 code 參數;
    • 通過 code 參數加上 AppID 和 AppSecret 等,通過 API 換取 access_token;
    • 通過 access_token 進行接口調用,獲取用戶基本數據資源或幫助用戶實現基本操作。
  • 小程序授權

    • 調用 wx.login() 獲取 臨時登錄憑證code ,並回傳到開發者服務器。

    • 調用 auth.code2Session 接口,換取 用戶唯一標識 OpenID 、 用戶在微信開放平台帳號下的唯一標識UnionID(若當前小程序已綁定到微信開放平台帳號) 和 會話密鑰 session_key

    • 之后開發者服務器可以根據用戶標識來生成自定義登錄態,用於后續業務邏輯中前后端交互時識別用戶身份

  • JS-SDK接口調用

功能實現

  • 屬性配置

    • yml 可以把地址也存放到配置里

      #微信開放平台賬號
      wechat:
        #服務號
        mp:
          appId: 
          secret: 
          token: 
          aesKey: 
      
    • WxMpConfig 配置類

      @Component
      @ConfigurationProperties("wechat.mp")
      public class WxMpConfig {
          /**
           * 設置微信公眾號的appid
           */
          public static String appId;
      
          /**
           * 設置微信公眾號的app secret
           */
          public static String secret;
      
          /**
           * 設置微信公眾號的token
           */
          public static String token;
      
          /**
           * js 回調地址
           */
          public static String jsUrl;
      
          /**
           * 微信認證路徑
           */
          public static String url;
      
          /**
           * 微信認證路徑
           */
          public static String aesKey;
      
          public void setAppId(String appId) {
              WxMpConfig.appId = appId;
          }
      
          public void setSecret(String secret) {
              WxMpConfig.secret = secret;
          }
      
          public void setToken(String token) {
              WxMpConfig.token = token;
          }
      
          public void setJsUrl(String jsUrl) {
              WxMpConfig.jsUrl = jsUrl;
          }
      
          public void setUrl(String url) {
              WxMpConfig.url = url;
          }
      
          public void setAesKey(String aesKey) {
              WxMpConfig.aesKey = aesKey;
          }
      
      }
      
    • WxAuthUtil

      @Slf4j
      public class WxAuthUtil {
      
          /**
           *  APPID
           */
          public final static String APP_ID = "APP_ID";
          /**
           * SECRET
           */
          public final static String SECRET = "SECRET";
      
          public final static String CODE = "CODE";
      
          public final static String OPEN_ID = "OPEN_ID";
      
          public final static String ACCESS_TOKEN = "ACCESS_TOKEN";
      
          public final static String REFRESH_TOKEN = "REFRESH_TOKEN";
      
          public final static String WX_ACCESS_TOKEN = "WX_ACCESS_TOKEN";
      
          public final static String WX_JSAPI_TICKET = "WX_JSAPI_TICKET";
      
          /**
           * 公用獲取access_token訪問地址
           */
          public final static String JS_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=APPSECRET";
      
          /**
           * 獲取jsApiTicket訪問地址
           */
          public final static String JS_API_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=ACCESS_TOKEN&type=jsapi";
      
          /**
           * 網頁授權 獲取access_token訪問地址
           */
          public final static String H5_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=APP_ID&secret=SECRET&code=CODE&grant_type=authorization_code";
      
          /**
           * 檢查access_token是否失效地址
           */
          public final static String CHECK_H5_URL = "https://api.weixin.qq.com/sns/auth?access_token="+ACCESS_TOKEN+"&openid="+OPEN_ID;
      
          /**
           * 刷新access_token地址
           */
          public final static String REFRESH_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=" + OPEN_ID
                  + "&grant_type=refresh_token&refresh_token=" + REFRESH_TOKEN;
      
          /**
           * 拉取用戶信息
           */
          public final static String USER_INFO_URL =  "https://api.weixin.qq.com/sns/userinfo?access_token=" + ACCESS_TOKEN + "&openid=" + OPEN_ID
                  + "&lang=zh_CN";
      
          /**
           * 獲取accessToken
           * @return Map
           * @throws IOException
           */
          public static JSONObject getAccessToken() throws IOException {
              String requestUrl = JS_ACCESS_TOKEN_URL.replace("APPID", WxMpConfig.appId).replace("APPSECRET",WxMpConfig.secret);
              //向微信發送get請求
              log.info("requestUrl:  {}", requestUrl);
              JSONObject callBack = doGet(requestUrl);
              log.info("獲取accessToken:{}", callBack);
              return callBack;
          }
      
          /**
           * 微信請求
           * @param url
           * @return Object
           * @throws IOException
           */
          public static JSONObject doGet(String url) throws IOException {
              JSONObject jsonObject = null;
              HttpClient client = HttpClientBuilder.create().build();
              final HttpGet httpGet = new HttpGet(url);
              HttpResponse response = client.execute(httpGet);
              HttpEntity entity = response.getEntity();
              if (entity != null) {
                  // 返回結果轉化為JSON對象
                  final String result = EntityUtils.toString(entity, "UTF-8");
                  jsonObject = JSON.parseObject(result);
              }
      
              return jsonObject;
          }
      }
      
  • 授權

    這里只詳細介紹網頁授權,其他方式大同小異,官方文檔可以查看

    • 獲取 code (拉起授權頁面)

    APP 與 H5 獲取 code 的區別:h5 是前端通過后台拿到授權 url,然后前端請求該 url 得到 code 再請求后台;APP 則是前端配合使用微信開放平台提供的 SDK 進行授權登錄請求,用戶同意授權后得到 code 再去請求后台;

    獲取code請求路徑

    https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx6fb2b539e0663d3b&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_re

    獲取code 需要先設置REDIRECT_URI 回調路徑

    如果用戶同意授權,頁面將跳轉至 redirect_uri/?code=CODE&state=STATE。

    code說明 : code作為換取access_token的票據,每次用戶授權帶上的code將不一樣,code只能使用一次,5分鍾未被使用自動過期。

    • 獲取access_token

    獲取code后,請求以下鏈接獲取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code

    參數 是否必須 說明
    appid 公眾號的唯一標識
    secret 公眾號的appsecret
    code 填寫第一步獲取的code參數
    grant_type 填寫為authorization_code
    • 通過openID + access_token獲取用戶信息

    需要用緩存存儲access_token

    https://api.weixin.qq.com/sns/userinfo?access_token=" + ACCESS_TOKEN + "&openid=" + OPEN_ID + "&lang=zh_CN";
    
    • access_token過期刷新

    檢查token是否失效

@ApiOperation("網頁拉起微信授權頁面,返回微信校驗信息")
    @GetMapping("/auth")
    public R<String> callBack(@ApiParam(value = "*code",required = true)String code) throws IOException {
        /**
         * 獲取到授權標志code(用戶同意微信授權之后產生的code)
         */
        log.info("進入微信回調code={}",code);
        String url = WxAuthUtil.H5_ACCESS_TOKEN_URL.
                replace(WxAuthUtil.APP_ID,WxMpConfig.appId).
                replace(WxAuthUtil.SECRET, WxMpConfig.secret).
                replace(WxAuthUtil.CODE,code);
        JSONObject jsonObject = WxAuthUtil.doGet(url);
        log.info("授權結果{}",jsonObject);
        return R.ok(jsonObject.getString("openid"));
    }
    
     @ApiOperation("拉取微信用戶信息,每天獲取token的次數受限,需要將accessToken緩存(目前不需要獲取用戶信息)")
    @GetMapping("/userInfo")
    public R<WxUserVO> getWeiChatUserInfo(@RequestParam("code")String openid,
                                          @RequestParam("accessToken")String accessToken,
                                          @RequestParam("refreshToken")String refreshToken) throws IOException {
        /**
         * 校驗access_token是否失效
         */
        String checkoutUrl = WxAuthUtil.CHECK_H5_URL.
                replace(WxAuthUtil.ACCESS_TOKEN, accessToken).
                replace(WxAuthUtil.OPEN_ID,openid);

        JSONObject checkoutInfo = WxAuthUtil.doGet(checkoutUrl);
        log.info("校驗信息-----{}",checkoutInfo.toString());
        if (!"0".equals(checkoutInfo.getString("errcode"))) {
            // 刷新access_token
            String refreshTokenUrl = WxAuthUtil.REFRESH_TOKEN_URL
                    .replace(WxAuthUtil.APP_ID,WxMpConfig.appId)
                    .replace(WxAuthUtil.REFRESH_TOKEN, refreshToken);
            JSONObject refreshInfo = WxAuthUtil.doGet(refreshTokenUrl);
            System.out.println(refreshInfo.toString());
            accessToken = refreshInfo.getString("access_token");
        }
        /**
         * 使用access_token拉取用戶信息
         */
        String infoUrl = WxAuthUtil.USER_INFO_URL
                .replace(WxAuthUtil.ACCESS_TOKEN, accessToken)
                .replace(WxAuthUtil.OPEN_ID, openid);
        JSONObject userInfo = WxAuthUtil.doGet(infoUrl);
        WxUserVO wxUserVO = JSONObject.parseObject(String.valueOf(userInfo), WxUserVO.class);
        log.info("用戶數據-----{}", userInfo);

        return R.ok(wxUserVO);
    }
  • JS-SDK生成signature

@ApiOperation("生成微信JS-SDK簽名")
    @PostMapping( "/signature")
    public R<Map<String, String>> makeWxSignature(@RequestBody @Validated WxH5VO wxH5VO){

        //獲取jsapiTicket
        String jsapiTicket = getJsapiTicket();

        Map<String, String> ret = new HashMap<>(6);
        String nonceStr = RandomUtils.getRandomStr();
        long timestamp = System.currentTimeMillis()/1000L;

        //加密
        String signature = SHA1.genWithAmple(
                "jsapi_ticket=" + jsapiTicket, "noncestr=" + nonceStr, "timestamp=" + timestamp, "url=" + wxH5VO.getUrl());

        ret.put("url", wxH5VO.getUrl());
        ret.put("jsapi_ticket", jsapiTicket);
        ret.put("nonceStr", nonceStr);
        ret.put("timestamp", Long.toString(timestamp));
        ret.put("signature", signature);
        ret.put("appId", WxMpConfig.appId);

        return R.ok(ret);
    }

    /**
     * 1、緩存中獲取 jsApiTicket
     * 2、沒有 已過期 重新獲取
     * 2、獲取緩存中的access_token
     * 3、沒有 證明已經過期 重新獲取
     * 獲取jsapiTicket
     * @return jsapiTicket
     */
    private String getJsapiTicket(){
        String jsapiTicket = null;
        try{
            //根據access_token 獲取jsapiTicket
            jsapiTicket = redisService.getCacheObject(WxAuthUtil.WX_JSAPI_TICKET);
            if(Objects.isNull(jsapiTicket)){
                //獲取access_token
                String accessToken = redisService.getCacheObject(WxAuthUtil.WX_ACCESS_TOKEN);
                if(Objects.isNull(accessToken)){
                    JSONObject accessTokenMap = WxAuthUtil.getAccessToken();
                    accessToken = accessTokenMap.getString("access_token");
                    log.info("訪問獲取access_token數據{}", accessTokenMap);
                    redisService.setCacheObject(WxAuthUtil.WX_ACCESS_TOKEN, accessToken, accessTokenMap.getLong("expires_in"), TimeUnit.SECONDS);
                }
                String requestUrl = WxAuthUtil.JS_API_TICKET_URL.replace("ACCESS_TOKEN", accessToken);
                //向微信發送get請求
                JSONObject  callBack = WxAuthUtil.doGet(requestUrl);
                log.info("訪問獲取jsApiTicket數據{}", callBack);
                jsapiTicket = callBack.getString("ticket");
                redisService.setCacheObject(WxAuthUtil.WX_JSAPI_TICKET, jsapiTicket,callBack.getLong("expires_in"), TimeUnit.SECONDS);
            }
            log.info("獲取jsApiTicket:{}", jsapiTicket);
        }catch (IOException e){
            log.info("獲取簽名異常:" + e.getMessage());
        }
        return jsapiTicket;
    }

錯誤處理

官方常見錯誤及解決方法

  • invalid signature簽名錯誤。建議按如下順序檢查:

    • 確認簽名算法正確,可用http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign 頁面工具進行校驗。
    • 確認config中nonceStr(js中駝峰標准大寫S), timestamp與用以簽名中的對應noncestr, timestamp一致。
    • 確認url是頁面完整的url(請在當前頁面alert(location.href.split('#')[0])確認),包括'http(s)😕/'部分,以及'?'后面的GET參數部分,但不包括'#'hash后面的部分。
    • 確認 config 中的 appid 與用來獲取 jsapi_ticket 的 appid 一致。
    • 確保一定緩存access_token和jsapi_ticket
    • 確保你獲取用來簽名的url是動態獲取的,動態頁面可參見實例代碼中php的實現方式。如果是html的靜態頁面在前端通過ajax將url傳到后台簽名,前端需要用js獲取當前頁面除去'#'hash部分的鏈接(可用location.href.split('#')[0]獲取,而且需要encodeURIComponent),因為頁面一旦分享,微信客戶端會在你的鏈接末尾加入其它參數,如果不是動態獲取當前鏈接,將導致分享后的頁面簽名失敗。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM