1 准備工作_OAuth2.0
本步驟的作用:
接入QQ登錄前,網站需首先進行申請,獲得對應的appid與appkey,以保證后續流程中可正確對網站與用戶進行驗證與授權。
本步驟在整個流程中的位置:
1.1 申請appid和appkey
appid:應用的唯一標識。在OAuth2.0認證過程中,appid的值即為oauth_consumer_key的值。
appkey:appid對應的密鑰,訪問用戶資源時用來驗證應用的合法性。在OAuth2.0認證過程中,appkey的值即為oauth_consumer_secret的值。
1.2 申請地址
https://connect.qq.com/manage.html#/
1.3 申請流程
1. 開發者資質審核
參考文章:開發者注冊流程
2. 申請appid(oauth_consumer_key/client_id)和appkey(auth_consumer_secret/client_secret)。
(1)進入https://connect.qq.com/manage.html#/頁面,點擊“創建應用”,在彈出的對話框中填寫網站或應用的詳細資料(名稱,域名,回調地址)。
(2)點擊“確定”按鈕,提交資料后,獲取appid和appkey。
注意:申請appid時,登錄的QQ號碼將與申請到的appid綁定,后續維護均需要使用該號碼。
注意:對appid和appkey信息進行保密,不要隨意泄漏。
1.4 保證連接暢通
接入QQ登錄時,網站需要不停的和Qzone進行交互,發送請求和接受響應。
1. 對於PC網站:
請在你的服務器上ping graph.qq.com ,保證連接暢通。
2. 移動應用無需此步驟
2 放置“QQ登錄”按鈕_OAuth2.0
本步驟的作用:
在網站頁面上放置“QQ登錄”按鈕,並為按鈕添加前台代碼,實現點擊按鈕即彈出QQ登錄對話框 。
本步驟在整個流程中的位置:
2.1 下載“QQ登錄”按鈕圖片,並將按鈕放置在頁面合適的位置
按鈕圖片下載: 點擊這里下載 。
可以到阿里矢量圖庫下載更多圖標:阿里巴巴矢量圖標庫 。
按照UI規范,將按鈕放置在頁面合適的位置:點擊這里查看 。
2.2 為“QQ登錄”按鈕添加前台代碼
2.2.1 效果演示
2.2.2 前端代碼(Vue)
為了實現上述效果,應該為“QQ登錄”按鈕圖片添加如下前台代碼:
<div style="line-height: 22px;margin:0 0 8px 0;color: #9b9b9b;">
<span style="vertical-align:middle">第三方登錄:</span>
<img :src="qqIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="QQ" @click="handleQqLogin">
<a href=""><img :src="weixinIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="微信"></a>
<img :src="weiboIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="微博" @click="handleWeiBoLogin">
<a href=""><img :src="qyweixinIcon" width="22" height="22" style="vertical-align:middle;margin-left: 2px" title="企業微信"></a>
</div>
// qq登錄
// https://graph.qq.com/oauth2.0/show?which=Login&display=pc&response_type=code&client_id=1042&redirect_uri=https%3A%2F%2Fwww.lovezmy.link%2FqqCallback&state=3c6bc7df93a3c
handleQqLogin() {
getQQCode().then(res => {
window.location.href = res;
})
},
2.2.2 后端代碼(Java)
qq登錄配置文件信息:
# QQ登錄配置
qq.appID = 116666602(替換成你的)
qq.appKEY = c04360a666666666666666c06(替換成你的)
qq.redirectURI = https://www.lovezmy.link/qqCallback(替換成你的)
qq.scope = get_user_info,add_topic,add_one_blog,add_album,upload_pic,list_album,add_share,check_page_fans,add_t,add_pic_t,del_t,get_repost_list,get_info,get_other_info,get_fanslist,get_idollist,add_idol,del_ido,get_tenpay_addr(\u8BF7\u4FEE\u6539\u6B64\u5904)
qq.baseURL = https://graph.qq.com/
qq.userInfoURL = https://graph.qq.com/user/get_user_info
qq.accessTokenURL = https://graph.qq.com/oauth2.0/token
qq.authorizeURL = https://graph.qq.com/oauth2.0/authorize
qq.openIDURL = https://graph.qq.com/oauth2.0/me
qq.addTopicURL = https://graph.qq.com/shuoshuo/add_topic
qq.addBlogURL = https://graph.qq.com/blog/add_one_blog
qq.addAlbumURL = https://graph.qq.com/photo/add_album
qq.uploadPicURL = https://graph.qq.com/photo/upload_pic
qq.listAlbumURL = https://graph.qq.com/photo/list_album
qq.addShareURL = https://graph.qq.com/share/add_share
qq.checkPageFansURL = https://graph.qq.com/user/check_page_fans
qq.addTURL = https://graph.qq.com/t/add_t
qq.addPicTURL = https://graph.qq.com/t/add_pic_t
qq.delTURL = https://graph.qq.com/t/del_t
qq.weiboUserInfoURL = https://graph.qq.com/user/get_info
qq.otherUserInfoURL = https://graph.qq.com/user/get_other_info
qq.fansListURL = https://graph.qq.com/relation/get_fanslist
qq.idolsListURL = https://graph.qq.com/relation/get_idollist
qq.addIdolURL = https://graph.qq.com/relation/add_idol
qq.delIdolURL = https://graph.qq.com/relation/del_idol
qq.tenpayAddrURL = https://graph.qq.com/cft_info/get_tenpay_addr
qq.repostListURL = https://graph.qq.com/t/get_repost_list
qq.version = 2.0.0.0
讀取配置文件信息常量類:
package com.modules.security.constants;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* QQ登陸常量配置類
*/
@Data
@Configuration
@PropertySource("classpath:thirdparty/config.properties") // 指定配置文件的路徑,屬性文件需放在根目錄的resources文件夾下面,才能被讀取出來
public class QQConstants {
@Value("${qq.appID}")
private String appID;
@Value("${qq.appKEY}")
private String appKEY;
@Value("${qq.redirectURI}")
private String redirectURI;
@Value("${qq.authorizeURL}")
private String authorizeURL;
@Value("${qq.accessTokenURL}")
private String accessTokenURL;
@Value("${qq.openIDURL}")
private String openIDURL;
@Value("${qq.userInfoURL}")
private String userInfoURL;
}
Conteoller(獲取QQ登錄的url)
/**
* 獲得跳轉到qq登錄頁的url,前台直接a連接訪問
*
* @return
* @throws Exception
*/
@LogAnnotation("獲得跳轉到qq登錄頁的url")
@ApiOperation("獲得跳轉到qq登錄頁的url")
@AnonymousAccess
@GetMapping("/getQQCode")
public ResponseEntity<Object> getCode() throws Exception {
// 授權地址 ,進行Encode轉碼
String authorizeURL = qqConstants.getAuthorizeURL();
// 回調地址 ,進行Encode轉碼
String redirectUri = qqConstants.getRedirectURI();
//用於第三方應用防止CSRF攻擊
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
// 保存到Redis
redisUtils.set(QQSTATE + "-" + uuid, uuid, expiration, TimeUnit.MINUTES);
// 拼接url
StringBuilder url = new StringBuilder();
url.append(authorizeURL);
url.append("?response_type=code");
url.append("&client_id=" + qqConstants.getAppID());
// 轉碼
url.append("&redirect_uri=" + URLEncodeUtil.getURLEncoderString(redirectUri));
url.append("&state=" + uuid);
return ResponseEntity.ok(url);
}
3 使用Authorization_Code獲取Access_Token
本步驟的作用:
通過用戶驗證登錄和授權,獲取Access Token,為下一步獲取用戶的OpenID做准備。
同時,Access Token是應用在調用OpenAPI訪問和修改用戶數據時必須傳入的參數。
移動端應用可以直接獲得AccessToken,請參考使用Implicit_Grant方式獲取Access_Token。
本步驟在整個流程中的位置:
3.1 簡介
即server-side模式,是OAuth2.0認證的一種模式,又稱Web Server Flow。
適用於需要從web server訪問的應用,例如Web網站。
對於應用而言,需要進行兩步:
1. 獲取Authorization Code。
2. 通過Authorization Code獲取Access Token。
3.2 獲取Authorization Code
請求地址:
PC網站:https://graph.qq.com/oauth2.0/authorize
請求方法:
GET
請求參數:
參數 | 是否必須 | 含義 |
---|---|---|
response_type | 必須 | 授權類型,此值固定為“code”。 |
client_id | 必須 | 申請QQ登錄成功后,分配給應用的appid。 |
redirect_uri | 必須 | 成功授權后的回調地址,必須是注冊appid時填寫的主域名下的地址,建議設置為網站首頁或網站的用戶中心。注意需要將url進行URLEncode。 |
state | 必須 | client端的狀態值。用於第三方應用防止CSRF攻擊,成功授權后回調時會原樣帶回。請務必嚴格按照流程檢查用戶與state參數狀態的綁定。 |
scope | 可選 | 請求用戶授權時向用戶顯示的可進行授權的列表。 可填寫的值是API文檔中列出的接口,如果要填寫多個接口名稱,請用逗號隔開。 例如:scope=get_user_info,list_album,upload_pic 不傳則默認請求對接口get_user_info進行授權。 建議控制授權項的數量,只傳入必要的接口名稱,因為授權項越多,用戶越可能拒絕進行任何授權。 |
display | 可選 | 僅PC網站接入時使用。用於展示的樣式。不傳則默認展示為PC下的樣式。如果傳入“mobile”,則展示為mobile端下的樣式。 |
返回說明:
1. 如果用戶成功登錄並授權,則會跳轉到指定的回調地址,並在redirect_uri地址后帶上Authorization Code和原始的state值。如:
https://www.lovezmy.link/qqCallback?code=1E83738E79B0CEBF13FC7C3B1224D9B3C&state=cca3c152c527229409cccd889ad2963fe
注意:此code會在10分鍾內過期。
2. 如果用戶在登錄授權過程中取消登錄流程,對於PC網站,登錄頁面直接關閉。
錯誤碼說明:
接口調用有錯誤時,會返回code和msg字段,以url參數對的形式返回,value部分會進行url編碼(UTF-8)。
PC網站接入時,錯誤碼詳細信息請參見:100000-100031:PC網站接入時的公共返回碼。
3.3 通過Authorization Code獲取Access Token
請求地址:
PC網站:https://graph.qq.com/oauth2.0/token
請求方法:
GET
請求參數:
參數 | 是否必須 | 含義 |
---|---|---|
grant_type | 必須 | 授權類型,在本步驟中,此值為“authorization_code”。 |
client_id | 必須 | 申請QQ登錄成功后,分配給網站的appid。 |
client_secret | 必須 | 申請QQ登錄成功后,分配給網站的appkey。 |
code | 必須 | 上一步返回的authorization code。 如果用戶成功登錄並授權,則會跳轉到指定的回調地址,並在URL中帶上Authorization Code。 例如,回調地址為www.qq.com/my.php,則跳轉到: http://www.qq.com/my.php?code=520DD95263C1CFEA087****** 注意此code會在10分鍾內過期。 |
redirect_uri | 必須 | 與上面一步中傳入的redirect_uri保持一致。 |
fmt | 可選 | 因歷史原因,默認是x-www-form-urlencoded格式,如果填寫json,則返回json格式 |
返回說明:
如果成功返回,即可在返回包中獲取到Access Token。 如(不指定fmt時):
access_token=FE04************CCE2&expires_in=7776000&refresh_token=88E4****************BE14
參數說明 | 描述 |
---|---|
access_token | 授權令牌,Access_Token。 |
expires_in | 該access token的有效期,單位為秒。 |
refresh_token | 在授權自動續期步驟中,獲取新的Access_Token時需要提供的參數。 注:refresh_token僅一次有效 |
錯誤碼說明:
接口調用有錯誤時,會返回code和msg字段,以url參數對的形式返回,value部分會進行url編碼(UTF-8)。
PC網站接入時,錯誤碼詳細信息請參見:100000-100031:PC網站接入時的公共返回碼。
接口代碼:
/**
* 獲得token信息(授權,每個用戶的都不一致) --> 獲得token信息該步驟返回的token期限為一個月
*
* @return
* @throws Exception
*/
public Map<String, Object> getToken(String code) throws Exception {
StringBuilder url = new StringBuilder();
url.append(qqConstants.getAccessTokenURL());
url.append("?grant_type=authorization_code");
url.append("&client_id=" + qqConstants.getAppID());
url.append("&client_secret=" + qqConstants.getAppKEY());
url.append("&code=" + code);
// 回調地址
String redirectUri = qqConstants.getRedirectURI();
// 轉碼
url.append("&redirect_uri=" + URLEncodeUtil.getURLEncoderString(redirectUri));
// 獲得token
String result = HttpClientUtils.get(url.toString(), "UTF-8");
// 把token保存
String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(result, "&");
String accessToken = StringUtils.substringAfterLast(items[0], "=");
Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
String refreshToken = StringUtils.substringAfterLast(items[2], "=");
//token信息
Map<String, Object> qqProperties = new HashMap<String, Object>();
qqProperties.put("accessToken", accessToken);
qqProperties.put("expiresIn", String.valueOf(expiresIn));
qqProperties.put("refreshToken", refreshToken);
return qqProperties;
}
3.4(可選)權限自動續期,獲取Access Token
Access_Token的有效期默認是3個月,過期后需要用戶重新授權才能獲得新的Access_Token。本步驟可以實現授權自動續期,避免要求用戶再次授權的操作,提升用戶體驗。
請求地址:
PC網站:https://graph.qq.com/oauth2.0/token
請求方法:
GET
請求參數:
參數 | 是否必須 | 含義 |
---|---|---|
grant_type | 必須 | 授權類型,在本步驟中,此值為“refresh_token”。 |
client_id | 必須 | 申請QQ登錄成功后,分配給網站的appid。 |
client_secret | 必須 | 申請QQ登錄成功后,分配給網站的appkey。 |
refresh_token | 必須 | 首次:使用在Step2中獲取到的最新的refresh_token。 后續:使用刷新后返回的最新refresh_token |
fmt | 可選 | 因歷史原因,默認是x-www-form-urlencoded格式,如果填寫json,則返回json格式 |
返回說明:
如果成功返回,即可在返回包中獲取到Access Token。 如(不指定fmt時):
access_token=FE04************************CCE2&expires_in=7776000&refresh_token=88E4************************BE14。
參數說明 | 描述 |
---|---|
access_token | 授權令牌,Access_Token。 |
expires_in | 該access token的有效期,單位為秒。 |
refresh_token | 在授權自動續期步驟中,獲取新的Access_Token時需要提供的參數。每次生成最新的refresh_token,且僅一次有效 |
錯誤碼說明:
接口調用有錯誤時,會返回code和msg字段,以url參數對的形式返回,value部分會進行url編碼(UTF-8)。
PC網站接入時,錯誤碼詳細信息請參見:100000-100031:PC網站接入時的公共返回碼。
接口代碼:
/**
* 刷新token 信息(token過期,重新授權)
*
* @return
* @throws Exception
*/
@GetMapping("/refreshToken")
public Map<String, Object> refreshToken(Map<String, Object> qqProperties) throws Exception {
// 獲取refreshToken
String refreshToken = (String) qqProperties.get("refreshToken");
StringBuilder url = new StringBuilder(qqConstants.getAccessTokenURL());
url.append("?grant_type=refresh_token");
url.append("&client_id=" + qqConstants.getAppID());
url.append("&client_secret=" + qqConstants.getAppKEY());
url.append("&refresh_token=" + refreshToken);
String result = HttpClientUtils.get(url.toString(), "UTF-8");
// 把新獲取的token存到map中
String[] items = StringUtils.splitByWholeSeparatorPreserveAllTokens(result, "&");
String accessToken = StringUtils.substringAfterLast(items[0], "=");
Long expiresIn = new Long(StringUtils.substringAfterLast(items[1], "="));
String newRefreshToken = StringUtils.substringAfterLast(items[2], "=");
//重置信息
qqProperties.put("accessToken", accessToken);
qqProperties.put("expiresIn", String.valueOf(expiresIn));
qqProperties.put("refreshToken", newRefreshToken);
return qqProperties;
}
4 獲取用戶OpenID_OAuth2.0
本步驟的作用:
通過輸入在上一步獲取的Access Token,得到對應用戶身份的OpenID。
OpenID是此網站上或應用中唯一對應用戶身份的標識,網站或應用可將此ID進行存儲,便於用戶下次登錄時辨識其身份,或將其與用戶在網站上或應用中的原有賬號進行綁定。
本步驟在整個流程中的位置:
請求地址:
PC網站:https://graph.qq.com/oauth2.0/me
請求方法:
GET
請求參數:
參數 | 是否必須 | 含義 |
---|---|---|
access_token | 必須 | 在Step1中獲取到的access token。 |
fmt | 可選 | 因歷史原因,默認是jsonpb格式,如果填寫json,則返回json格式 |
返回說明:
PC網站接入時,獲取到用戶OpenID,返回包如下(如果fmt參數未指定):
callback( {"client_id":"YOUR_APPID","openid":"YOUR_OPENID"} );
openid是此網站上唯一對應用戶身份的標識,網站可將此ID進行存儲便於用戶下次登錄時辨識其身份,或將其與用戶在網站上的原有賬號進行綁定。
錯誤碼說明:
接口調用有錯誤時,會返回code和msg字段,以url參數對的形式返回,value部分會進行url編碼(UTF-8)。
PC網站接入時,錯誤碼詳細信息請參見:100000-100031:PC網站接入時的公共返回碼。
接口代碼:
/**
* 獲取用戶openId(根據token)
*
* @return
* @throws Exception
*/
public String getOpenId(Map<String, Object> qqProperties) throws Exception {
// 獲取保存的用戶的token
String accessToken = (String) qqProperties.get("accessToken");
if (StringUtils.isEmpty(accessToken)) {
throw new BadRequestException("QQ登錄信息異常,請重試!!!");
}
StringBuilder url = new StringBuilder(qqConstants.getOpenIDURL());
url.append("?access_token=" + accessToken);
String result = HttpClientUtils.get(url.toString(), "UTF-8");
String openId = StringUtils.substringBetween(result, "\"openid\":\"", "\"}");
return openId;
}
5 OpenAPI調用說明_OAuth2.0
本步驟的作用:
獲取到Access Token和OpenID后,可通過調用OpenAPI來獲取或修改用戶個人信息。
本步驟在整個流程中的位置:
5.1 前提說明
1. 該appid已經開通了該OpenAPI的使用權限。
從API列表的接口列表中可以看到,有的接口是完全開放的,有的接口則需要提前提交申請,以獲取訪問權限。
2. 准備訪問的資源是用戶授權可訪問的。
網站調用該OpenAPI讀寫某個openid(用戶)的信息時,必須是該用戶已經對你的appid進行了該OpenAPI的授權(例如用戶已經設置了相冊不對外公開,則網站是無法讀取照片信息的)。
用戶可以進入手機QQ->設置->隱私->授權管理,進行訪問權限管理。
3. 已經成功獲取到Access Token,並且Access Token在有效期內。
5.2 調用OpenAPI接口
QQ登錄提供了用戶信息等OpenAPI(詳見API列表),網站需要將請求發送到某個具體的OpenAPI接口,以訪問或修改用戶數據。
調用所有OpenAPI時,除了各接口私有的參數外,所有OpenAPI都需要傳入基於OAuth2.0協議的通用參數:
參數 | 含義 |
---|---|
access_token | 可通過使用Authorization_Code獲取Access_Token 或來獲取。access_token有3個月有效期。 |
oauth_consumer_key | 申請QQ登錄成功后,分配給應用的appid |
openid | 用戶的ID,與QQ號碼一一對應。 可通過調用https://graph.qq.com/oauth2.0/me?access_token=YOUR_ACCESS_TOKEN 來獲取。 |
5.3 示例
1. 以get_user_info接口為例:(請將access_token,appid等參數值替換為你自己的)
2. 成功返回后,即可獲取到用戶數據:
package com.modules.security.entity;
import lombok.Data;
import java.io.Serializable;
/**
* QQ登錄用戶信息
* @author Liyh
* @date 2020/12/22
*/
@Data
public class QQUserInfo implements Serializable {
private String ret; // 返回碼
private String msg; // 如果ret<0,會有相應的錯誤信息提示,返回數據全部用UTF-8編碼。
private String nickname; // 用戶在QQ空間的昵稱。
private String figureurl; // 大小為30×30像素的QQ空間頭像URL。
private String figureurl_1; // 大小為50×50像素的QQ空間頭像URL。
private String figureurl_2; // 大小為100×100像素的QQ空間頭像URL。
private String figureurl_qq_1; // 大小為40×40像素的QQ頭像URL。
private String figureurl_qq_2; // 大小為100×100像素的QQ頭像URL。需要注意,不是所有的用戶都擁有QQ的100x100的頭像,但40x40像素則是一定會有。
private String gender; // 性別。 如果獲取不到則默認返回"男"
private Integer gendertype; // 性別 數字
private String is_yellow_vip; // 標識用戶是否為黃鑽用戶(0:不是;1:是)。
private String vip; // 標識用戶是否為黃鑽用戶(0:不是;1:是)
private String yellow_vip_level; // 黃鑽等級
private String level; // QQ等級
private String is_yellow_year_vip; // 標識是否為年費黃鑽用戶(0:不是; 1:是)
private String province; // 省
private String city; // 市
}
3. 接口代碼:
/**
* 根據token,openId獲取用戶信息
*/
public QQUserInfo getUserInfo(Map<String, Object> qqProperties) throws Exception {
// 取出token
String accessToken = (String) qqProperties.get("accessToken");
String openId = (String) qqProperties.get("openId");
if (StringUtils.isEmpty(accessToken) || StringUtils.isEmpty(openId)) {
throw new BadRequestException("QQ登錄信息異常,請重試!!!");
}
// 拼接url
StringBuilder url = new StringBuilder(qqConstants.getUserInfoURL());
url.append("?access_token=" + accessToken);
url.append("&oauth_consumer_key=" + qqConstants.getAppID());
url.append("&openid=" + openId);
// 獲取qq相關數據
String result = HttpClientUtils.get(url.toString(), "UTF-8");
Object json = JSON.parseObject(result, QQUserInfo.class);
QQUserInfo userInfo = (QQUserInfo) json;
return userInfo;
}
6 個人網站(YOUYOUSHOP)(用戶名:admin,密碼:adminliyh),QQ,微博等已經實現,需要的小伙伴可以測試
6.1 每個人做的項目需求不同,可能會出現不同的問題,文章可以參考,也可以留言你的問題,我會幫你解決,大家一起加油