首页 技术 正文
技术 2022年11月14日
0 收藏 605 点赞 2,375 浏览 19632 个字

从零玩转第三方登录之QQ登录

前言

在真正开始对接之前,我们先来聊一聊后台的方案设计。既然是对接第三方登录,那就免不了如何将用户信息保存。首先需要明确一点的是,用户在第三方登录成功之后,

我们能拿到的仅仅是一个代表用户唯一身份的ID(微博是真实uid,QQ是加密的openID)以及用来识别身份的accessToken,当然还有昵称、头像、性别等有限资料,

对接第三方登录的关键就是如何确定用户是合法登录,如果确定这次登录的和上次登录的是同一个人并且不是假冒的。其实这个并不用我们特别操心,就以微博登录为例,

用户登录成功之后会回调一个code给我们,然后我们再拿code去微博那换取 accessToken ,如果这个code是用户乱填的,那这一关肯定过不了,所以,前面的担心有点多余,哈哈。

1. 认识Oauth2.0

现在很多网站都要不管是为了引流也好,为了用户方便也好一般都有第三方账号登陆的需求,今天以QQ登陆为例,来实现一个最简单的第三方登陆。

目前主流的第三方登录都是依赖的Oauth2.0实现的,最常见的就是在各种中小型网站或者App中的QQ登录,微信登录等等。所以我建议想要学习和实现第三方登录同学去了解下这个协议。

必须要域名并且进行备案

比如我的域名: https://yangbuyi.top/

因为腾讯有一个域名认证机制啥的。。。。。。

2.实名认证

QQ登录我们对接的是QQ互联,地址:https://connect.qq.com ,首先需要注册成为开发者并实名认证,需要手持身份证照片,具体就不讲了。

2.1、进行申请开发者身份

2.2 创建应用

进入应用管理页面创建应用,根据实际需要是创建网站应用还是移动应用,我这里是网站应用:

提交成功完步后等待客服审核即可

这是我网站的基本接口信息

QQ登陆流程

请求参数

创建springboot工程

依赖

        <!-- qq登陆集成 开始 -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.https://www.shuzhiduo.com/A/LPdoG7MEd3/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
**/
@Datapublic 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="http://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="http://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

到此 从零玩转 第三方登录之QQ登录 就结束了哦。

GITEE:https://gitee.com/yangbuyi

GITHUB: https://github.com/GenuineYangShuai

个人博客网站: https://www.yangbuyi.top/

春天交流群 :598347590

你的压力源于无法自律,只是假装努力,现状跟不上你内心的欲望,所以你焦急又恐慌—杨不易.|

本文作者:杨不易呀

本文链接:https://www.yangbuyi.top/qqLogin

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,111
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,584
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,431
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,203
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,838
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,922