从零玩转第三方登录之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.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="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 中国大陆许可协议进行许可。

从零玩转第三方登录之QQ登录的更多相关文章

  1. 网站如何接入第三方登录,微信登录和QQ登录:注册认证篇

    第三方登录平台接入 (QQ\微信登录) QQ登录接入 第一步成为QQ应用开发者,审核期限七天 一.所需材料 1.公司注册相关信息 2.营业执照扫描件 微信登录接入 第一步成为微信开发平台开发者,认证费 ...

  2. Android之第三方平台实现QQ登录和QQ分享

    目前大多数APP都包含了第三方平台的登录,特别是QQ和微信,这篇博客主要讲的是如何实现QQ第三方平台实现QQ登录和分享功能,功能包含: 登录授权登录获取用户信息(昵称,头像,地址等) QQ分享给好友 ...

  3. dedecms织梦第三方登录插件-QQ登录、微博登录、微信登录

    织梦程序集成第三方QQ登录.微博登录.微信登录,获取QQ.微博.微信,并存储至数据库,一键注册为网站会员,不用再次填写绑定信息,方便粘贴用户更强. 织梦第三方登录效果 第三方登录插件特点 1.所有文件 ...

  4. 基于Vue、Springboot网站实现第三方登录之QQ登录,以及邮件发送

    基于Vue.Springboot实现第三方登录之QQ登录 前言 一.前提(准备) 二.QQ登录实现 1.前端 2.后端 1.application.yml 和工具类QQHttpClient 2.QQL ...

  5. 第三方登录(QQ登录)开发流程详解

    原文:http://www.cnblogs.com/it-cen/p/4338202.html 近排由于工作的繁忙,已经一个星期没写博文做分享了,接下来我对网站接入第三方登录----QQ登录的实现逻辑 ...

  6. (转)第三方登录(QQ登录)开发流程详解

    近排由于工作的繁忙,已经一个星期没写博文做分享了,接下来我对网站接入第三方登录----QQ登录的实现逻辑做一个详细的讲解. 对于整个流程的详细文档可以到QQ互联官网(http://wiki.conne ...

  7. android实现第三方登录之QQ登录

    首先,当然是在腾讯开放平台(http://open.qq.com/)注册成为开发者,然后获取APP ID 1.下载SDK http://wiki.open.qq.com/wiki/mobile/SDK ...

  8. 第三方登录:QQ登录实现(OAuth2.0)

    一.创建应用 1.在 QQ互联 创建应用 地址:https://connect.qq.com/manage.html#/ 然后进行实名认证,创建应用,审核通过 然后点击查看,可以获得 APP ID 和 ...

  9. Spring Security构建Rest服务-0102-Spring Social开发第三方登录之qq登录

    图一 基于SpringSocial实现qq登录,要走一个OAuth流程,拿到服务提供商qq返回的用户信息. 由上篇介绍的可知,用户信息被封装在了Connection里,所以最终要拿到Connectio ...

随机推荐

  1. Dva & Umi

    Dva & Umi Dva.js & Umi.js React & Redux https://dvajs.com/ React and redux based, lightw ...

  2. 比特币跌破3.5万美元,巨鲸们将目光瞄向SPC算力币

    比特币最近又迎来了大幅下跌,截至周三(1月20日),比特币跌幅超过5%,跌破3.5万美元.很显然,比特币没有预期那样顺顺利利地登顶4万美元,反而又出现了回调迹象.有些巨鲸们在大肆囤币,然而也有些巨鲸们 ...

  3. 深入解析 HTTP 缓存控制

    缓存(Cache)是计算机领域里的一个重要概念,是优化系统性能的利器. 由于链路漫长,网络时延不可控,浏览器使用 HTTP 获取资源的成本较高.所以,非常有必要把"来之不易"的数据 ...

  4. 使用 Tye 辅助开发 k8s 应用竟如此简单(四)

    续上篇,这篇我们来进一步探索 Tye 更多的使用方法.本篇我们来了解一下如何在 Tye 中如何进行日志的统一管理. Newbe.Claptrap 是一个用于轻松应对并发问题的分布式开发框架.如果您是首 ...

  5. oracle之用户

    命令都是在命令行窗口执行 创建用户 1)登陆管理员用户 sqlplus system/密码 sqlplus system/briup 注意不要以分号结尾 2)创建用户 create user 用户名 ...

  6. es初步搭建

    1.es tar包传至linux上 并解压 tar -zxvf elasticsearch-7.4.0-linux-x86_64.tar.gz 2.新建用户 useradd xxxname passw ...

  7. Java 12 新特性介绍,快来补一补

    Java 12 早在 2019 年 3 月 19 日发布,它不是一个长久支持(LTS)版本.在这之前我们已经介绍过其他版本的新特性,如果需要可以点击下面的链接进行阅读. Java 11 新特性介绍 J ...

  8. 阿里云DataWorks实践:数据集成+数据开发

    简介 什么是DataWorks: DataWorks(数据工场,原大数据开发套件)是阿里云重要的PaaS(Platform-as-a-Service)平台产品,为您提供数据集成.数据开发.数据地图.数 ...

  9. C++教程01:计算机系统的组成

    教程首发 | 公众号:lunvey 学习C++之前,需要先了解一点基础的计算机知识.毕竟C++是跑在计算机系统上的,我们写的程序都是一段段的指令集. 首台计算机ENIAC问世之后,缺少原理指导.冯诺依 ...

  10. 翻译:《实用的Python编程》04_00_Overview

    目录 | 上一节 (3 程序组织) | 下一节 (5 Python对象的内部工作原理) 4. 类和对象 到目前为止,我们的程序仅使用了内置的 Python 数据类型.本节,我们介绍类(class)和对 ...