QQ login from zero to third-party login
preface
Before we really start docking, let's talk about the scheme design of the background. Since it is connected to the third-party login, it is inevitable how to save the user information. First of all, it should be clear that after the user successfully logs in to the third party, All we can get is an ID representing the user's unique identity (microblog is a real uid, QQ is an encrypted openID) and an accessToken used to identify the user's identity. Of course, there are limited information such as nickname, avatar and gender, The key to third-party login is how to determine that the user is logged in legally. If it is determined that the login and the last login are the same person and are not fake. In fact, we don't have to worry about this. Take microblog login as an example, After successful login, the user will call back a code to us, and then we will take the code to the microblog in exchange for the accessToken. If the code is filled in by the user, it will certainly not pass this level. Therefore, the previous worry is a little superfluous, ha ha.
1. Know oauth2 0
Now many websites need to log in with a third-party account, whether for drainage or user convenience. Today, take QQ login as an example to realize the simplest third-party login. At present, the mainstream third-party login depends on oauth2 0, the most common is QQ login, wechat login and so on in various small and medium-sized websites or apps. So I suggest students who want to learn and implement the third-party login to understand this protocol.
The domain name must be and filed
For example, my domain name: https://yangbuyi.top/ Because Tencent has a domain name authentication mechanism......
2. Real name authentication QQ login, we connect to QQ Internet, address: https://connect.qq.com First, you need to register as a developer and authenticate your real name. You need to hold an ID card photo. I won't talk about the details.
2.1. Apply for developer identity

2.2 creating applications
Enter the application management page to create an application. According to the actual needs, create a website application or a mobile application. Here is a website application:


After the submission is successful, wait for the customer service to review

This is the basic interface information of my website

QQ login process

Request parameters

Create springboot project

rely on
<!-- qq Login integration started --> <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 Conversion tool--> <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 End of login integration --> <!-- Template --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <!-- Other configurations --> <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>
Create http request tool
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: Yang Buyi website: www.yangbuyi.com 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 { // Set up connection pool connMgr = new PoolingHttpClientConnectionManager(); // Set connection pool size connMgr.setMaxTotal(100); connMgr.setDefaultMaxPerRoute(connMgr.getMaxTotal()); // Validate connections after 1 sec of inactivity connMgr.setValidateAfterInactivity(1000); RequestConfig.Builder configBuilder = RequestConfig.custom(); // Set connection timeout configBuilder.setConnectTimeout(MAX_TIMEOUT); // Set read timeout configBuilder.setSocketTimeout(MAX_TIMEOUT); // Sets the timeout for getting connection instances from the connection pool configBuilder.setConnectionRequestTimeout(MAX_TIMEOUT); requestConfig = configBuilder.build(); } /** * Send GET request (HTTP) without input data * * @param url * @return */ public static String doGet(String url) { return doGet(url, new HashMap<String, Object>()); } /** * Send GET request (HTTP) in K-V format * * @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; } /** * Send POST request (HTTP) without input data * * @param apiUrl * @return */ public static String doPost(String apiUrl) { return doPost(apiUrl, new HashMap<String, Object>()); } /** * Send POST request in K-V format * * @param apiUrl API Interface URL * @param params Parameter 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; } /** * Send POST request in JSON form * * @param apiUrl * @param json json object * @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");// Solve the problem of Chinese garbled code 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; } /** * Create SSL secure connection * * @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 start*/ /** * Send the get request and send the request using java code * @param url * @return * @throws Exception */ public static String doGetHub(String url) throws Exception{ CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); // An http request was sent CloseableHttpResponse response = httpclient.execute(httpGet); // If the response 200 is successful, parse the response result if(response.getStatusLine().getStatusCode()==200){ // Get the content of the response HttpEntity responseEntity = response.getEntity(); return EntityUtils.toString(responseEntity); } return null; } /** * Convert string to map * @param responseEntity * @return */ public static Map<String,String> getMap(String responseEntity) { Map<String, String> map = new HashMap<>(); // Parse string with & String[] result = responseEntity.split("\\&"); for (String str : result) { // Parse string with = String[] split = str.split("="); // Store string in map if (split.length == 1) { map.put(split[0], null); } else { map.put(split[0], split[1]); } } return map; } /** * Get map through json * @param responseEntity * @return */ public static Map<String,String> getMapByJson(String responseEntity) { Map<String, String> map = new HashMap<>(); // Alibaba fastjson converts json into map JSONObject jsonObject = JSONObject.parseObject(responseEntity); for (Map.Entry<String, Object> entry : jsonObject.entrySet()) { String key = entry.getKey(); // Convert obj to string String value = String.valueOf(entry.getValue()) ; map.put(key, value); } return map; } /*gitHub end*/ }
Create a cross domain configuration class in case of cross domain problems
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("*"); // Indicates what domain name is cross domain * indicates that all domains are cross domain corsConfiguration.addAllowedOrigin("*"); // Inject it urlBasedCorsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); CorsFilter corsFilter = new CorsFilter(urlBasedCorsConfigurationSource); return corsFilter; } }
Create 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: Yang Buyi website: www.yangbuyi.com top * @program: qqlogindemo * @ClassName: loginController * @create: 2020-08-18 14:41 * @author: yangbuyi * @since: JDK1.8 * @loginController: Third party QQ login **/ @Controller @Slf4j @RequestMapping("api") public class loginController { /** * Authentication parameters */ @Autowired private OAuthProperties oauth; /** * Call QQ login interface * Process: first call the interface to get the code, and then get access according to the code_ Token, which is used to obtain the corresponding user information according to the token * @param response */ @GetMapping("/login/oauth") public void loginQQ( HttpServletResponse response) { // Redirect access to QQ login server try { response.sendRedirect(oauth.getQQ().getCode_callback_uri() + //Get code address "?client_id=" + oauth.getQQ().getClient_id() //appid +"&state=" + UUID.randomUUID() + //This is said to be anti attack, just give a random uuid "&redirect_uri=" + oauth.getQQ().getRedirect_uri() +//This is very important. This is the callback address, that is, the code code returned by Tencent "&response_type=code"); } catch (IOException e) { e.printStackTrace(); } } /** * Callback address set on qq platform * * Receive the code brought from the callback address * * @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()); // Get Tencent access token Map<String, String> reulsts = getAccess_token(params); System.out.println("Traverse the obtained data:"); for (Map.Entry<String, String> entry : reulsts.entrySet()) { System.out.println(entry.getKey() + "=" + entry.getValue()); } System.out.println("Traversal complete"); //Access here_ The token has been handled //Next, get the openid. Only when you get the openid can you get the user information 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); //Next, handle openid //Intercept the required part of the json string String openid = openidContent.substring(openidContent.indexOf("{"), openidContent.indexOf("}") + 1); // json to object Gson gson = new Gson(); //Convert the returned openid into DTO QQOpenidDTO qqOpenidDTO = gson.fromJson(openid, QQOpenidDTO.class); // Encapsulate parameter request user information data params.clear(); //Set access_token params.put("access_token", reulsts.get("access_token")); //Set openid params.put("openid", qqOpenidDTO.getOpenid()); //Set appid params.put("oauth_consumer_key", qqOpenidDTO.getClient_id()); //Get user information String userInfo = HttpsUtils.doGet(oauth.getQQ().getUser_info_callback_uri(), params); QQDTO qqDTO = gson.fromJson(userInfo, QQDTO.class); // (normally, you can use openid as the user name during development and define your own password.) try { /* Assembly data */ HashMap<String, Object> map = new HashMap<>(); map.put("user", qqDTO); map.put("qqOpenidDTO", qqOpenidDTO); request.setAttribute("map", map); log.info("user data:{}" + qqDTO); log.info("qqOpenidDTO data:{}" + qqOpenidDTO); return "home"; } catch (Exception e) { e.printStackTrace(); return "login"; } } /** * Get Tencent access_token * * @return */ public Map<String, String> getAccess_token(HashMap<String, Object> params) { // Authentication address //Get access_token such as access_token=9724892714FDF1E3ED5A4C6D074AF9CB&expires_ in=7776000&refresh_ token=9E0DE422742ACCAB629A54B3BFEC61FF String result = HttpsUtils.doGet(oauth.getQQ().getAccess_token_callback_uri(), params); //Cut the string of the obtained data String[] strings = result.split("&"); //Put it into the map after cutting 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; } }
Create QQ parameter entity class
Create OAuthProperties to dynamically obtain parameters with yml configuration file
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; /** * description: Yang Buyi website: www.yangbuyi.com top * ClassName: OAuthProperties * create: 2020-06-24 17:06 * * @author: yangbuyi * @since: JDK1.8 * <p> * Get Code **/ @Component //Corresponding to application Parameter under oauth in YML @ConfigurationProperties(prefix = "oauth") public class OAuthProperties { //Get applicaiton All parameters under YML and qq private QQProperties qq = new QQProperties(); public QQProperties getQQ() { return qq; } public void setQQ(QQProperties qq) { this.qq = qq; } }
Create QQProperties parameters for requesting qq
import lombok.Data; import org.springframework.stereotype.Component; /** * description: Yang Buyi website: www.yangbuyi.com top * ClassName: QQProperties * create: 2020-06-24 17:04 * * @author: yangbuyi * @since: JDK1.8 * * Integrate third-party login QQ parameters **/ @Data @Component public class QQProperties { /** * Your appid */ private String client_id; /** * #Your appkey */ private String client_secret; /** * You receive the response code address */ private String redirect_uri; /** * Tencent get code address */ private String code_callback_uri; /** * Tencent get access_token address */ private String access_token_callback_uri; /** * Tencent obtains openid address */ private String openid_callback_uri; /** * Tencent obtains user information address */ private String user_info_callback_uri; /** * Which website do you want to call back to */ private String redirect_url_index_yby; private String redirect_url_login_yby; }
Create QQOpenidDTO to obtain access_token,openid
import lombok.Data; /** * description: Yang Buyi website: www.yangbuyi.com top * ClassName: QQOpenidDTO * create: 2020-06-24 17:19 * * @author: yangbuyi * @since: JDK1.8 * * Used to get access_token,openid **/ @Data public class QQOpenidDTO { private String openid; private String client_id; }
Create QQDTO to receive json parameters returned from QQ
import lombok.Data; /** * description: Yang Buyi website: www.yangbuyi.com top * program: yangbuyi-erp-2020 * ClassName: QQDTO * create: 2020-06-24 17:20 * * @author: yangbuyi * @since: JDK1.8 * @QQDTO: Used to store the parameters returned by QQ server **/ @Data public class QQDTO { private String ret; //Return code private String msg; //If RET < 0, there will be a corresponding error message prompt, and all the returned data are encoded in UTF-8. private String nickname; //User's nickname in QQ space. private String figureurl; //Size 30 × 30 pixel QQ space avatar URL. private String figureurl_1; //Size 50 × 50 pixel QQ space avatar URL. private String figureurl_2; //Size is 100 × 100 pixel QQ space avatar URL. private String figureurl_qq_1; //Size 40 × 40 pixel QQ avatar URL. private String figureurl_qq_2; //Size is 100 × 100 pixel QQ avatar URL. It should be noted that not all users have QQ's 100x100 avatar, but 40x40 pixels will certainly have it. private String gender; //Gender. If it is not available, it returns "male" by default private Integer gendertype; // Gender number private String is_yellow_vip; //Identify whether the user is a yellow diamond user (0: No; 1: Yes). private String vip; //Identify whether the user is a yellow diamond user (0: No; 1: Yes) private String yellow_vip_level; //Yellow diamond grade private String level; //Yellow diamond grade private String is_yellow_year_vip; //Identify whether it is an annual fee yellow diamond user (0: No; 1: Yes) private String province; // province private String city; // city }
Example

Create front-end request jump controller
@Controller @Slf4j public class RequestController { @RequestMapping("login") public String login() { System.out.println("Log in"); return "login"; } @RequestMapping("home") public String home() { return "home"; } }
Create front page

login.html
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <!-- Login address action="/api/login/oauth" --> <form action="/api/login/oauth"> <input type="submit" style="background: red;size: 25px" value="land"> </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="">Login successful</label> <div class=""> <p th:text="'openID :' + ${map.qqOpenidDTO.openid}"></p> <p th:text="'User name :' + ${map.user.nickname}"></p> User Avatar: <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=""> Gender: <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> <!--parameter list:--> <!--private String ret; //Return code -- > <!--private String msg; //If RET < 0, there will be a corresponding error message prompt, and all the returned data are encoded in UTF-8. -- > <!--private String nickname; //User's nickname in QQ space. -- > <!--private String figureurl; //Size 30 × 30 pixel QQ space avatar URL. -- > <!--private String figureurl_1; //Size 50 × 50 pixel QQ space avatar URL. -- > <!--private String figureurl_2; //Size is 100 × 100 pixel QQ space avatar URL. -- > <!--private String figureurl_qq_1; //Size 40 × 40 pixel QQ avatar URL. -- > <!--private String figureurl_qq_2; //Size is 100 × 100 pixel QQ avatar URL. It should be noted that not all users have QQ's 100x100 avatar, but 40x40 pixels must be. - > <!--private String gender; //Gender. If it cannot be obtained, it returns "male" - > <!--private Integer gendertype; // Gender number -- > <!--private String is_yellow_vip; //Identifies whether the user is a yellow diamond user (0: No; 1: Yes). -- > <!--private String vip; //Identify whether the user is a yellow diamond user (0: No; 1: Yes) - > <!--private String yellow_vip_level; //Yellow diamond grade -- > <!--private String level; //Yellow diamond grade -- > <!--private String is_yellow_year_vip; //Identify whether it is an annual yellow diamond user (0: No; 1: Yes) - > <!--private String province; // Province -- > <!--private String city; // City -- > </body> </html>
Startup precautions
It must be packaged to the server to start QQ before callback
Project deployment
Scheme I:
Click package to package

Copy project and application Upload YML to linux server


Modify application The port in YML is 80

Running Java programs
java -jar qqlogindemo-0.0.1-SNAPSHOT.jar
Start successful

Visit the login page

Click "login" QQ code scanning or "password login" to successfully jump to home


