1. 程式人生 > 實用技巧 >第三方登入之微信

第三方登入之微信

一丶說明

由於沒有企業資質 ,下面的微信登入是站在尚矽谷提供appid,appsecret,和回撥地址(回撥地址他們做了處理,回撥到了本地的localhost)之上的。

其實和正常的差不多 ,就一個回撥地址不同,如果是正常的話,微信的回撥地址好像不能用本地的,這裡可以用花生殼等內網穿透工具做測試。

二丶步驟

首先 拿到你的appip appsecret 設定好你的回撥地址後

1.請求固定的地址,拉起授權頁面。

        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s
" + "&redirect_uri=%s" + "&response_type=code" + "&scope=snsapi_login" + "&state=%s" + "#wechat_redirect";

%s =佔位符 將appid和回撥地址填上 state 這個引數 你給他帶過去什麼值 他就會給你返回什麼值 可以隨便寫

2.使用者掃碼點選登入後 微信 會調到你的回撥地址 傳給你2個引數。一個code 一個state

code 相當於零時票據, state 就是第一步帶過去的 state

再次請求

            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";

將appid sercret code 填好

通過這個url 會得到一個accessToken 和一個openId

openId 為使用者的唯一標識 accessToken為臨時令牌

3.然後用accessToken 和openid 訪問微信的資源伺服器 得到使用者資訊

            //訪問微信的資源伺服器,獲取使用者資訊
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
            "?access_token=%s" +
            "&openid=%s";

4.SUCCESS

三丶具體程式碼

1.依賴

     <!--commons-io-->
            <dependency>
                <groupId>commons-io</groupId>
                <artifactId>commons-io</artifactId>
                <version>2.6</version>
            </dependency>
            <!--httpclient-->
            <dependency>
                <groupId>org.apache.httpcomponents</groupId>
                <artifactId>httpclient</artifactId>
                <version>4.5.1</version>
            </dependency>
            <dependency>
                <groupId>com.google.code.gson</groupId>
                <artifactId>gson</artifactId>
                <version>2.8.2</version>
            </dependency>

2.application.properties 這裡的appid appsecret redirect_url 是寫在這裡的當然也可以存其他地方

#服務埠
server.port=8150
#服務名
spring.application.name=service-ucenter
#環境設定:dev、test、prod
spring.profiles.active=dev
# mysql資料庫連線
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/guli?serverTimezone=GMT%2B8&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=root
#redis
spring.redis.host=192.168.117.129
spring.redis.port=6379
spring.redis.database= 0
spring.redis.timeout=1800000
spring.redis.lettuce.pool.max-active=20
spring.redis.lettuce.pool.max-wait=-1
#最大阻塞等待時間(負數表示沒限制)
spring.redis.lettuce.pool.max-idle=5
spring.redis.lettuce.pool.min-idle=0

# 微信開放平臺 appid
wx.open.app_id=xxxx
# 微信開放平臺 appsecret
wx.open.app_secret=xxx
# 微信開放平臺 重定向url
wx.open.redirect_url=http://xxx/api/ucenter/wx/callback

# 返回json的全域性時間格式
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#配置mapper xml檔案的路徑
mybatis-plus.mapper-locations=classpath:com/atguigu/cmsservice/mapper/xml/*.xml

#mybatis日誌
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
# nacos服務地址
spring.cloud.nacos.discovery.server-addr=127.0.0.1:8848

3.utils

讀取配置資訊的類

package com.atguigu.educenter.utils;

import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * 讀取配置檔案中的內容  當專案已啟動,spring載入之後,執行介面一個方法
 */
@Component
public class ConstantWxUtils implements InitializingBean {
//當spring把值載入後 afterPropertiesSet方法就會執行
    //讀取配置檔案中的內容

    @Value("${wx.open.app_id}")
    private String appId;

    @Value("${wx.open.app_secret}")
    private String appSecret;

    @Value("${wx.open.redirect_url}")
    private String redirectUrl;

    public static String WX_OPEN_APP_ID;
    public static String WX_OPEN_APP_SECRET;
    public static String WX_OPEN_REDIRECT_URL;
    @Override
    public void afterPropertiesSet() throws Exception {

        WX_OPEN_APP_ID = appId;
        WX_OPEN_APP_SECRET = appSecret;
        WX_OPEN_REDIRECT_URL = redirectUrl;
    }


}

httpclient

package com.atguigu.educenter.utils;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.config.RequestConfig.Builder;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ConnectTimeoutException;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLContextBuilder;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.conn.ssl.X509HostnameVerifier;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.net.SocketTimeoutException;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 *  依賴的jar包有:commons-lang-2.6.jar、httpclient-4.3.2.jar、httpcore-4.3.1.jar、commons-io-2.4.jar
 * @author zhaoyb
 *
 */
public class HttpClientUtils {

    public static final int connTimeout=10000;
    public static final int readTimeout=10000;
    public static final String charset="UTF-8";
    private static HttpClient client = null;

    static {
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        cm.setMaxTotal(128);
        cm.setDefaultMaxPerRoute(128);
        client = HttpClients.custom().setConnectionManager(cm).build();
    }

    public static String postParameters(String url, String parameterStr) throws ConnectTimeoutException, SocketTimeoutException, Exception{
        return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, String parameterStr,String charset, Integer connTimeout, Integer readTimeout) throws ConnectTimeoutException, SocketTimeoutException, Exception{
        return post(url,parameterStr,"application/x-www-form-urlencoded",charset,connTimeout,readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params) throws ConnectTimeoutException,
            SocketTimeoutException, Exception {
        return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String postParameters(String url, Map<String, String> params, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
            SocketTimeoutException, Exception {
        return postForm(url, params, null, connTimeout, readTimeout);
    }

    public static String get(String url) throws Exception {
        return get(url, charset, null, null);
    }

    public static String get(String url, String charset) throws Exception {
        return get(url, charset, connTimeout, readTimeout);
    }

    /**
     * 傳送一個 Post 請求, 使用指定的字符集編碼.
     *
     * @param url
     * @param body RequestBody
     * @param mimeType 例如 application/xml "application/x-www-form-urlencoded" a=1&b=2&c=3
     * @param charset 編碼
     * @param connTimeout 建立連結超時時間,毫秒.
     * @param readTimeout 響應超時時間,毫秒.
     * @return ResponseBody, 使用指定的字符集編碼.
     * @throws ConnectTimeoutException 建立連結超時異常
     * @throws SocketTimeoutException  響應超時
     * @throws Exception
     */
    public static String post(String url, String body, String mimeType,String charset, Integer connTimeout, Integer readTimeout)
            throws ConnectTimeoutException, SocketTimeoutException, Exception {
        HttpClient client = null;
        HttpPost post = new HttpPost(url);
        String result = "";
        try {
            if (StringUtils.isNotBlank(body)) {
                HttpEntity entity = new StringEntity(body, ContentType.create(mimeType, charset));
                post.setEntity(entity);
            }
            // 設定引數
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            post.setConfig(customReqConf.build());

            HttpResponse res;
            if (url.startsWith("https")) {
                // 執行 Https 請求.
                client = createSSLInsecureClient();
                res = client.execute(post);
            } else {
                // 執行 Http 請求.
                client = HttpClientUtils.client;
                res = client.execute(post);
            }
            result = IOUtils.toString(res.getEntity().getContent(), charset);
        } finally {
            post.releaseConnection();
            if (url.startsWith("https") && client != null&& client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
        return result;
    }


    /**
     * 提交form表單
     *
     * @param url
     * @param params
     * @param connTimeout
     * @param readTimeout
     * @return
     * @throws ConnectTimeoutException
     * @throws SocketTimeoutException
     * @throws Exception
     */
    public static String postForm(String url, Map<String, String> params, Map<String, String> headers, Integer connTimeout,Integer readTimeout) throws ConnectTimeoutException,
            SocketTimeoutException, Exception {

        HttpClient client = null;
        HttpPost post = new HttpPost(url);
        try {
            if (params != null && !params.isEmpty()) {
                List<NameValuePair> formParams = new ArrayList<NameValuePair>();
                Set<Entry<String, String>> entrySet = params.entrySet();
                for (Entry<String, String> entry : entrySet) {
                    formParams.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
                }
                UrlEncodedFormEntity entity = new UrlEncodedFormEntity(formParams, Consts.UTF_8);
                post.setEntity(entity);
            }

            if (headers != null && !headers.isEmpty()) {
                for (Entry<String, String> entry : headers.entrySet()) {
                    post.addHeader(entry.getKey(), entry.getValue());
                }
            }
            // 設定引數
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            post.setConfig(customReqConf.build());
            HttpResponse res = null;
            if (url.startsWith("https")) {
                // 執行 Https 請求.
                client = createSSLInsecureClient();
                res = client.execute(post);
            } else {
                // 執行 Http 請求.
                client = HttpClientUtils.client;
                res = client.execute(post);
            }
            return IOUtils.toString(res.getEntity().getContent(), "UTF-8");
        } finally {
            post.releaseConnection();
            if (url.startsWith("https") && client != null
                    && client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
    }




    /**
     * 傳送一個 GET 請求
     *
     * @param url
     * @param charset
     * @param connTimeout  建立連結超時時間,毫秒.
     * @param readTimeout  響應超時時間,毫秒.
     * @return
     * @throws ConnectTimeoutException   建立連結超時
     * @throws SocketTimeoutException   響應超時
     * @throws Exception
     */
    public static String get(String url, String charset, Integer connTimeout,Integer readTimeout)
            throws ConnectTimeoutException,SocketTimeoutException, Exception {

        HttpClient client = null;
        HttpGet get = new HttpGet(url);
        String result = "";
        try {
            // 設定引數
            Builder customReqConf = RequestConfig.custom();
            if (connTimeout != null) {
                customReqConf.setConnectTimeout(connTimeout);
            }
            if (readTimeout != null) {
                customReqConf.setSocketTimeout(readTimeout);
            }
            get.setConfig(customReqConf.build());

            HttpResponse res = null;

            if (url.startsWith("https")) {
                // 執行 Https 請求.
                client = createSSLInsecureClient();
                res = client.execute(get);
            } else {
                // 執行 Http 請求.
                client = HttpClientUtils.client;
                res = client.execute(get);
            }

            result = IOUtils.toString(res.getEntity().getContent(), charset);
        } finally {
            get.releaseConnection();
            if (url.startsWith("https") && client != null && client instanceof CloseableHttpClient) {
                ((CloseableHttpClient) client).close();
            }
        }
        return result;
    }


    /**
     * 從 response 裡獲取 charset
     *
     * @param ressponse
     * @return
     */
    @SuppressWarnings("unused")
    private static String getCharsetFromResponse(HttpResponse ressponse) {
        // Content-Type:text/html; charset=GBK
        if (ressponse.getEntity() != null  && ressponse.getEntity().getContentType() != null && ressponse.getEntity().getContentType().getValue() != null) {
            String contentType = ressponse.getEntity().getContentType().getValue();
            if (contentType.contains("charset=")) {
                return contentType.substring(contentType.indexOf("charset=") + 8);
            }
        }
        return null;
    }



    /**
     * 建立 SSL連線
     * @return
     * @throws GeneralSecurityException
     */
    private static CloseableHttpClient createSSLInsecureClient() throws GeneralSecurityException {
        try {
            SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
                public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
                    return true;
                }
            }).build();

            SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new X509HostnameVerifier() {

                @Override
                public boolean verify(String arg0, SSLSession arg1) {
                    return true;
                }

                @Override
                public void verify(String host, SSLSocket ssl)
                        throws IOException {
                }

                @Override
                public void verify(String host, X509Certificate cert)
                        throws SSLException {
                }

                @Override
                public void verify(String host, String[] cns,
                                   String[] subjectAlts) throws SSLException {
                }

            });

            return HttpClients.custom().setSSLSocketFactory(sslsf).build();

        } catch (GeneralSecurityException e) {
            throw e;
        }
    }

    public static void main(String[] args) {
        try {
            String str= post("https://localhost:443/ssl/test.shtml","name=12&page=34","application/x-www-form-urlencoded", "UTF-8", 10000, 10000);
            //String str= get("https://localhost:443/ssl/test.shtml?name=12&page=34","GBK");
            /*Map<String,String> map = new HashMap<String,String>();
            map.put("name", "111");
            map.put("page", "222");
            String str= postForm("https://localhost:443/ssl/test.shtml",map,null, 10000, 10000);*/
            System.out.println(str);
        } catch (ConnectTimeoutException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (SocketTimeoutException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}

4.業務程式碼

拉起授權頁面的介面

 @GetMapping("login")
    public String getWxCode(){
        // 微信開放平臺授權baseUrl  %s相當於佔位符
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";
        //對redirect_url進行URLEncoder編碼
        String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try{
            redirectUrl= URLEncoder.encode(redirectUrl,"utf-8");
        }catch(Exception e){

        }

        String url= String.format(baseUrl, ConstantWxUtils.WX_OPEN_APP_ID,ConstantWxUtils.WX_OPEN_REDIRECT_URL,"atguigu");
        //重定向到請求微信地址裡面
        return "redirect:"+url;
 }

回撥介面

  @GetMapping("callback")
    public String callback(String code,String state) {
        try {
            //1 獲取code ,臨時票據 ,類似於驗證碼


            //2 拿著code請求 微信固定的地址 ,得到兩個值 access_token 和open id
            //向認證伺服器傳送請求換取access_token
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";

            String accessTokenUrl= String.format(baseAccessTokenUrl,ConstantWxUtils.WX_OPEN_APP_ID,ConstantWxUtils.WX_OPEN_APP_SECRET,code);
            //請求拼好的地址
            //使用httpclient 傳送請求
            String accessTokenInfo=   HttpClientUtils.get(accessTokenUrl);
            //使用json 轉換工具gosn 取出資訊
            Gson gson=new Gson();
            //引數一 要轉換的引數    引數2 要轉換的型別
          HashMap mapAccessToken=    gson.fromJson(accessTokenInfo, HashMap.class);
          String openid= (String) mapAccessToken.get("openid");
          String access_token=  (String) mapAccessToken.get("access_token");

            //訪問微信的資源伺服器,獲取使用者資訊
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
            "?access_token=%s" +
            "&openid=%s";

            //拼接兩個引數
            String  userInfoUrl=String.format(baseUserInfoUrl,access_token,openid);
            String userInfo=HttpClientUtils.get(userInfoUrl);
            System.out.println("=========掃碼人資訊=======");
            System.out.println(userInfo);
            System.out.println("=========掃碼人資訊=======");

        }catch (Exception e){

        }



        System.out.println(code);
        return null;
    }

整個類

package com.atguigu.educenter.controller;


import com.atguigu.educenter.utils.ConstantWxUtils;
import com.atguigu.educenter.utils.HttpClientUtils;
import com.google.gson.Gson;
import com.netflix.ribbon.proxy.annotation.Http;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.net.URLEncoder;
import java.util.HashMap;

@CrossOrigin
@Controller//注意這裡沒有配置 @RestController
@RequestMapping("/api/ucenter/wx")
public class WxApiController {


    @GetMapping("login")
    public String getWxCode(){
        // 微信開放平臺授權baseUrl  %s相當於佔位符
        String baseUrl = "https://open.weixin.qq.com/connect/qrconnect" +
                "?appid=%s" +
                "&redirect_uri=%s" +
                "&response_type=code" +
                "&scope=snsapi_login" +
                "&state=%s" +
                "#wechat_redirect";
        //對redirect_url進行URLEncoder編碼
        String redirectUrl = ConstantWxUtils.WX_OPEN_REDIRECT_URL;
        try{
            redirectUrl= URLEncoder.encode(redirectUrl,"utf-8");
        }catch(Exception e){

        }

        String url= String.format(baseUrl, ConstantWxUtils.WX_OPEN_APP_ID,ConstantWxUtils.WX_OPEN_REDIRECT_URL,"atguigu");
        //重定向到請求微信地址裡面
        return "redirect:"+url;
    }

    @GetMapping("callback")
    public String callback(String code,String state) {
        try {
            //1 獲取code ,臨時票據 ,類似於驗證碼


            //2 拿著code請求 微信固定的地址 ,得到兩個值 access_token 和open id
            //向認證伺服器傳送請求換取access_token
            String baseAccessTokenUrl = "https://api.weixin.qq.com/sns/oauth2/access_token" +
                    "?appid=%s" +
                    "&secret=%s" +
                    "&code=%s" +
                    "&grant_type=authorization_code";

            String accessTokenUrl= String.format(baseAccessTokenUrl,ConstantWxUtils.WX_OPEN_APP_ID,ConstantWxUtils.WX_OPEN_APP_SECRET,code);
            //請求拼好的地址
            //使用httpclient 傳送請求
            String accessTokenInfo=   HttpClientUtils.get(accessTokenUrl);
            //使用json 轉換工具gosn 取出資訊
            Gson gson=new Gson();
            //引數一 要轉換的引數    引數2 要轉換的型別
          HashMap mapAccessToken=    gson.fromJson(accessTokenInfo, HashMap.class);
          String openid= (String) mapAccessToken.get("openid");
          String access_token=  (String) mapAccessToken.get("access_token");

            //訪問微信的資源伺服器,獲取使用者資訊
            String baseUserInfoUrl = "https://api.weixin.qq.com/sns/userinfo" +
            "?access_token=%s" +
            "&openid=%s";

            //拼接兩個引數
            String  userInfoUrl=String.format(baseUserInfoUrl,access_token,openid);
            String userInfo=HttpClientUtils.get(userInfoUrl);
            System.out.println("=========掃碼人資訊=======");
            System.out.println(userInfo);
            System.out.println("=========掃碼人資訊=======");

        }catch (Exception e){

        }



        System.out.println(code);
        return null;
    }

}