天天看點

解決網頁微信掃碼登入報40163

1、問題描述:

大家在做微信掃碼登入時,可能會遇到40163的錯誤,具體報錯如下:

{“errcode”:40163,“errmsg”:"code been used}。網上對于這個問題的說法千差萬别,但都沒有一個好的解決方法。經過仔細分析,最終找到了問題所在(如果急于排錯,可直接跳轉到下面第6步)

2、開發準備:

要完成微信掃碼登入功能,要先注冊相關賬号,拿到應用的appId和secret,配置好外網可以通路的網址(授權回調位址)。注意授權回調位址,不能簡單使用用回調方法(例如: /api-uaa/oauth/wechat/callback),而是需要帶上http或者https協定,完整回調位址應該類似于:

http://www.super.com/api-uaa/oauth/weChat/callback(注意:授權回調域非回調位址,授權回調域:類似:www.super.com,回調位址:http://www.super.com/api-uaa/oauth/weChat/callback)。所有都配置好了就需要編寫相關方法了。

下面附上基本的代碼,有需要者隻需要根據自己項目需要修改appId和secret以及回調位址等即可:

(1)配置appId、secret等參數:

wx:
  appId: wxfb72c85ee5329311
  secret: e6eba215f6df135d023e42d69b17f4e0
  redirect_uri: /api-uaa/oauth/wechat/callback
  openVisitUrl: http://www.super.com
  qrCode: https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE&connect_redirect=1#wechat_redirect
  webAccessTokenHttpsOAuth: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
           

(2)編寫相關工具類:

1)AES加密解密

/**
 * @Description: AES加密解密
 * @Date: 2021-03-10
 * @Author: Jonathan.WQ
 * @Version V1.0
 * @Modified By: Jonathan.WQ
 */
public class AesUtil {

    private AesUtil() {
    }

    /**
     * 秘鑰
     */
    public static final String PASSWORD_SECRET_KEY = "EasyRailEveryday";

    /**
     * 初始向量
     */
    public static final String INITIAL_VECTOR = "EasyRailEasyRail";

    /**
     * 加密
     *
     * @param content  需要加密的内容
     * @param password 加密密碼
     * @param keySize  密鑰長度16,24,32(密碼長度為24和32時需要将local_policy.jar/US_export_policy.jar兩個jar包放到JRE目錄%jre%/lib/security下)
     * @return
     */
    public static byte[] encrypt(String content, String password, int keySize) {
        try {
            //密鑰長度不夠用0補齊。
            SecretKeySpec key = new SecretKeySpec(ZeroPadding(password.getBytes(Base64Util.DEFAULT_CHARSET), keySize), "AES");
            //定義加密算法AES、算法模式ECB、補碼方式PKCS5Padding
            //Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            //定義加密算法AES 算法模式CBC、補碼方式PKCS5Padding
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            //CBC模式模式下初始向量 不足16位用0補齊
            IvParameterSpec iv = new IvParameterSpec(ZeroPadding(INITIAL_VECTOR.getBytes(Base64Util.DEFAULT_CHARSET), 16));
            byte[] byteContent = content.getBytes();
            //初始化加密
            //ECB
            //cipher.init(Cipher.ENCRYPT_MODE, key);
            //CBC
            cipher.init(Cipher.ENCRYPT_MODE, key, iv);
            byte[] result = cipher.doFinal(byteContent);
            return result;
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 解密
     *
     * @param content  待解密内容
     * @param password 解密密鑰
     * @param keySize  密鑰長度16,24,32(密碼長度為24和32時需要将local_policy.jar/US_export_policy.jar兩個jar包放到JRE目錄%jre%/lib/security下)
     * @return
     */
    public static String decrypt(byte[] content, String password, int keySize) {
        try {
            //密鑰長度不夠用0補齊。
            SecretKeySpec key = new SecretKeySpec(ZeroPadding(password.getBytes(), keySize), "AES");
            //定義加密算法AES、算法模式ECB、補碼方式PKCS5Padding
            //Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
            //定義加密算法AES 算法模式CBC、補碼方式PKCS5Padding
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            //CBC模式模式下初始向量 不足16位用0補齊
            IvParameterSpec iv = new IvParameterSpec(ZeroPadding(INITIAL_VECTOR.getBytes(Base64Util.DEFAULT_CHARSET), 16));
            // 初始化解密
            //ECB
            //cipher.init(Cipher.DECRYPT_MODE, key);
            //CBC
            cipher.init(Cipher.DECRYPT_MODE, key, iv);
            byte[] result = cipher.doFinal(content);
            return new String(result, Base64Util.DEFAULT_CHARSET);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e) {
            e.printStackTrace();
        } catch (InvalidKeyException e) {
            e.printStackTrace();
        } catch (IllegalBlockSizeException e) {
            e.printStackTrace();
        } catch (BadPaddingException e) {
            e.printStackTrace();
        } catch (InvalidAlgorithmParameterException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将二進制轉換成16進制
     *
     * @param buf
     * @return
     */
    public static String parseByte2HexStr(byte buf[]) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < buf.length; i++) {
            String hex = Integer.toHexString(buf[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex.toUpperCase());
        }
        return sb.toString();
    }

    /**
     * 将16進制轉換為二進制
     *
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1) {
            return null;
        }
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    /**
     * 字元達不到指定長度補0
     *
     * @param in        字元數組
     * @param blockSize 長度
     * @return
     */
    public static byte[] ZeroPadding(byte[] in, Integer blockSize) {
        Integer copyLen = in.length;
        if (copyLen > blockSize) {
            copyLen = blockSize;
        }
        byte[] out = new byte[blockSize];
        System.arraycopy(in, 0, out, 0, copyLen);
        return out;
    }
}

           

2)Http請求工具類

/**
 * @Description: httpClient 工具類</p>
 * @Date: 2021-03-10
 * @Author: Jonathan.WQ
 * @Version V1.0
 * @Modified By:
 */
@Slf4j
public class HttpUtils {

    private HttpUtils(){}

    /**
     * 預設參數設定
     * setConnectTimeout:設定連接配接逾時時間,機關毫秒。
     * setConnectionRequestTimeout:設定從connect Manager擷取Connection 逾時時間,機關毫秒。
     * setSocketTimeout:請求擷取資料的逾時時間,機關毫秒。通路一個接口,多少時間内無法傳回資料,就直接放棄此次調用。 暫時定義15分鐘
     */
    private static RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(600000).setConnectTimeout(600000).setConnectionRequestTimeout(600000).build();

    /**
     * 靜态内部類---作用:單例産生類的執行個體
     * @author Administrator
     *
     */
    private static class LazyHolder {
        private static final HttpUtils INSTANCE = new HttpUtils();

    }
    public static HttpUtils getInstance(){
        return LazyHolder.INSTANCE;
    }

    /**
     * 發送 post請求
     * @param httpUrl 位址
     */
    public static String sendHttpPost(String httpUrl) {
        HttpPost httpPost = new HttpPost(httpUrl);// 建立httpPost
        return sendHttpPost(httpPost);
    }

    /**
     * 發送 post請求
     * @param httpUrl 位址
     * @param params 參數(格式:key1=value1&key2=value2)
     */
    public static String sendHttpPost(String httpUrl, String params) {
        HttpPost httpPost = new HttpPost(httpUrl);// 建立httpPost
        try {
            //設定參數
            StringEntity stringEntity = new StringEntity(params, "UTF-8");
            stringEntity.setContentType("application/x-www-form-urlencoded");
            httpPost.setEntity(stringEntity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sendHttpPost(httpPost);
    }

    /**
     * 發送 post請求
     * @param httpUrl 位址
     * @param maps 參數
     */
    public static String sendHttpPost(String httpUrl, Map<String, String> maps) {
        HttpPost httpPost = new HttpPost(httpUrl);// 建立httpPost
        // 建立參數隊列
        List<NameValuePair> nameValuePairs = new ArrayList<NameValuePair>();
        for (String key : maps.keySet()) {
            nameValuePairs.add(new BasicNameValuePair(key, maps.get(key)));
        }
        try {
            httpPost.setEntity(new UrlEncodedFormEntity(nameValuePairs, "UTF-8"));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return sendHttpPost(httpPost);
    }

    /**
     * 發送Post請求
     * @param httpPost
     * @return
     */
    private static String sendHttpPost(HttpPost httpPost) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        String responseContent = null;
        try {
            // 建立預設的httpClient執行個體
            httpClient = HttpClients.createDefault();
            httpPost.setConfig(requestConfig);
            // 執行請求
            long execStart = System.currentTimeMillis();
            response = httpClient.execute(httpPost);
            long execEnd = System.currentTimeMillis();
            System.out.println("=================執行post請求耗時:"+(execEnd-execStart)+"ms");
            long getStart = System.currentTimeMillis();
            entity = response.getEntity();
            responseContent = EntityUtils.toString(entity, "UTF-8");
            long getEnd = System.currentTimeMillis();
            System.out.println("=================擷取響應結果耗時:"+(getEnd-getStart)+"ms");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 關閉連接配接,釋放資源
                if (response != null) {
                    response.close();
                }
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }

    /**
     * 發送 get請求
     * @param httpUrl
     */
    public static String sendHttpGet(String httpUrl) {
        HttpGet httpGet = new HttpGet(httpUrl);// 建立get請求
        return sendHttpGet(httpGet);
    }

    /**
     * 發送 get請求Https
     * @param httpUrl
     */
    public static String sendHttpsGet(String httpUrl) {
        HttpGet httpGet = new HttpGet(httpUrl);// 建立get請求
        return sendHttpsGet(httpGet);
    }

    /**
     * 發送Get請求
     * @param httpGet
     * @return
     */
    private static String sendHttpGet(HttpGet httpGet) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        String responseContent = null;
        try {
            // 建立預設的httpClient執行個體.
            httpClient = HttpClients.createDefault();
            httpGet.setConfig(requestConfig);
            // 執行請求
            response = httpClient.execute(httpGet);
            entity = response.getEntity();
            responseContent = EntityUtils.toString(entity, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 關閉連接配接,釋放資源
                if (response != null) {
                    response.close();
                }
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }

    /**
     * 發送Get請求Https
     * @param httpGet
     * @return
     */
    private static String sendHttpsGet(HttpGet httpGet) {
        CloseableHttpClient httpClient = null;
        CloseableHttpResponse response = null;
        HttpEntity entity = null;
        String responseContent = null;
        try {
            // 建立預設的httpClient執行個體.
            PublicSuffixMatcher publicSuffixMatcher = PublicSuffixMatcherLoader.load(new URL(httpGet.getURI().toString()));
            DefaultHostnameVerifier hostnameVerifier = new DefaultHostnameVerifier(publicSuffixMatcher);
            httpClient = HttpClients.custom().setSSLHostnameVerifier(hostnameVerifier).build();
            httpGet.setConfig(requestConfig);
            // 執行請求
            response = httpClient.execute(httpGet);
            entity = response.getEntity();
            responseContent = EntityUtils.toString(entity, "UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                // 關閉連接配接,釋放資源
                if (response != null) {
                    response.close();
                }
                if (httpClient != null) {
                    httpClient.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return responseContent;
    }

    /**
     * 發送post請求
     *
     * @param url
     * @param params
     *            沒有參數則傳入null
     * @return
     * @throws IOException
     */
    public static String post(String url, Map<String, String> params) throws IOException {
        // 建立http客戶對象
        CloseableHttpClient client = HttpClients.createDefault();
        // 定義一個通路url後傳回的結果對象
        CloseableHttpResponse response = null;
        // 建立HttpGet對象,如不攜帶參數可以直接傳入url建立對象
        HttpPost post = new HttpPost(url);
        // 從結果對象中擷取的内容
        String content = null;

        // 設定請求頭,為浏覽器通路
        post.setHeader("User-Agent",
                "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.87 Safari/537.36");

        // 設定表單項,對于的是一個添加方法,添加需要的屬性
        List<NameValuePair> nvps = new ArrayList<NameValuePair>();
        if (params != null && params.size() > 0) {
            for (String key : params.keySet()) {
                nvps.add(new BasicNameValuePair(key, params.get(key)));
            }
        }

        // 設定表單項
        post.setEntity(new UrlEncodedFormEntity(nvps, "utf-8"));

        try {
            // 通路這個url,并攜帶參數,擷取結果對象
            response = client.execute(post);

            // 從結果對象中擷取傳回的内容
            content = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 關閉連接配接
            if (response != null) {
                response.close();
            }
            client.close();
        }
        return content;
    }


    /**
     * get方式調用接口
     *
     * @param url
     * @param params
     *            沒有參數則傳入null
     * @return
     * @throws URISyntaxException
     * @throws IOException
     */
    public static String get(String url, Map<String, String> params) throws URISyntaxException, IOException {
        // 建立http客戶對象
        CloseableHttpClient client = HttpClients.createDefault();
        // 定義一個通路url後傳回的結果對象
        CloseableHttpResponse response = null;
        // 從結果對象中擷取的内容
        String content = null;

        // GET方法如果要攜帶參數先建立URIBuilder對象,然後設定參數,如果不攜帶可以忽略這步驟
        URIBuilder builder = new URIBuilder(url);
        if (params != null && params.size() > 0) {
            for (String key : params.keySet()) {
                builder.setParameter(key, params.get(key));
            }
        }

        // 建立HttpGet對象,如不攜帶參數可以直接傳入url建立對象
        HttpGet get = new HttpGet(builder.build());

        try {
            // 通路這個url,并攜帶參數,擷取結果對象
            response = client.execute(get);
            // 從結果對象中擷取傳回的内容
            content = EntityUtils.toString(response.getEntity(), "utf-8");
        } catch (Exception e) {
            e.printStackTrace();

            // 關閉連接配接
        } finally {
            if (response != null) {
                response.close();
            }
            client.close();
        }
        return content;
    }


    /**
     * 向指定URL發送GET方法的請求
     *
     * @param url
     *            發送請求的URL
     * @param param
     *            請求參數,請求參數應該是 name1=value1&name2=value2 的形式。
     * @return URL 所代表遠端資源的響應結果
     */
    @SuppressWarnings("unused")
    public static String sendGet(String url, String param) {
        String result = "";
        BufferedReader in = null;
        try {
            String urlNameString = url + "?" + param;
            URL realUrl = new URL(urlNameString);
            // 打開和URL之間的連接配接
            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();
            //設定相應請求時間
            connection.setConnectTimeout(30000);
            //設定讀取逾時時間
            connection.setReadTimeout(30000);
            // 擷取所有響應頭字段
            Map<String, List<String>> map = connection.getHeaderFields();
            // 周遊所有的響應頭字段
            /*for (String key : map.keySet()) {
                //System.out.println(key + "--->" + map.get(key));
            }*/
            //System.out.println("響應時間--->" + map.get(null));
            // 定義 BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(new InputStreamReader(
                    connection.getInputStream(),"utf-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println(e);
            return "發送GET請求出現異常!";
        }
        // 使用finally塊來關閉輸入流
        finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (Exception e2) {
                e2.printStackTrace();
            }
        }
        return result;
    }


    /**
     * 向指定 URL 發送POST方法的請求
     *
     * @param url
     *            發送請求的 URL
     * @param param
     *            請求參數
     * @return 所代表遠端資源的響應結果
     */
    public static String sendPost(String url, Map<String, String> param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(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)");
            // 發送POST請求必須設定如下兩行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            // 設定相應請求時間
            conn.setConnectTimeout(30000);
            // 設定讀取逾時時間
            conn.setReadTimeout(30000);
            // 擷取URLConnection對象對應的輸出流
            out = new PrintWriter(conn.getOutputStream());
            // 發送請求參數
            if (param != null && param.size() > 0) {
                String paramStr = "";
                for (String key : param.keySet()) {
                    paramStr += "&" + key + "=" + param.get(key);
                }
                paramStr = paramStr.substring(1);
                out.print(paramStr);
            }
            // flush輸出流的緩沖
            out.flush();
            // 定義BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "utf-8"));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            System.out.println(e);
            return "發送 POST 請求出現異常!";
        }
        // 使用finally塊來關閉輸出流、輸入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }

    /**
     * 發送https請求
     *
     *
     * @param requestUrl    請求位址
     * @param requestMethod 請求方式(GET、POST)
     * @param outputStr     送出的資料
     * @return JSONObject(通過JSONObject.get ( key)的方式擷取json對象的屬性值)
     */
    public static JSONObject httpsRequest(String requestUrl, String requestMethod, String outputStr) {
        JSONObject jsonObject = null;
        try {
            // 建立SSLContext對象,并使用我們指定的信任管理器初始化
            TrustManager[] tm = {new MyX509TrustManager()};
            SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
            sslContext.init(null, tm, new java.security.SecureRandom());
            // 從上述SSLContext對象中得到SSLSocketFactory對象
            SSLSocketFactory ssf = sslContext.getSocketFactory();

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

            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setUseCaches(false);
            // 設定請求方式(GET/POST)
            conn.setRequestMethod(requestMethod);

            // 當outputStr不為null時向輸出流寫資料
            if (null != outputStr) {
                OutputStream outputStream = conn.getOutputStream();
                // 注意編碼格式
                outputStream.write(outputStr.getBytes("UTF-8"));
                outputStream.close();
            }
            // 從輸入流讀取傳回内容
            InputStream inputStream = conn.getInputStream();
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "utf-8");
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String str = null;
            StringBuffer buffer = new StringBuffer();
            while ((str = bufferedReader.readLine()) != null) {
                buffer.append(str);
            }

            // 釋放資源
            bufferedReader.close();
            inputStreamReader.close();
            inputStream.close();
            inputStream = null;
            conn.disconnect();
            jsonObject = JSONUtil.parseObj(buffer.toString());
        } catch (ConnectException ce) {
            log.error("連接配接逾時:{}", ce);
        } catch (Exception e) {
            log.error("https請求異常:{}", e);
        }
        return jsonObject;
    }


    public static String getSha1(String str) {
        if (str == null || str.length() == 0) {
            return null;
        }
        char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
                'a', 'b', 'c', 'd', 'e', 'f'};
        try {
            MessageDigest mdTemp = MessageDigest.getInstance("SHA1");
            mdTemp.update(str.getBytes("UTF-8"));

            byte[] md = mdTemp.digest();
            int j = md.length;
            char buf[] = new char[j * 2];
            int k = 0;
            for (int i = 0; i < j; i++) {
                byte byte0 = md[i];
                buf[k++] = hexDigits[byte0 >>> 4 & 0xf];
                buf[k++] = hexDigits[byte0 & 0xf];
            }
            return new String(buf);
        } catch (Exception e) {
            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;
    }
}
           

3)通過微信掃碼回調的code擷取AccessToken對象封裝

import lombok.Data;

/**
 * @Description:  通過code擷取access_token</p>
 * @Date: 2021-03-10
 * @Author: Jonathan.WQ
 * @Version V1.0
 * @Modified By:
 */
@Data
public class AccessToken {

    /**
     * 接口調用憑證
     */
    private String access_token;

    /**
     * access_token接口調用憑證逾時時間,機關(秒)
     */
    private Integer expires_in;

    /**
     * 使用者重新整理access_token
     */
    private String refresh_token;

    /**
     * 授權使用者唯一辨別
     */
    private String openid;

    /**
     * 使用者授權的作用域,使用逗号(,)分隔
     */
    private String scope;

    /**
     * 當且僅當該網站應用已獲得該使用者的userinfo授權時,才會出現該字段。
     */
    private String unionid;

}
           

4)微信使用者對象封裝

/**
 * @Description:  微信使用者對象
 * @Date: 2021-03-10
 * @Author: Jonathan.WQ
 * @Version V1.0
 * @Modified By:
 */
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("member_wechat")
@JsonInclude(JsonInclude.Include.NON_EMPTY)
public class MemberWechat extends BaseEntity {

    private static final long serialVersionUID = 1L;

    @TableField("open_id")
    private String openId;//微信的openid

    @TableField("mini_open_id")
    private String miniOpenId;//小程式的openId

    @TableField("union_id")
    private String unionId;//使用者在微信的唯一辨別

    @TableField("member_id")
    private String memberId;//會員ID

    @TableField("groupid")
    private Integer groupid;//使用者所在的分組ID(相容舊的使用者分組接口)

    @TableField("province")
    private String province;//使用者所在省份

    @TableField("headimgurl")
    private String headimgurl;//使用者頭像

    @TableField("nickname")
    private String nickname;//使用者的昵稱

    @TableField("language")
    private String language;//使用者的語言,簡體中文為zh_CN

	@TableField("sex")
	private Integer sex;//性别

    @TableField("subscribe_time")
    private Date subscribeTime;//使用者關注時間

    @TableField("subscribe")
    private Integer subscribe;//使用者是否訂閱該公衆号辨別,值為0時,代表此使用者沒有關注該公衆号,拉取不到其餘資訊

    @TableField("country")
    private String country;//使用者所在國家

    @TableField("city")
    private String city;//使用者所在城市

    @TableField("create_user")
    private String createUser;//

    @TableField("create_time")
    private Date createTime;//

    @TableField("update_user")
    private String updateUser;//

    @TableField("update_time")
    private Date updateTime;//

    @TableField("data_status")
    private Integer dataStatus;//

    @TableField("version")
    private Integer version;//

    @TableField(exist = false)
    private Integer errcode;

    @TableField(exist = false)
    private String errmsg;

    public MemberWechat() {
    }
}

           

5)擷取微信掃碼的二維碼:

@ApiOperation("擷取微信二維碼")
    @ResponseBody
    @RequestMapping("/api-uaa/oauth/wechat/wxLogin")
    public CommonResult toLogin(HttpServletRequest request, @RequestParam(value = "redirectUrl", required = false) String redirectUrl) {
        if (StringUtils.isEmpty(redirectUrl)) {//redirectUrl為掃碼成功之後需要跳轉的頁面位址
            return new CommonResult().validateFailed("redirectUrl參數不能為空");
        }
        //緩存redirectURL位址
        redisTemplate.set("PROJECT:MEMBERWECHAT:REDIRECTURL", redirectUrl);
        String url = weChatService.getWeChatLoginUrl();
        return new CommonResult().success(url);
    }
           

備注:CommonResult類很簡單,就提供三個屬性:data(資料)、msg(消息)、code(狀态碼),關于狀态碼大家可以根據自身項目需要與前端溝通好預設好就行(例如:20000成功,20001失敗,20004無權限,20003認證失敗)。

6)WeChatService

特别注意:報40163的錯誤就是在這裡生成連結的時候

/**
 * @Description: (用一句話描述該檔案的作用)
 * @Date: 2020-11-23
 * @Author: WQ
 * @Version V1.0
 * @Modified By:
 */
@Service
public class WeChatServiceImpl implements WeChatService {

    @Value(("${wx.qrCode}"))
    private String url;
    @Value("${wx.appId}")
    private String appId;
    @Value("${wx.redirect_uri}")
    private String redirectUri;
    @Value("${wx.openVisitUrl}")
    private String openVisitUrl;
    @Value("${wx.webAccessTokenHttpsOAuth}")
    private String webAccessTokenHttpsOAuth;
    @Value("${wx.secret}")
    private String appSecret;
    @Autowired
    private RedisTemplate redisTemplate;

    @Override
    public String getWeChatLoginUrl() {
        String content = CommonConstant.PWD_MD5 + DateUtils.format(Calendar.getInstance().getTime(), "yyyyMMdd");
        byte[] encrypt = AesUtil.encrypt(content, AesUtil.PASSWORD_SECRET_KEY, 16);
        String parseByte2HexStr = AesUtil.parseByte2HexStr(encrypt);
        String wxLoginUrl = url;
        wxLoginUrl = wxLoginUrl.replaceAll("APPID", appId);

        try {
            wxLoginUrl = wxLoginUrl.replaceAll("REDIRECT_URI", URLEncoder.encode(
                    openVisitUrl + redirectUri, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        wxLoginUrl = wxLoginUrl.replaceAll("SCOPE", "snsapi_login");
        wxLoginUrl = wxLoginUrl.replace("STATE", parseByte2HexStr);    //加密state進行驗證 回調位址當天有效 防止惡意攻擊
        return wxLoginUrl;
    }

	/**
	*40163錯誤就出現在這裡,code不能重複使用。如果直接讀取配置文   件的連結并替換相關占位符參數,使用原來的webAccessTokenHttpsOAuth接收,會導緻code不能及時被替換成新獲得的code,用生成的連結請求微信擷取AccessToken時就會報40163的錯誤
錯誤代碼:
 @Override
    public AccessToken getAccessToken(String code) {
        webAccessTokenHttpsOAuth = webAccessTokenHttpsOAuth.replaceAll("APPID", appId);
        webAccessTokenHttpsOAuth = webAccessTokenHttpsOAuth.replaceAll("SECRET", appSecret);
        webAccessTokenHttpsOAuth = webAccessTokenUrl.replaceAll("CODE", code);
        String responseContent = HttpUtils.sendHttpGet(webAccessTokenHttpsOAuth);
        if (responseContent == null || responseContent == "") {
            return null;
        }
        JSONObject parseObject = JSONObject.parseObject(responseContent);
        AccessToken accessToken = JSONObject.toJavaObject(parseObject, AccessToken.class);
        return accessToken;
    }
	*/
    @Override
    public AccessToken getAccessToken(String code) {
        String webAccessTokenUrl = webAccessTokenHttpsOAuth;
        webAccessTokenUrl = webAccessTokenUrl.replaceAll("APPID", appId);
        webAccessTokenUrl = webAccessTokenUrl.replaceAll("SECRET", appSecret);
        webAccessTokenUrl = webAccessTokenUrl.replaceAll("CODE", code);
        String responseContent = HttpUtils.sendHttpGet(webAccessTokenUrl);
        if (responseContent == null || responseContent == "") {
            return null;
        }
        JSONObject parseObject = JSONObject.parseObject(responseContent);
        AccessToken accessToken = JSONObject.toJavaObject(parseObject, AccessToken.class);
        return accessToken;
    }
}

           

7)掃碼授權成功之後的回調方法

/**
     * 回調位址處理(上面方法的備份)
     *
     * @param code 授權回調碼
     * @param state 狀态參數(防止跨站僞造攻擊)
     * @return
     */
    @GetMapping( "/api-uaa/oauth/wechat/callback")
    public ModelAndView callback(String code, String state) {
        String redirectUrl = String.valueOf(redisRepository.get("PROJECT:MEMBERWECHAT:REDIRECTURL"));
        ModelAndView modelAndView = new ModelAndView();
        try {
            if (code != null && state != null) {
                // 驗證state為了用于防止跨站請求僞造攻擊
                String decrypt = AesUtil.decrypt(AesUtil.parseHexStr2Byte(state), AesUtil.PASSWORD_SECRET_KEY, 16);
                if (!decrypt.equals(CommonConstant.PWD_MD5 + DateUtils.format(Calendar.getInstance().getTime(), "yyyyMMdd"))) {
                    //校驗失敗跳轉
                    modelAndView.setViewName("redirect:" + redirectUrl);
                    return modelAndView;
                }
                AccessToken access = weChatService.getAccessToken(code);
                if (access != null ) {
                    // 把擷取到的OPENID和ACCESS_TOKEN寫到redis中,用于校驗使用者授權的微信使用者是否存在于我們的系統中,用完即删除
                    redisRepository.set(SecurityMemberConstants.WEIXIN_TOKEN_CACHE_KEY + ":" + "ACCESS_TOKEN", access.getAccess_token());
                    redisTemplate.setExpire(SecurityMemberConstants.WEIXIN_TOKEN_CACHE_KEY + ":" + "OPEN_ID", access.getOpenid(), 60 * 60);//一個小時過期
                    // 拿到openid擷取微信使用者的基本資訊
                    MemberWechat memberWechat = umsCenterFeignService.selectByOpenId(access.getOpenid());
                    boolean isExists = memberWechat == null ? false : true;

                    if (!isExists) {//不存在
                        // 跳轉綁定頁面
                        modelAndView.setViewName("redirect:" + openVisitUrl + "/bind");
                    } else {
                        //校驗是否已經綁定過了系統使用者(之前綁定過,但是解綁了)
                        if (memberWechat.getMemberId() == null) {
                            modelAndView.setViewName("redirect:" + openVisitUrl + "/bind");
                        } else {
                            // 存在則跳轉前端傳遞的redirectURL,并攜帶OPENID和state參數
                            String content = CommonConstant.PWD_MD5 + DateUtils.format(Calendar.getInstance().getTime(), "yyyyMMdd");
                            byte[] encrypt = AesUtil.encrypt(content, AesUtil.PASSWORD_SECRET_KEY, 16);
                            String parseByte2HexStr = AesUtil.parseByte2HexStr(encrypt);

                            if (redirectUrl.contains("?")) {
                                modelAndView.setViewName("redirect:" + openVisitUrl + redirectUrl + "&openId=" + access.getOpenid() + "&state=" + parseByte2HexStr);
                            } else {
                                modelAndView.setViewName("redirect:" + openVisitUrl + redirectUrl + "?openId=" + access.getOpenid() + "&state=" + parseByte2HexStr);
                            }
                        }
                    }
                    return modelAndView;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            redisRepository.del("PROJECT:MEMBERWECHAT:REDIRECTURL");
        }
        modelAndView.setViewName("redirect:" + openVisitUrl + redirectUrl);//登入失敗跳轉
        return modelAndView;
    }
           

備注:

MemberWechat memberWechat =umsCenterFeignService.selectByOpenId(access.getOpenid());這個就是拿着openId去自己搭建的系統看是否存在該使用者,不存在則添加,根據自身項目需要編寫相關邏輯(因為需要跨服務調用,是以才緩存AccessToken和OpenId)

8)如果已綁定系統賬号,需要通過獲得的openId和state請求背景接口獲得token令牌

@ApiOperation(value = "openId擷取token")
    @PostMapping("/api-uaa/oauth/openId/ums/token")
    public void getTokenByOpenId(@ApiParam(required = true, name = "openId", value = "openId") String
                                         openId, @ApiParam(required = true, name = "state", value = "state") String
                                         state, HttpServletRequest request, HttpServletResponse response) throws IOException {
        String decrypt = AesUtil.decrypt(AesUtil.parseHexStr2Byte(state), AesUtil.PASSWORD_SECRET_KEY, 16);
        if (!decrypt.equals(CommonConstant.PWD_MD5 + DateUtils.format(Calendar.getInstance().getTime(), "yyyyMMdd"))) {
            exceptionHandler(response, "非法登入");
        }
        MemberWechat member = umsCenterFeignService.selectByOpenId(openId);
        if (member != null) {
            MemberInfo memberInfo = umsCenterFeignService.selectById(member.getMemberId());
            OpenIdMemberAuthenticationToken token = new OpenIdMemberAuthenticationToken(openId);
            writeToken(request, response, token, "openId錯誤", member.getMemberId());
        } else {
            exceptionHandler(response, "openId錯誤");
        }
    }
           

備注:具體的通過Feign跨服務調用的方法就不細寫了,這個相對來說比較簡單。最後附上writeToken()方法:

private void writeToken(HttpServletRequest request, HttpServletResponse response,
                            AbstractAuthenticationToken token, String badCredenbtialsMsg, String memberId) throws IOException {
        try {
        	//Nginx預設是過濾掉以_開頭的參數的,su
            String clientId = request.getHeader("client-id");
            String clientSecret = request.getHeader("client-secret");
            if (StringUtils.isBlank(clientId)) {
                throw new UnapprovedClientAuthenticationException("請求頭中無client-id資訊");
            }

            if (StringUtils.isBlank(clientSecret)) {
                throw new UnapprovedClientAuthenticationException("請求頭中無client-secret資訊");
            }
            Map<String, String> requestParameters = new HashedMap();
            requestParameters.put("memberId", memberId);
            ClientDetails clientDetails = getClient(clientId, clientSecret, null);
            TokenRequest tokenRequest = new TokenRequest(requestParameters, clientId, clientDetails.getScope(),
                    "customer");
            OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
            Authentication authentication = authenticationManager.authenticate(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
            OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
            OAuth2AccessToken oAuth2AccessToken = authorizationServerTokenServices
                    .createAccessToken(oAuth2Authentication);
            oAuth2Authentication.setAuthenticated(true);
            writerObj(response, oAuth2AccessToken);
        } catch (BadCredentialsException | InternalAuthenticationServiceException e) {
            exceptionHandler(response, badCredenbtialsMsg);
            e.printStackTrace();
        } catch (Exception e) {
            exceptionHandler(response, e);
        }
    }
           

繼續閱讀