1. 程式人生 > >解釋微信作為第三方授權登入

解釋微信作為第三方授權登入

微信開放平臺的官網https://open.weixin.qq.com/

這裡是文件,

這個圖看著來,

1. 使用者點選微信登入,這時請求我們自己的應用(一個藉口,該介面給頁面返回一個微信端的登入地址)。

例如:https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect

其中的appid是在開放平臺註冊應用後獲取的secret也是,redirect_uri 是告訴微信,用後授權後,微信需要對我們進行回撥的介面(把臨時授權code,返回這個介面)這個地址也要與註冊的一致。state:我看視屏的時候,這個傳你登入的頁面,也就是登入成功後想到到的頁面(官方解釋:用於保持請求和回撥的狀態,授權請求後原樣帶回給第三方。該引數可用於防止csrf攻擊(跨站請求偽造攻擊),建議第三方帶上該引數,可設定為簡單的隨機數加session進行校驗)

2. 根據第一步獲取的登入地址,頁面請求載入微信的登入頁面,然後進行確認。

3. 確認後微信會對我們自己的應用進行一個回撥,並且給我們一個臨時的授權code。

4.根據code+appid+appsecret 請求微信能獲取使用者資訊的access_token。

5.微信返回access_token,我們進行解析,處理儲存等。

感覺寫的有點水,權當記錄了,輕點噴……

package com.lpw.dbvideo.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

/**
 * 微信配置類
 */
@Configuration
@PropertySource(value = "classpath:application.properties")
public class WeChatConfig {
    /**
     * 微信公眾號AppId
     */
    @Value("${wx.oa.appid}")
    private String wxOAAppId;
    /**
     * 微信公眾號AppSecret
     */
    @Value("${wx.oa.appsecret}")
    private String wxOAAppSecret;

    /**
     * 微信開放平臺 openappid
     */
    @Value("${wx.open.appid}")
    private String wxOpenAppId;

    /**
     * 微信開放平臺 openappsecret
     */
    @Value("${wx.open.appsecret}")
    private String wxOpenSecret;

    /**
     * 微信開放平臺重定向地址
     */
    @Value("${wx.open.redirecturl}")
    private String wxOpenRedirectUrl;

    /**
     * 微信開放平臺二維碼地址
     */
//    private static final String OPEN_QRCODE_URL = "https://open.weixin.qq.com/connect/qrconnect?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE#wechat_redirect";
    private static final String OPEN_QRCODE_URL = "127.0.0.1:8081/connect/qrconnect?appid=%s&redirect_uri=%s&response_type=code&scope=SCOPE&state=%s#wechat_redirect";

    /**
     * 開放平臺獲取access_token 地址
     */
    private static final String OPEN_ACCESS_TOKEN_URL = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code";

    /**
     * 獲取微信使用者資訊的介面
     */
    private static final String OPEN_USER_INFO_URL = "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s";

    public String getWxOAAppId() {
        return wxOAAppId;
    }

    public void setWxOAAppId(String wxOAAppId) {
        this.wxOAAppId = wxOAAppId;
    }

    public String getWxOAAppSecret() {
        return wxOAAppSecret;
    }

    public void setWxOAAppSecret(String wxOAAppSecret) {
        this.wxOAAppSecret = wxOAAppSecret;
    }

    public String getWxOpenAppId() {
        return wxOpenAppId;
    }

    public void setWxOpenAppId(String wxOpenAppId) {
        this.wxOpenAppId = wxOpenAppId;
    }

    public String getWxOpenSecret() {
        return wxOpenSecret;
    }

    public void setWxOpenSecret(String wxOpenSecret) {
        this.wxOpenSecret = wxOpenSecret;
    }

    public String getWxOpenRedirectUrl() {
        return wxOpenRedirectUrl;
    }

    public void setWxOpenRedirectUrl(String wxOpenRedirectUrl) {
        this.wxOpenRedirectUrl = wxOpenRedirectUrl;
    }

    public static String getOpenQrcodeUrl() {
        return OPEN_QRCODE_URL;
    }

    public static String getOpenAccessTokenUrl() {
        return OPEN_ACCESS_TOKEN_URL;
    }

    public static String getOpenUserInfoUrl() {
        return OPEN_USER_INFO_URL;
    }
}
package com.lpw.dbvideo.login.controller;

import com.lpw.dbvideo.config.WeChatConfig;
import com.lpw.dbvideo.user.service.UserService;
import com.lpw.dbvideo.utils.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletResponse;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;

/**
 * 第一步 使用者前端請求進入(本專案中的)介面,介面請求微信平臺,微信平臺給(本專案中的)介面返回一個授權地址,(本專案中的)介面再給前端返回,前端展示;
 * 第二步 使用者前端進行確認(即掃碼確認),接著微信平臺會重定向到(本專案中的)介面,並且帶上一個授權臨時票據code;
 * 第三步 根據code加上appid和appsecret 請求微信獲取token,微信返回token結束。
 */
@Controller
@RequestMapping("/api/wechat")
public class WeChatController {

    @Autowired
    private WeChatConfig weChatConfig;

    @Autowired
    private UserService userService;

    /**
     * 微信掃一掃登入,獲取微信二維碼的地址
     * @param state  用於保持請求和回撥的狀態,授權請求後原樣帶回給第三方。該引數可用於防止csrf攻擊(跨站請求偽造攻擊),建議第三方帶上該引數,可設定為簡單的隨機數加session進行校驗
     * @return
     * @throws UnsupportedEncodingException
     */
    @RequestMapping("login")
    @ResponseBody
    @CrossOrigin
    public JsonData loginUrl(@RequestParam(value = "state")String state) throws UnsupportedEncodingException {

        String redirecrUrl = weChatConfig.getWxOpenRedirectUrl(); // 獲取開放平臺重定向地址
        String callBackUrl = URLEncoder.encode(redirecrUrl,"UTF-8"); // 進行編碼
        String qrcodeUrl = String.format(WeChatConfig.getOpenQrcodeUrl(),weChatConfig.getWxOpenAppId(),callBackUrl,state);
        return JsonData.success(qrcodeUrl);
    }

    /**
     *  微信客戶端的回撥介面
     * @param code 使用者確認後,微信端重定向會這個介面,帶上授權臨時票據(code)
     * @param state 上面我們給微信端傳的state,微信端會繼續返回給我們
     * @param response HttpServletResponse
     */
    @GetMapping("/user/callback")
    public void wechatUserCallBack(String code, String state, HttpServletResponse response){
        userService.saveWeChatUser(code);
    }

}
package com.lpw.dbvideo.utils;

import com.google.gson.Gson;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

import java.util.HashMap;
import java.util.Map;

/**
 * 封裝http get,post
 */
public class HttpUtils {

    public static final Gson gson = new Gson();

    /**
     * get方法
     * @param url
     * @return
     */
    public static Map<String, Object> doGet(String url){

        Map<String, Object> map = new HashMap<String, Object>();
        CloseableHttpClient httpClient = HttpClients.createDefault();

        RequestConfig requestConfig =RequestConfig.custom().setConnectTimeout(5000) // 連線超時
                .setConnectionRequestTimeout(5000) // 請求超時
                .setSocketTimeout(5000) // 連線socket超時
                .setRedirectsEnabled(true) // 允許重定向
                .build();

        HttpGet httpGet = new HttpGet(url);
        httpGet.setConfig(requestConfig);
        try{
            HttpResponse httpResponse = httpClient.execute(httpGet);
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                String jsonResult = EntityUtils.toString(httpResponse.getEntity());
                map = gson.fromJson(jsonResult, map.getClass());
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();            }
        }
        return map;
    }

    /**
     * 封裝 post
     * @param url 請求路徑
     * @param data 請求資料
     * @param timeout 過期時間
     * @return
     */
    public static  String doPost(String url, String data,int timeout ){

        CloseableHttpClient httpClient = HttpClients.createDefault();
        // 設定超時
        RequestConfig requestConfig =RequestConfig.custom().setConnectTimeout(timeout) // 連線超時
                .setConnectionRequestTimeout(timeout) // 請求超時
                .setSocketTimeout(timeout) // 連線socket超時
                .setRedirectsEnabled(true) // 允許重定向
                .build();

        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(requestConfig);
        httpPost.addHeader("Content-type", "text/html;charset=UTF-8");
        if(data != null && data instanceof String){
            StringEntity stringEntity = new StringEntity(data, "UTF-8");
            httpPost.setEntity(stringEntity);
        }
        try{
            CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
            HttpEntity httpEntity = httpResponse.getEntity();
            if(httpResponse.getStatusLine().getStatusCode() == 200){
                String jsonResult = EntityUtils.toString(httpEntity);
                return jsonResult;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                httpClient.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
        return null;
    }
}
<!-- httpclient 相關-->
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpclient</artifactId>
			<version>4.5.3</version>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpmime</artifactId>
			<version>4.5.2</version>
		</dependency>
		<dependency>
			<groupId>commons-codec</groupId>
			<artifactId>commons-codec</artifactId>
		</dependency>
		<dependency>
			<groupId>commons-logging</groupId>
			<artifactId>commons-logging</artifactId>
			<version>1.1.1</version>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents</groupId>
			<artifactId>httpcore</artifactId>
		</dependency>

由於測試的時候沒有開放平臺的認證賬號,所以只好自己寫一個服務模擬測試(有出入諒解,畢竟不知道微信怎麼做的)。附上碼雲地址