解决网页微信扫码登录报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);
        }
    }
  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值