QQ第三方授權登錄OAuth2.0實現(Java)


准備材料

創建應用

  • 1.訪問 https://connect.qq.com/manage.html ,登錄。
  • 2.創建網站應用,填寫網站基本信息以及平台信息,提交審核。注:網站回調域后續會用到,是點擊授權登錄時回調地址,需要與后續開發一致。

程序開發

1. 添加QQ登錄按鈕,用於點擊跳轉至QQ授權登錄頁

<a href="/account/qqConnect" class="blog-user"> <i
				class="fa fa-qq"></i>
			</a>

2. Java后台實現頁面跳轉

2.1 編寫一個工具類

QQUtil

package cn.zwqh.springboot.common.qq;
import java.io.IOException;
import java.net.URLEncoder;

import org.apache.http.client.ClientProtocolException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;

import cn.zwqh.springboot.common.http.HttpClientUtils;
import cn.zwqh.springboot.entity.sys.User;


public class QQUtil {
	private static final Logger log = LoggerFactory.getLogger(QQUtil.class);

	private static final String QQ_APP_ID="XXX";//改成自己的
	private static final String QQ_APP_SECRET="XXX";//改成自己的
	private static final String LOGIN_REDIRECT_URI="https://www.zwqh.top/account/qqLogin";	//改成自己的
	private static final String BIND_REDIRECT_URI="https://www.zwqh.top/account/qqBind";									  //改成自己的
	private static final String AUTH_CODE_URL="https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id="+QQ_APP_ID+"&redirect_uri=REDIRECT_URI&state=STATE";
    private static final String ACCESS_TOKEN_URL="https://graph.qq.com/oauth2.0/token?client_id="+QQ_APP_ID+"&client_secret="+QQ_APP_SECRET+"&code=CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI";
    private static final String REFRESH_TOKEN_URL="https://graph.qq.com/oauth2.0/token?client_id="+QQ_APP_ID+"&client_secret="+QQ_APP_SECRET+"&grant_type=refresh_token&refresh_token=REFRESH_TOKEN";
    private static final String OPEN_ID_URL="https://graph.qq.com/oauth2.0/me?access_token=ACCESS_TOKEN";
    private static final String USER_INFO_URL="https://graph.qq.com/user/get_user_info?access_token=ACCESS_TOKEN&oauth_consumer_key="+QQ_APP_ID+"&openid=OPENID";
    
    public static JSONObject getJsonStrByQueryUrl(String paramStr){
        String[] params = paramStr.split("&");
        JSONObject obj = new JSONObject();
        for (int i = 0; i < params.length; i++) {
            String[] param = params[i].split("=");
            if (param.length >= 2) {
                String key = param[0];
                String value = param[1];
                for (int j = 2; j < param.length; j++) {
                    value += "=" + param[j];
                }
                try {
                    obj.put(key,value);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        }
        return obj;
    }
    /**
     * 獲取授權登錄頁碼url
     * @return
     */
    public static String getLoginConnectUrl(String state) {
    	String url=null;
    	try{
    		url=AUTH_CODE_URL.replace("REDIRECT_URI", URLEncoder.encode(LOGIN_REDIRECT_URI, "utf-8")).replace("STATE", state);
    	}catch (Exception e) {
			log.error(e.toString());
		}
		return url;
	}
    
    /**
     * 獲取授權綁定頁碼url
     * @return
     */
    public static String getBindConnectUrl() {
    	String url=null;
    	try{
    		url=AUTH_CODE_URL.replace("REDIRECT_URI", URLEncoder.encode(BIND_REDIRECT_URI, "utf-8"));
    	}catch (Exception e) {
			log.error(e.toString());
		}
		return url;
	}
      
    /**
     * 獲取AccessToken
     * @return 返回拿到的access_token及有效期
     */
    public static QQAccessToken getQQLoginAccessToken(String code) throws ClientProtocolException, IOException{
    	QQAccessToken token = new QQAccessToken();
        String url = ACCESS_TOKEN_URL.replace("CODE", code).replace("REDIRECT_URI", URLEncoder.encode(LOGIN_REDIRECT_URI, "utf-8"));
        log.info("這是請求路徑:"+url);
        String result = HttpClientUtils.doGet(url);
        JSONObject jsonObject=getJsonStrByQueryUrl(result);
        log.info("這是返回結果:"+jsonObject);
        if(jsonObject!=null){ //如果返回不為空,將返回結果封裝進AccessToken實體類
            token.setAccessToken(jsonObject.getString("access_token"));//接口調用憑證
            token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口調用憑證超時時間,單位(秒)
            token.setRefreshToken(jsonObject.getString("refresh_token"));
        }
        return token;
    }
    
    /**
     * 獲取AccessToken
     * @return 返回拿到的access_token及有效期
     */
    public static QQAccessToken getQQBindAccessToken(String code) throws ClientProtocolException, IOException{
    	QQAccessToken token = new QQAccessToken();
        String url = ACCESS_TOKEN_URL.replace("CODE", code).replace("REDIRECT_URI", URLEncoder.encode(BIND_REDIRECT_URI, "utf-8"));
        log.info("這是請求路徑:"+url);
        String result = HttpClientUtils.doGet(url);
        JSONObject jsonObject=getJsonStrByQueryUrl(result);
        log.info("這是返回結果:"+jsonObject);
        if(jsonObject!=null){ //如果返回不為空,將返回結果封裝進AccessToken實體類
            token.setAccessToken(jsonObject.getString("access_token"));//接口調用憑證
            token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口調用憑證超時時間,單位(秒)
            token.setRefreshToken(jsonObject.getString("refresh_token"));
        }
        return token;
    }
    
    /**
     * 刷新或續期access_token使用
     * @return 返回拿到的access_token及有效期
     */
    public static QQAccessToken refreshQQAccessToken(String refreshToken) throws ClientProtocolException, IOException{
    	QQAccessToken token = new QQAccessToken();
        String url = REFRESH_TOKEN_URL.replace("REFRESH_TOKEN",refreshToken);
        log.info("這是請求路徑:"+url);
        String result = HttpClientUtils.doGet(url);
        log.info("這是返回結果:"+result);
        JSONObject jsonObject=getJsonStrByQueryUrl(result);
        log.info("這是轉為json的結果:"+jsonObject);
        if(jsonObject!=null){ //如果返回不為空,將返回結果封裝進AccessToken實體類
            token.setAccessToken(jsonObject.getString("access_token"));//接口調用憑證
            token.setExpiresIn(jsonObject.getInteger("expires_in"));//access_token接口調用憑證超時時間,單位(秒)
            token.setRefreshToken(jsonObject.getString("refresh_token"));
        }
        return token;
    }
    /**
     * 獲取QQopenId
     * @return QQopenId
     */
    public static String getQQOpenId(String accessToken) throws ClientProtocolException, IOException{
        String url = OPEN_ID_URL.replace("ACCESS_TOKEN",accessToken);
        log.info("這是請求路徑:"+url);
        String result = HttpClientUtils.doGet(url).replace("callback(", "").replace(");", "");
        log.info("這是返回結果:"+result);
        JSONObject jsonObject=JSON.parseObject(result);
        log.info("這是轉為json的結果:"+jsonObject);
        if(jsonObject!=null&&jsonObject.getString("openid")!=null){ //如果返回不為空
            return jsonObject.getString("openid");
        }
        return null;
    }  
    /**
     * 獲取QQ用戶信息
     * @param accessToken
     * @param openId
     * @return
     * @throws IOException 
     * @throws ClientProtocolException 
     */
    public static JSONObject getUserInfo(String accessToken, String openId) throws ClientProtocolException, IOException {
        // 拼接請求地址
        String url = USER_INFO_URL.replace("ACCESS_TOKEN", accessToken).replace("OPENID", openId);
        log.info("這是請求路徑:"+url);
        String result = HttpClientUtils.doGet(url);
        log.info("這是返回結果:"+result);
        JSONObject jsonObject=JSONObject.parseObject(result);
        log.info("這是轉為json的結果:"+jsonObject);
        JSONObject json=new JSONObject();
        if (jsonObject!=null&&jsonObject.getInteger("ret").equals(0)) {
            try {
            	User user= new User();
                // 用戶的標識
                user.setQqId(openId);
                // 昵稱
                user.setNickname(jsonObject.getString("nickname"));
                if(jsonObject.getString("figureurl_2")!=null&&!jsonObject.getString("figureurl_2").isEmpty()) {
                	  // 用戶頭像
                    user.setAvatar(jsonObject.getString("figureurl_qq_2"));
                }else {
                	  // 用戶頭像
                    user.setAvatar(jsonObject.getString("figureurl_qq_1"));
                }
                json.put("success", true);
                json.put("msg", "success");
                json.put("user", user);
            } catch (Exception e) {
                int errorCode = jsonObject.getInteger("ret");
                String errorMsg = jsonObject.getString("msg");
                log.error("獲取用戶信息失敗 errcode:{} errmsg:{}", errorCode, e.toString());
                json.put("success", false);
                json.put("msg", errorMsg);
                json.put("user", null);
            }
        }else {
        	  json.put("success", false);
              json.put("msg", "請先登錄");
              json.put("user", null);
        }
        return json;
    }
}

QQAccessToken

package cn.zwqh.springboot.common.qq;

import java.io.Serializable;

public class QQAccessToken implements Serializable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 5258435811207021018L;
	private String accessToken;//接口調用憑證
	private int expiresIn;//access_token接口調用憑證超時時間,單位(秒)
	private String openid;//授權用戶唯一標識
	private String refreshToken;//用戶刷新access_token
	private String scope;//用戶授權的作用域,使用逗號(,)分隔

	public String getOpenid() {
		return openid;
	}

	public void setOpenid(String openid) {
		this.openid = openid;
	}

	public String getAccessToken() {
		return accessToken;
	}

	public void setAccessToken(String accessToken) {
		this.accessToken = accessToken;
	}

	public int getExpiresIn() {
		return expiresIn;
	}

	public void setExpiresIn(int expiresIn) {
		this.expiresIn = expiresIn;
	}

	public String getRefreshToken() {
		return refreshToken;
	}

	public void setRefreshToken(String refreshToken) {
		this.refreshToken = refreshToken;
	}

	public String getScope() {
		return scope;
	}

	public void setScope(String scope) {
		this.scope = scope;
	}

}

2.2 Controller層實現
package cn.zwqh.springboot.action.web;

import java.io.IOException;
import java.util.Date;
import java.util.UUID;

import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.alibaba.fastjson.JSONObject;

import cn.zwqh.springboot.action.BaseAction;
import cn.zwqh.springboot.common.CookieUtil;
import cn.zwqh.springboot.common.DateUtil;
import cn.zwqh.springboot.common.EscapeUnescape;
import cn.zwqh.springboot.common.qq.QQAccessToken;
import cn.zwqh.springboot.common.qq.QQUtil;
import cn.zwqh.springboot.common.redis.RedisHandle;
import cn.zwqh.springboot.entity.SessionUser;
import cn.zwqh.springboot.entity.sys.User;
import cn.zwqh.springboot.service.UserService;

@Controller
@RequestMapping("/account")
public class AccountAction extends BaseAction {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1729415442021645693L;

	@Resource
	private RedisHandle redisHandle;

	@Autowired
	private UserService userService;

	/**
	 * 跳轉至QQ登錄界面
	 */
	@RequestMapping("/qqConnect")
	@ResponseBody
	public void qqConnect() {
		try {
			String referer = getRequest().getHeader("REFERER");
			String state = DateUtil.formatUserDefineDate(new Date(), "yyyyMMddHHmmssSSS");
			redisHandle.set(state, referer, 60 * 30L);
			getResponse().sendRedirect(QQUtil.getLoginConnectUrl(state));
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * QQ第三方登錄
	 * 
	 * @throws Exception
	 */
	@RequestMapping("/qqLogin")
	@ResponseBody
	public void qqLogin() throws Exception {
		String code = getRequest().getParameter("code");
		String state = getRequest().getParameter("state");
		System.out.println("code = " + code + ", state = " + state);
		if (code != null && !"".equals(code)) {
			QQAccessToken qqAccessToken = QQUtil.getQQLoginAccessToken(code);
			if (qqAccessToken.getAccessToken().equals("")) {
				// 我們的網站被CSRF攻擊了或者用戶取消了授權
				// 做一些數據統計工作
				System.out.print("沒有獲取到響應參數");
				// 跳轉返回地址
				outJsonFailure("未獲取到AccessToken,請重新進行QQ授權登錄");
			} else {
				QQAccessToken qqAccessToken2 = QQUtil.refreshQQAccessToken(qqAccessToken.getRefreshToken());
				String accessToken = qqAccessToken2.getAccessToken();
				String referer = redisHandle.get(state).toString();
				redisHandle.set(accessToken, referer, 60 * 30L);
				redisHandle.remove(state);
				getResponse().sendRedirect("https://www.zwqh.top/account/getQQUserInfo?qqAccessToken=" + accessToken);

			}
		} else {
			outJsonFailure("缺少code參數");
		}
	}

	/**
	 * 獲取QQ用戶信息
	 * 
	 * @param qqAccessToken
	 * @throws Exception
	 */
	@GetMapping("/getQQUserInfo")
	public String getQQUserInfo(String qqAccessToken) throws Exception {
		System.out.println("accessToken = " + qqAccessToken);
		String referer = redisHandle.get(qqAccessToken).toString();
		if (qqAccessToken != null && !"".equals(qqAccessToken)) {
			try {
				String qqOpenId = QQUtil.getQQOpenId(qqAccessToken);
				if (qqOpenId != null) {
					System.out.println("**************qq登錄成功 qqOpenId = " + qqOpenId);
					// 獲取QQ用戶信息
					JSONObject object = QQUtil.getUserInfo(qqAccessToken, qqOpenId);
					// 數據庫中判斷qqOpenId是否存在,存在則登錄,不存在則注冊
					User user = userService.getUserByQQOpenId(qqOpenId);
					if (user != null) {
						user.setAvatar(object.getJSONObject("user").getString("avatar"));
						user.setNickname(object.getJSONObject("user").getString("nickname"));
						user.setLastLoginTime(DateUtil.formatDateTime(new Date()));
						userService.updateUser(user);
						SessionUser suser = SessionUser.getInstance(user);
						String token = UUID.randomUUID().toString();
						redisHandle.set(token, suser, 60 * 60L * 24 * 7);// 設置用戶緩存及過期時間(一星期)
						JSONObject data = new JSONObject();
						data.put("userId", user.getId());
						data.put("nickname", user.getNickname());
						data.put("avatar", user.getAvatar());
						data.put("token", token);
						CookieUtil.setValue(getResponse(), "loginUser", data.toString());
					} else {
						user = new User();
						user.setAvatar(object.getJSONObject("user").getString("avatar"));
						user.setNickname(object.getJSONObject("user").getString("nickname"));
						user.setLastLoginTime(DateUtil.formatDateTime(new Date()));
						user.setRegisterTime(DateUtil.formatDateTime(new Date()));
						user.setQqId(qqOpenId);
						userService.insertUser(user);
						SessionUser suser = SessionUser.getInstance(user);
						String token = UUID.randomUUID().toString();
						redisHandle.set(token, suser, 60 * 60L * 24 * 7);// 設置用戶緩存及過期時間(一星期)
						JSONObject data = new JSONObject();
						data.put("userId", user.getId());
						data.put("nickname", user.getNickname());
						data.put("avatar", user.getAvatar());
						data.put("token", token);
						CookieUtil.setValue(getResponse(), "loginUser", data.toString());
					}

				} else {
					putInRequest("error", "未獲取到用戶openid,請重新QQ授權登錄");
				}
			} catch (Exception e) {
				e.printStackTrace();
				putInRequest("error", "登錄異常");
			}
		} else {
			putInRequest("error", "缺少code參數");
		}
		return "redirect:" + referer;
	}
	/**
	 * 退出登錄
	 * @return
	 */
	@RequestMapping("/logout")
	public String logout() {
		String referer = getRequest().getHeader("REFERER");
		String data= CookieUtil.getCookieValue(getRequest(), "loginUser");
		if(data!=null&&data!="") {
			JSONObject user=JSONObject.parseObject(EscapeUnescape.unescape(data));
			String token=user.getString("token");
			redisHandle.remove(token);
			CookieUtil.deleteValue("loginUser",getResponse());
		}
		return "redirect:" + referer;
	}
}

2.3 JavaScript 處理頁面
var data=eval('('+unescape(getCookie("loginUser"))+')');
	var a = document.getElementsByClassName("blog-user")[0];
	if(data!=null){		
		a.setAttribute("href","/account/logout");
		a.innerHTML='<img alt="'+data.nickname+'" title="'+data.nickname+'" src="'+data.avatar+'" class="layui-circle" width="40px" height="40px">';
	}else{
		a.setAttribute("href","/account/qqConnect");
		a.innerHTML='<i class="fa fa-qq"></i>';
	}

總結

總的來說QQ授權登錄還是很簡單的,該方法使用web端以及wap端。

非特殊說明,本文版權歸 朝霧輕寒 所有,轉載請注明出處.

本文標題: QQ第三方授權登錄OAuth2.0實現(Java)

本文網址: https://www.zwqh.top/article/info/7

如果文章對您有幫助,請掃碼關注下我的公眾號,文章持續更新中...


免責聲明!

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



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