springboot整合QQ第三方登錄


QQ授權登錄

參考官方文檔和網上的博客

過程說明

按照官方文檔

https://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0

准備工作,申請appid和appkey

定義你的網站地址和回調地址,我這里為了方便直接用本地

放置"QQ登錄"按鈕,就是下文的html

獲取AccessToken

點擊登錄的按鈕之后就會進入后端的loginByQQ()方法。

以GET方式訪問 https://graph.qq.com/oauth2.0/authorize,在后面添加參數

也就是訪問這個url ,並在后面加上必要的參數,參數代表的意思文檔已經給出說明,下文的代碼里會有具體例子。

String url = String.format(LoginQQConstant.GET_AUTHORIZATION_CODE + "?response_type=%s&client_id=%s&redirect_uri=%s&state=%s", response_type, client_id, redirect_uri, state);

resp.sendRedirect(url);

這一步過后會出現用戶QQ登錄授權頁面,用戶授權后再進入我們定義的回調方法callbackHandler();

從回調方法中的request中取出authorization_code

訪問 https://graph.qq.com/oauth2.0/token

獲取到accessToken。

獲取用戶的OpenID

使用accessToken去獲取openid

請求 https://graph.qq.com/oauth2.0/me

OpenAPI調用

根據openid調用官方提供的api,來獲取用戶信息

https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID

代碼分析

先建立一個html文件,圖標就用百度的了,為了方便,就直接用a標簽訪問后端的登錄接口

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首頁</title>
</head>
<body>
    <div>
        點擊圖片登錄,隨便找了一張百度的圖片表示登錄按鈕了
    </div>
	<a href="/login/loginByQQ">
    	<img src="http://www.baidu.com/img/baidu_jgylogo3.gif"/>
	</a>
</body>
</html>

定義一個常量類,保存一下官方提供的網址,給其他地方調用

package com.qiankai.qqlogin.constant;

/**
 * 常量類
 *
 * @author kai_qian
 * @version v1.0
 * @since 2020/02/18 15:43
 */
public final class LoginQQConstant {
    private LoginQQConstant(){}

    /**
     * 登錄處理回調地址,可自定義,此時是為了演示方便,實際開發不應該放在常量類中,應放在配置文件
     */
    public static final String CALLBACK_URL ="http://127.0.0.1:8080/login/callbackHandler";

    /**
     * 自己的appID和appKey
     */
    public static final String APP_ID = "自己的APP-ID";
    public static final String APP_KEY = "自己的APP-KEY";

    /**
     * 獲取Authorization Code
     */
    public static final String GET_AUTHORIZATION_CODE = "https://graph.qq.com/oauth2.0/authorize";

    /**
     * 通過Authorization Code獲取Access Token
     */
    public static final String GET_ACCESS_TOKEN = "https://graph.qq.com/oauth2.0/token";

    /**
     * 獲取用戶openId
     */
    public static final String GET_OPEN_ID = "https://graph.qq.com/oauth2.0/me";

    /**
     * 獲取用戶信息
     */
    public static final String GET_USER_INFO = "https://graph.qq.com/user/get_user_info";
}

后端進行業務邏輯處理,基本步驟就是先根據官方文檔中提供的url,按要求添加參數進行訪問,然后獲得返回的數據。

package com.qiankai.qqlogin.controller;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.qiankai.qqlogin.constant.LoginQQConstant;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.client.RestTemplate;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Date;
import java.util.HashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * 登錄邏輯處理
 *
 * @author kai_qian
 * @version v1.0
 * @since 2020/02/18 13:55
 */
@Controller
@RequestMapping("/login")
public class LoginController {


    @Autowired
    private RestTemplate restTemplate;

    /**
     * 處理登錄事件,點擊前端的圖片會訪問到這個方法
     */
    @RequestMapping("/loginByQQ")
    public void loginByQQ(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        String response_type = "code";
        String client_id = LoginQQConstant.APP_ID;
        // 官方文檔強調此處要對url進行URLEncode,我試了一下,直接使用原始回調地址也能正常使用
        String redirect_uri = URLEncoder.encode(LoginQQConstant.CALLBACK_URL, "UTF-8");
//        String redirect_uri = LoginQQConstant.CALLBACK_URL;
        //client端的狀態值。用於第三方應用防止CSRF攻擊。
        String state = new Date().toString();
        req.getSession().setAttribute("state", state);

        String url = String.format(LoginQQConstant.GET_AUTHORIZATION_CODE +
                "?response_type=%s&client_id=%s&redirect_uri=%s&state=%s", response_type, client_id, redirect_uri, state);

        resp.sendRedirect(url);

        // 如果一切順利,就會進入callbackHandler方法
    }

    /**
     * 用戶授權后的回調方法
     */
    @ResponseBody
    @RequestMapping("/callbackHandler")
    public String callbackHandler(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
        // 1.獲取回調的authorization
        String authorization_code = req.getParameter("code");
        if (authorization_code == null || authorization_code.trim().isEmpty()) {
            throw new RuntimeException("未獲取到AuthorizationCode");
        }
        // 2.client端的狀態值。用於第三方應用防止CSRF攻擊。
        String state = req.getParameter("state");
        if (!state.equals(req.getParameter("state"))) {
            throw new RuntimeException("client端的狀態值不匹配!");
        }

        // 3.獲取accessToken
        String urlForAccessToken = getUrlForAccessToken(authorization_code);
        String access_token = getAccessToken(urlForAccessToken);

        // 4.根據accessToken獲取openId
        if (access_token == null || access_token.trim().isEmpty()) {
            throw new RuntimeException("未獲取到accessToken");
        }
        String openid = getOpenId(access_token);

        // 5.根據openid獲取用戶信息
        if (openid == null || openid.trim().isEmpty()) {
            throw new RuntimeException("未獲取到openId");
        }
        String userInfo = getUserInfo(openid,access_token);
        return "userInfo為:" + userInfo;

        // ... 獲取到用戶信息就可以進行自己的業務邏輯處理了
    }

    // 下面是輔助方法

    /**
     * 拼接用於獲取accessToken的鏈接
     */
    private String getUrlForAccessToken(String authorization_code) throws UnsupportedEncodingException {
        String grant_type = "authorization_code";
        String client_id = LoginQQConstant.APP_ID;
        String client_secret = LoginQQConstant.APP_KEY;
//        String redirect_uri = URLEncoder.encode(CALLBACK_URL, "UTF-8"); 此處進行URLEncode會導致無法獲取AccessToken
        String redirect_uri = LoginQQConstant.CALLBACK_URL;

        String url = String.format(LoginQQConstant.GET_ACCESS_TOKEN +
                        "?grant_type=%s&client_id=%s&client_secret=%s&code=%s&redirect_uri=%s",
                grant_type, client_id, client_secret, authorization_code, redirect_uri);

        return url;
    }

    /**
     * 獲取accessToken
     */
    private String getAccessToken(String urlForAccessToken) {
        //第一次用服務器發起模擬客戶端訪問,得到的是包含access_token的字符串,其格式如下
        //access_token=0FFD92ABD1DFD4F5&expires_in=7776000&refresh_token=04CE5D1F1E290B0974C5
        String firstCallbackInfo = restTemplate.getForObject(urlForAccessToken, String.class);
        String[] params = firstCallbackInfo.split("&");
        String access_token = null;
        for (String param : params) {
            String[] keyvalue = param.split("=");
            if (keyvalue[0].equals("access_token")) {
                access_token = keyvalue[1];
                break;
            }
        }
        return access_token;
    }

    /**
     * 根據accessToken獲取openid
     */
    private String getOpenId(String access_token) throws IOException {
        String url = String.format(LoginQQConstant.GET_OPEN_ID + "?access_token=%s", access_token);
        //第二次模擬客戶端發出請求后得到的是帶openid的返回數據,格式如下
        //callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
        String secondCallbackInfo = restTemplate.getForObject(url, String.class);

        //正則表達式處理
        String regex = "\\{.*\\}";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(secondCallbackInfo);
        if (!matcher.find()) {
            throw new RuntimeException("異常的回調值: " + secondCallbackInfo);
        }

        //調用jackson
        ObjectMapper objectMapper = new ObjectMapper();
        HashMap hashMap = objectMapper.readValue(matcher.group(0), HashMap.class);

        String openid = ((String) hashMap.get("openid"));
        //return "獲取到的openid為:" + openid;
        return openid;
    }

    /**
     * 根據openid獲取用戶信息
     */
    private String getUserInfo(String openid,String access_token) {
        String infoUrl = String.format(LoginQQConstant.GET_USER_INFO + "?access_token=%s&oauth_consumer_key=%s&openid=%ss", access_token, LoginQQConstant.APP_ID, openid);
        String userInfo = restTemplate.getForObject(infoUrl, String.class);
        return userInfo;
    }
}

參考

https://segmentfault.com/a/1190000020181967?utm_source=tag-newest

https://wiki.connect.qq.com/%E5%87%86%E5%A4%87%E5%B7%A5%E4%BD%9C_oauth2-0


免責聲明!

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



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