title: 从零玩转QQ登录
date: 2021-05-01 15:55:39.951
updated: 2023-03-30 13:29:03.865
url: https://www.yby6.com/archives/clwzqqdlu
categories:
- OSS
- api
- 单例模式
- 从零玩转系列
tags:
- 第三方登录
- QQ登录
- Oauth2.0

从零玩转第三方登录之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/yangbuyiya

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

春天交流群 :598347590

从零玩转QQ登录-clwzqqdlu的更多相关文章

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

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

  2. 零基础~仿qq登录界面

    html代码: <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <t ...

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

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

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

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

  5. 从零玩转SpringSecurity+JWT整合前后端分离

    从零玩转SpringSecurity+JWT整合前后端分离 2021年4月9日 · 预计阅读时间: 50 分钟 一.什么是Jwt? Json web token (JWT), 是为了在网络应用环境间传 ...

  6. 从零玩转人脸识别之RGB人脸活体检测

    从零玩转RGB人脸活体检测 前言 本期教程人脸识别第三方平台为虹软科技,本文章讲解的是人脸识别RGB活体追踪技术,免费的功能很多可以自行搭配,希望在你看完本章课程有所收获. ArcFace 离线SDK ...

  7. 【开源】简单4步搞定QQ登录,无需什么代码功底【无语言界限】

    说17号发超简单的教程就17号,qq核审通过后就封装了这个,现在放出来~~ 这个是我封装的一个开源项目:https://github.com/dunitian/LoTQQLogin ————————— ...

  8. 腾讯开放平台 手机QQ登录 错误码:110406 解决办法

    作者:Panda Fang 出处:http://www.cnblogs.com/lonkiss/p/4204284.html 原创文章,转载请注明作者和出处,未经允许不可用于商业营利活动 腾讯开发平台 ...

  9. WPF简单模拟QQ登录背景动画

    介绍 之所以说是简单模拟,是因为我不知道QQ登录背景动画是怎么实现的.这里是通过一些办法把它简化了,做成了类似的效果 效果图 大体思路 首先把背景看成是一个4行8列的点的阵距,X轴Y轴都是距离70.把 ...

  10. 使用Java的Frame类编写的QQ登录界面

    public static void main(String[] args) { Frame f = new Frame(); //关闭窗体 f.addWindowListener(new Windo ...

随机推荐

  1. 集群部署专题之二:超高性能RPC框架Zeroc-ICE集群部署简易教程

    一.前言 Zeroc ICE在简中互联网的资料十分匮乏,以至于大家线上使用时可能会有所顾虑.其实大家尽可放心,ZerocICE是一款性能和稳定性都非常优秀的RPC组件,这也是我当时选择ZerocICE ...

  2. std::for_each易忽略点

    以下代码为修改vector内部的每一个元素,使其每个元素大小变为原来的平方. std::vector v1{1, 2, 4, 2}; std::for_each(begin(v1), end(v1), ...

  3. 【AI 模型】首个 Joy 模型诞生!!!全民生成 Joy 大片

    接上一篇文章 "只要10秒,AI生成IP海报,解放双手",这次是全网第一个"共享joy模型",真的赚到了! 经过这段时间无数次的探索.试错.实验,最终积累了非常 ...

  4. 12 款最棒 Vue 开源 UI 库

    文章目录 Element Plus - 经典中的经典,全面支持 Vue 3 TDesign Vue - 鹅厂优质 UI 组件,配套工具完满,设计工整,文档清晰 ArcoDesign Vue- 字节优质 ...

  5. Python基础——二分法、面向过程编程思想、有名函数、lambda、max、_min的应用、sorted排序、map的应用、filter的应用、reduce的应用

    文章目录 内容回顾 二分法 伪代码模板 面向过程编程思想 函数式 def用于定义有名函数 lambda用于定义匿名函数 调用匿名函数 匿名函数作用 匿名函数的示范 max的应用 min的应用 sort ...

  6. 【动画进阶】神奇的背景,生化危机4日食 Loading 动画还原

    最近,在 Steam 玩一款老游戏(生化危机 4 重置版),其中,每当游戏转场的过程中,都有这么一个有趣的 Loading 动画: 整个效果有点类似于日食效果,中间一圈黑色,向外散发着太阳般的光芒. ...

  7. 2023年SWPU NSS 秋季招新赛 (校外赛道)WP—Crypto

    一.Caesar_base 题目信息 s = "HIJKLMNOPQRSTUVWXYZABCDEFGhijklmnopqrstuvwxyzabcdefg0123456789+/" ...

  8. 回文自动机(PAM) 详解

    PAM 是一种高效存储字符串中所有回文子串的自动机,用于解决回文串相关问题. 虽然代码稍微长一点,但写起来比 manacher 容易很多,毕竟没有加了一堆字符再转回原串的若干上取整下取整问题. 前置知 ...

  9. JavaScript 语法:注释与输入 / 输出

    作者:WangMin 格言:努力做好自己喜欢的每一件事 JavaScript 注释 JavaScript 注释用于解释 JavaScript 代码,提高代码的可读性,也可以用于在测试替代代码时阻止执行 ...

  10. 栈与队列应用:迷宫问题(DFS非最短路径)

    //先输入行列,在输入迷宫 以-1 -1 结束 #include<stdio.h> #include<stdlib.h> #define MAXSIZE 100 #define ...