從零玩轉第三方登錄之QQ登錄
從零玩轉第三方登錄之QQ登錄
前言
在真正開始對接之前,我們先來聊一聊後台的方案設計。既然是對接第三方登錄,那就免不了如何將用戶資訊保存。首先需要明確一點的是,用戶在第三方登錄成功之後,
我們能拿到的僅僅是一個代表用戶唯一身份的ID(微博是真實uid,QQ是加密的openID)以及用來識別身份的accessToken,當然還有昵稱、頭像、性別等有限資料,
對接第三方登錄的關鍵就是如何確定用戶是合法登錄,如果確定這次登錄的和上次登錄的是同一個人並且不是假冒的。其實這個並不用我們特別操心,就以微博登錄為例,
用戶登錄成功之後會回調一個code給我們,然後我們再拿code去微博那換取 accessToken ,如果這個code是用戶亂填的,那這一關肯定過不了,所以,前面的擔心有點多餘,哈哈。
1. 認識Oauth2.0
現在很多網站都要不管是為了引流也好,為了用戶方便也好一般都有第三方帳號登陸的需求,今天以QQ登陸為例,來實現一個最簡單的第三方登陸。
目前主流的第三方登錄都是依賴的Oauth2.0實現的,最常見的就是在各種中小型網站或者App中的QQ登錄,微信登錄等等。所以我建議想要學習和實現第三方登錄同學去了解下這個協議。
必須要域名並且進行備案
比如我的域名: //yangbuyi.top/
因為騰訊有一個域名認證機制啥的。。。。。。
2.實名認證
QQ登錄我們對接的是QQ互聯,地址://connect.qq.com
,首先需要註冊成為開發者並實名認證,需要手持身份證照片,具體就不講了。
2.1、進行申請開發者身份
2.2 創建應用
進入應用管理頁面創建應用,根據實際需要是創建網站應用還是移動應用,我這裡是網站應用:
提交成功完步後等待客服審核即可
這是我網站的基本介面資訊
QQ登陸流程
請求參數
創建springboot工程
依賴
<!-- qq登陸集成 開始 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.11</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.8</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpmime</artifactId>
</dependency>
<!--json轉換工具-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.5</version>
</dependency>
<!--QQSDK-->
<dependency>
<groupId>net.gplatform</groupId>
<artifactId>Sdk4J</artifactId>
<version>2.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
<!-- qq登陸集成 結束 -->
<!-- 模板 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<!-- 其它配置 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
創建http請求工具
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
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.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* description: 楊不易網站 :www.yangbuyi.top
* ClassName: HttpsUtils
* create: 2020-06-24 17:30
*
* @author: yangbuyi
* @since: JDK1.8
**/
public class HttpsUtils {
private static PoolingHttpClientConnectionManager connMgr;
private static RequestConfig requestConfig;
private static final int MAX_TIMEOUT = 7000;
private static final Logger logger = LoggerFactory.getLogger(HttpsUtils.class);
static {
// 設置連接池
connMgr = new PoolingHttpClientConnectionManager();
// 設置連接池大小
connMgr.setMaxTotal(100);
connMgr.setDefaultMaxPerRoute(connMgr.getMaxTotal());
// Validate connections after 1 sec of inactivity
connMgr.setValidateAfterInactivity(1000);
RequestConfig.Builder configBuilder = RequestConfig.custom();
// 設置連接超時
configBuilder.setConnectTimeout(MAX_TIMEOUT);
// 設置讀取超時
configBuilder.setSocketTimeout(MAX_TIMEOUT);
// 設置從連接池獲取連接實例的超時
configBuilder.setConnectionRequestTimeout(MAX_TIMEOUT);
requestConfig = configBuilder.build();
}
/**
* 發送 GET 請求(HTTP),不帶輸入數據
*
* @param url
* @return
*/
public static String doGet(String url) {
return doGet(url, new HashMap<String, Object>());
}
/**
* 發送 GET 請求(HTTP),K-V形式
*
* @param url
* @param params
* @return
*/
public static String doGet(String url, Map<String, Object> params) {
String apiUrl = url;
StringBuffer param = new StringBuffer();
int i = 0;
for (String key : params.keySet()) {
if (i == 0)
param.append("?");
else
param.append("&");
param.append(key).append("=").append(params.get(key));
i++;
}
apiUrl += param;
String result = null;
HttpClient httpClient = null;
if (apiUrl.startsWith("https")) {
httpClient = HttpClients.custom().setSSLSocketFactory(createSSLConnSocketFactory())
.setConnectionManager(connMgr).setDefaultRequestConfig(requestConfig).build();
} else {
httpClient = HttpClients.createDefault();
}
try {
HttpGet httpGet = new HttpGet(apiUrl);
HttpResponse response = httpClient.execute(httpGet);
HttpEntity entity = response.getEntity();
if (entity != null) {
InputStream instream = entity.getContent();
result = new BufferedReader(new InputStreamReader(instream)).lines().collect(Collectors.joining(System.lineSeparator()));
}
} catch (IOException e) {
logger.error(e.getMessage());
}
return result;
}
/**
* 發送 POST 請求(HTTP),不帶輸入數據
*
* @param apiUrl
* @return
*/
public static String doPost(String apiUrl) {
return doPost(apiUrl, new HashMap<String, Object>());
}
/**
* 發送 POST 請求,K-V形式
*
* @param apiUrl API介面URL
* @param params 參數map
* @return
*/
public static String doPost(String apiUrl, Map<String, Object> params) {
CloseableHttpClient httpClient = null;
if (apiUrl.startsWith("https")) {
httpClient = HttpClients.custom().setSSLSocketFactory(createSSLConnSocketFactory())
.setConnectionManager(connMgr).setDefaultRequestConfig(requestConfig).build();
} else {
httpClient = HttpClients.createDefault();
}
String httpStr = null;
HttpPost httpPost = new HttpPost(apiUrl);
CloseableHttpResponse response = null;
try {
httpPost.setConfig(requestConfig);
List<NameValuePair> pairList = new ArrayList<>(params.size());
for (Map.Entry<String, Object> entry : params.entrySet()) {
NameValuePair pair = new BasicNameValuePair(entry.getKey(), entry.getValue().toString());
pairList.add(pair);
}
httpPost.setEntity(new UrlEncodedFormEntity(pairList, Charset.forName("UTF-8")));
response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
httpStr = EntityUtils.toString(entity, "UTF-8");
} catch (IOException e) {
logger.error(e.getMessage());
} finally {
if (response != null) {
try {
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
return httpStr;
}
/**
* 發送 POST 請求,JSON形式
*
* @param apiUrl
* @param json json對象
* @return
*/
public static String doPost(String apiUrl, Object json) {
CloseableHttpClient httpClient = null;
if (apiUrl.startsWith("https")) {
httpClient = HttpClients.custom().setSSLSocketFactory(createSSLConnSocketFactory())
.setConnectionManager(connMgr).setDefaultRequestConfig(requestConfig).build();
} else {
httpClient = HttpClients.createDefault();
}
String httpStr = null;
HttpPost httpPost = new HttpPost(apiUrl);
CloseableHttpResponse response = null;
try {
httpPost.setConfig(requestConfig);
StringEntity stringEntity = new StringEntity(json.toString(), "UTF-8");// 解決中文亂碼問題
stringEntity.setContentEncoding("UTF-8");
stringEntity.setContentType("application/json");
httpPost.setEntity(stringEntity);
response = httpClient.execute(httpPost);
HttpEntity entity = response.getEntity();
httpStr = EntityUtils.toString(entity, "UTF-8");
} catch (IOException e) {
logger.error(e.getMessage());
} finally {
if (response != null) {
try {
EntityUtils.consume(response.getEntity());
} catch (IOException e) {
logger.error(e.getMessage());
}
}
}
return httpStr;
}
/**
* 創建SSL安全連接
*
* @return
*/
private static SSLConnectionSocketFactory createSSLConnSocketFactory() {
SSLConnectionSocketFactory sslsf = null;
try {
SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy() {
@Override
public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException {
return true;
}
}).build();
sslsf = new SSLConnectionSocketFactory(sslContext, new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
} catch (GeneralSecurityException e) {
logger.error(e.getMessage());
}
return sslsf;
}
/*gitHub開始*/
/**
* 發送get請求,利用java程式碼發送請求
* @param url
* @return
* @throws Exception
*/
public static String doGetHub(String url) throws Exception{
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet(url);
// 發送了一個http請求
CloseableHttpResponse response = httpclient.execute(httpGet);
// 如果響應200成功,解析響應結果
if(response.getStatusLine().getStatusCode()==200){
// 獲取響應的內容
HttpEntity responseEntity = response.getEntity();
return EntityUtils.toString(responseEntity);
}
return null;
}
/**
* 將字元串轉換成map
* @param responseEntity
* @return
*/
public static Map<String,String> getMap(String responseEntity) {
Map<String, String> map = new HashMap<>();
// 以&來解析字元串
String[] result = responseEntity.split("\\&");
for (String str : result) {
// 以=來解析字元串
String[] split = str.split("=");
// 將字元串存入map中
if (split.length == 1) {
map.put(split[0], null);
} else {
map.put(split[0], split[1]);
}
}
return map;
}
/**
* 通過json獲得map
* @param responseEntity
* @return
*/
public static Map<String,String> getMapByJson(String responseEntity) {
Map<String, String> map = new HashMap<>();
// 阿里巴巴fastjson 將json轉換成map
JSONObject jsonObject = JSONObject.parseObject(responseEntity);
for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
String key = entry.getKey();
// 將obj轉換成string
String value = String.valueOf(entry.getValue()) ;
map.put(key, value);
}
return map;
}
/*gitHub結束*/
}
創建跨域配置類 以防萬一出現跨域問題
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* ClassName: CorsAutoConfig
*
* @author yangshuai
* @Date: 2021-04-13 14:54
* @Description: $
**/
@Configuration
public class CorsAutoConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource urlBasedCorsConfigurationSource = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.addAllowedHeader("*");
corsConfiguration.addAllowedMethod("*");
// 表示什麼域名跨域 *表示全部都跨域
corsConfiguration.addAllowedOrigin("*");
// 注入進去
urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration);
CorsFilter corsFilter = new CorsFilter(urlBasedCorsConfigurationSource);
return corsFilter;
}
}
創建Logincontroller
import com.google.gson.Gson;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import top.yangbuyi.QQ.OAuthProperties;
import top.yangbuyi.QQ.vo.QQDTO;
import top.yangbuyi.QQ.vo.QQOpenidDTO;
import top.yangbuyi.common.HttpsUtils;
import javax.management.RuntimeErrorException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.server.PathParam;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* @description: 楊不易網站:www.yangbuyi.top
* @program: qqlogindemo
* @ClassName: loginController
* @create: 2020-08-18 14:41
* @author: yangbuyi
* @since: JDK1.8
* @loginController: 第三方QQ登陸
**/
@Controller
@Slf4j
@RequestMapping("api")
public class loginController {
/**
* 認證參數
*/
@Autowired
private OAuthProperties oauth;
/**
* 調用QQ登陸介面
* 流程: 先調用介面獲取code,在根據code獲取access_token,在根據token獲取對應的用戶資訊
* @param response
*/
@GetMapping("/login/oauth")
public void loginQQ( HttpServletResponse response) {
// 重定向訪問QQ登錄伺服器
try {
response.sendRedirect(oauth.getQQ().getCode_callback_uri() + //獲取code碼地址
"?client_id=" + oauth.getQQ().getClient_id() //appid
+"&state=" + UUID.randomUUID() + //這個說是防攻擊的,就給個隨機uuid吧
"&redirect_uri=" + oauth.getQQ().getRedirect_uri() +//這個很重要,這個是回調地址,即就收騰訊返回的code碼
"&response_type=code");
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 在qq平台設置的回調地址
*
* 接收回調地址帶過來的code碼
*
* @param code
* @param request
* @return
*/
@GetMapping("/oauth2")
public String authorizeQQ(String code, HttpServletRequest request) {
HashMap<String, Object> params = new HashMap<>();
params.put("code", code);
params.put("grant_type", "authorization_code");
params.put("redirect_uri", oauth.getQQ().getRedirect_uri());
params.put("client_id", oauth.getQQ().getClient_id());
params.put("client_secret", oauth.getQQ().getClient_secret());
// 獲取騰訊access token
Map<String, String> reulsts = getAccess_token(params);
System.out.println("遍歷拿到的數據:");
for (Map.Entry<String, String> entry : reulsts.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println("遍歷完畢");
//到這裡access_token已經處理好了
//下一步獲取openid,只有拿到openid才能拿到用戶資訊
String openidContent = HttpsUtils.doGet(oauth.getQQ().getOpenid_callback_uri() + "?access_token=" + reulsts.get("access_token"));
// callback( {"client_id":"101887062","openid":"74DD1353321FD56375F34422D833848D"} );
System.out.println("openidContent: " + openidContent);
//接下來對openid進行處理
//截取需要的那部分json字元串
String openid = openidContent.substring(openidContent.indexOf("{"), openidContent.indexOf("}") + 1);
// json 轉 對象
Gson gson = new Gson();
//將返回的openid轉換成DTO
QQOpenidDTO qqOpenidDTO = gson.fromJson(openid, QQOpenidDTO.class);
// 封裝參數 請求用戶資訊數據
params.clear();
//設置access_token
params.put("access_token", reulsts.get("access_token"));
//設置openid
params.put("openid", qqOpenidDTO.getOpenid());
//設置appid
params.put("oauth_consumer_key", qqOpenidDTO.getClient_id());
//獲取用戶資訊
String userInfo = HttpsUtils.doGet(oauth.getQQ().getUser_info_callback_uri(), params);
QQDTO qqDTO = gson.fromJson(userInfo, QQDTO.class);
// (正常情況下,在開發時候用openid作為用戶名,再自己定義個密碼就可以了)
try {
/* 組裝數據 */
HashMap<String, Object> map = new HashMap<>();
map.put("user", qqDTO);
map.put("qqOpenidDTO", qqOpenidDTO);
request.setAttribute("map", map);
log.info("user數據:{}" + qqDTO);
log.info("qqOpenidDTO數據:{}" + qqOpenidDTO);
return "home";
} catch (Exception e) {
e.printStackTrace();
return "login";
}
}
/**
* 獲取騰訊 access_token
*
* @return
*/
public Map<String, String> getAccess_token(HashMap<String, Object> params) {
// 認證地址
//獲取access_token如:access_token=9724892714FDF1E3ED5A4C6D074AF9CB&expires_in=7776000&refresh_token=9E0DE422742ACCAB629A54B3BFEC61FF
String result = HttpsUtils.doGet(oauth.getQQ().getAccess_token_callback_uri(), params);
//對拿到的數據進行切割字元串
String[] strings = result.split("&");
//切割好後放進map
Map<String, String> reulsts = new HashMap<>();
for (String str : strings) {
String[] split = str.split("=");
if (split.length > 1) {
reulsts.put(split[0], split[1]);
}
}
return reulsts;
}
}
創建QQ參數實體類
創建 OAuthProperties 用於配合yml配置文件動態獲取參數
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* description: 楊不易網站 :www.yangbuyi.top
* ClassName: OAuthProperties
* create: 2020-06-24 17:06
*
* @author: yangbuyi
* @since: JDK1.8
* <p>
* 獲取Code碼
**/
@Component
//對應application.yml中,oauth下參數
@ConfigurationProperties(prefix = "oauth")
public class OAuthProperties {
//獲取applicaiton.yml下qq下所有的參數
private QQProperties qq = new QQProperties();
public QQProperties getQQ() {
return qq;
}
public void setQQ(QQProperties qq) {
this.qq = qq;
}
}
創建 QQProperties 用於請求qq的參數
import lombok.Data;
import org.springframework.stereotype.Component;
/**
* description: 楊不易網站 :www.yangbuyi.top
* ClassName: QQProperties
* create: 2020-06-24 17:04
*
* @author: yangbuyi
* @since: JDK1.8
*
* 集成第三方登陸 QQ 參數
**/
@Data
@Component
public class QQProperties {
/**
* 你的appid
*/
private String client_id;
/**
* #你的appkey
*/
private String client_secret;
/**
* 你接收響應code碼地址
*/
private String redirect_uri;
/**
* 騰訊獲取code碼地址
*/
private String code_callback_uri;
/**
* 騰訊獲取access_token地址
*/
private String access_token_callback_uri;
/**
* 騰訊獲取openid地址
*/
private String openid_callback_uri;
/**
* 騰訊獲取用戶資訊地址
*/
private String user_info_callback_uri;
/**
* 要回調到哪個網站
*/
private String redirect_url_index_yby;
private String redirect_url_login_yby;
}
創建 QQOpenidDTO 用於獲取 access_token、openid
import lombok.Data;
/**
* description: 楊不易網站 :www.yangbuyi.top
* ClassName: QQOpenidDTO
* create: 2020-06-24 17:19
*
* @author: yangbuyi
* @since: JDK1.8
*
* 用來獲取 access_token、openid
**/
@Data
public class QQOpenidDTO {
private String openid;
private String client_id;
}
創建QQDTO 接收QQ返回來的json參數
import lombok.Data;
/**
* description: 楊不易網站 :www.yangbuyi.top
* program: yangbuyi-erp-2020
* ClassName: QQDTO
* create: 2020-06-24 17:20
*
* @author: yangbuyi
* @since: JDK1.8
* @QQDTO: 用於存儲QQ伺服器返回來的參數
**/
@Data
public class QQDTO {
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; //黃鑽等級
private String is_yellow_year_vip; //標識是否為年費黃鑽用戶(0:不是; 1:是)
private String province; // 省
private String city; // 市
}
示例
創建前端請求跳轉 controller
@Controller
@Slf4j
public class RequestController {
@RequestMapping("login")
public String login() {
System.out.println("登陸進來啦");
return "login";
}
@RequestMapping("home")
public String home() {
return "home";
}
}
創建前端頁面
login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<!--
登錄地址 action="/api/login/oauth"
-->
<form action="/api/login/oauth">
<input type="submit" style="background: red;size: 25px" value="登陸">
</form>
</body>
</html>
home.html
<!DOCTYPE html>
<html lang="en" xmlns:th="//www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div class="">
<label class="">登陸成功</label>
<div class="">
<p th:text="'openID :' + ${map.qqOpenidDTO.openid}"></p>
<p th:text="'用戶名稱 :' + ${map.user.nickname}"></p>
用戶頭像:
<img th:src="${map.user.figureurl_qq_1}" alt="">
<br>
<img th:src="${map.user.figureurl_qq_1}" alt="">
<img th:src="${map.user.figureurl_qq_2}" alt="">
性別:
<p th:text="${map.user.gender}"></p>
<p th:text="${map.user.vip}"></p>
<p th:text="${map.user.yellow_vip_level}"></p>
<p th:text="${map.user.is_yellow_year_vip}"></p>
<p th:text="${map.user.province}"></p>
<p th:text="${map.user.city}"></p>
</div>
</div>
<!--參數列表:-->
<!--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; //黃鑽等級-->
<!--private String is_yellow_year_vip; //標識是否為年費黃鑽用戶(0:不是; 1:是)-->
<!--private String province; // 省-->
<!--private String city; // 市-->
</body>
</html>
啟動注意事項
必須要打包到伺服器啟動QQ才能回調
項目部署
方案一:
點擊package 打包
複製 項目 和 application.yml 上傳到linux伺服器
修改application.yml 中的埠為 80
運行 Java程式
java -jar qqlogindemo-0.0.1-SNAPSHOT.jar
啟動成功
訪問 login 頁面
點擊登錄 》 QQ掃碼或者密碼登錄 》 登錄成功 跳轉到 home