准備材料
- 1.已經備案好的域名
- 2.服務器(域名和服務器為統一主體或域名已接入服務器)
- 3.QQ號
- 4.開發流程:https://wiki.connect.qq.com/准備工作_oauth2-0
創建應用
- 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
如果文章對您有幫助,請掃碼關注下我的公眾號,文章持續更新中...