准备工作
1.云服务器
2.备案的域名
3.本地调试需要修改hosts文件,将域名映射到127.0.0.1
一、申请QQ互联,并成为开发者
QQ互联:https://connect.qq.com/index.html
1.1 登录后,点击头像,进入认证页面,填写信息,等待审核。
1.2 审核通过后,点击创建应用



1.3 审核通过后,就可以使用APP ID 和 APP Key

二、网站开发的流程
2.1 准备工作_OAuth2.0
本步骤的作用:
接入QQ登录前,网站需首先进行申请,获得对应的appid与appkey,以保证后续流程中可正确对网站与用户进行验证与授权。
本步骤在整个流程中的位置:
开发者文档
https://wiki.connect.qq.com/%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c_oauth2-0
2.2 放置“QQ登录”按钮_OAuth2.0
2.2.1 下载“QQ登录”按钮图片,并将按钮放置在页面合适的位置
按钮图片下载: 点击这里下载 。
按照UI规范,将按钮放置在页面合适的位置:点击这里查看。
2.2.2 为“QQ登录”按钮添加前台代码
2.2.2.1 效果演示
用户在页面上点击“QQ登录”按钮,将触发QQ登录对话框,效果如下图所示:

2.2.2.2 前台代码
为了实现上述效果,应该为“QQ登录”按钮图片添加如下前台代码:
<img src=QQ登录图标文件在服务器上的地址 onclick=按钮点击事件>
2.2.2.3 代码示例
1)写一个函数“toLogin()”,该函数通过调用“index.php”中的qq_login函数来实现将页面跳转到QQ登录页面。
(示例中的oauth/index.php,请参见从SDK下载页面下载PHP SDK,在Connect2.1文件夹下的index.php文件。)
<script>
function toLogin()
{
//以下为按钮点击事件的逻辑。注意这里要重新打开窗口
//否则后面跳转到QQ登录,授权页面时会直接缩小当前浏览器的窗口,而不是打开新窗口
var A=window.open("oauth/index.php","TencentLogin",
"width=450,height=320,menubar=0,scrollbars=1,
resizable=1,status=1,titlebar=0,toolbar=0,location=1");
}
</script>
2)为按钮添加“toLogin()”事件:
<a href="#" onclick='toLogin()'>
<img src="img/qq_login.png"></a>
3.3 使用Authorization_Code获取Access_Token
3.3.1 导入POM依赖
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.2</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore</artifactId>
<version>4.4.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.47</version>
</dependency>
3.3.2 抽取QQutils工具类,用来封装APP_ID、APP_KEY等常量,以及获取token和openId及根据openId获取用户信息的工具方法
package com.gmw.boot.send_meail.utils;
import com.alibaba.fastjson.JSONObject;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.io.IOException;
/**
* 这是一个QQ的工具类,获取token,openID以及用户的信息
*/
public class QQutils {
//设置用户的APP_ID
public static final String APP_ID = "101870215";
//设置用户的APP_KEY
public static final String APP_KEY = "5fdd507e863617d7f33b1d69ed5a1eed";
//设置用户的回调地址
public static final String REDIRECT_URL = "http://www.raincoding.cn/qq/callback";
/**
* 获取Token的方法
*/
public static String getAccessToken(String url) throws IOException {
String token = null;
//创建client对象
CloseableHttpClient client = HttpClients.createDefault();
//创建HttpGet请求
HttpGet httpGet = new HttpGet(url);
//执行execute方法,返回response
HttpResponse response = client.execute(httpGet);
//获取响应体
HttpEntity entity = response.getEntity();
//判断响应体是否为空
if(entity != null){
String result = EntityUtils.toString(entity, "UTF-8");
//返回一串json字符串
//access_token=SHDJS45645FAD4ASD45
//expires_in=77766000
//refresh_token=DJFHDJSF5445SDSD
String[] all = result.split("&");
for (String s : all) {
if(s.indexOf("=") > 0){
token = s.substring(s.indexOf("=") + 1);
break;
}
}
System.out.println(result);
}
return token;
}
/**
* 根据token,得到对应用户身份的OpenID。
*/
public static String getOpenId(String url) throws IOException{
String openId = null;
//创建client对象
CloseableHttpClient client = HttpClients.createDefault();
//创建HttpGet请求
HttpGet httpGet = new HttpGet(url);
//执行execute方法,返回response
HttpResponse response = client.execute(httpGet);
//获取响应体
HttpEntity entity = response.getEntity();
//判断响应体是否为空
if(entity != null){
String result = EntityUtils.toString(entity, "UTF-8");
//返回一串json字符串
//({"cliend_id":"1056365","openId":"4687FASDAS61ER"})
//调用工具类,将json字符串转换成json对象
JSONObject ob = JSONUtils.castToJson(result);
openId = ob.getString("openid");
System.out.println(result);
}
return openId;
}
/**
* 根据openId获取用户的信息,因为openId是唯一的
*/
public static JSONObject getUserInfo(String url) throws IOException{
JSONObject object = null;
//创建client对象
CloseableHttpClient client = HttpClients.createDefault();
//创建HttpGet请求
HttpGet httpGet = new HttpGet(url);
//执行execute方法,返回response
HttpResponse response = client.execute(httpGet);
//获取响应体
HttpEntity entity = response.getEntity();
//判断响应体是否为空
if(entity != null){
String result = EntityUtils.toString(entity, "UTF-8");
object = JSONObject.parseObject(result);
System.out.println(result);
}
return object;
}
}
3.3.3 创建JSONUtils工具类用来将json字符串转换成json对象
package com.gmw.boot.send_meail.utils;
import com.alibaba.fastjson.JSONObject;
public class JSONUtils {
public static JSONObject castToJson(String jsoup){
int startIndex = jsoup.indexOf("(");
int endIndex = jsoup.indexOf(")");
String json = jsoup.substring(startIndex + 1,endIndex);
JSONObject object = JSONObject.parseObject(json);
return object;
}
}
3.3.4 获取Authorization Code
请求地址:
PC网站:https://graph.qq.com/oauth2.0/authorize
请求方法:
GET
请求参数:
请求参数请包含如下内容:
| 参数 | 是否必须 | 含义 |
|---|---|---|
| response_type | 必须 | 授权类型,此值固定为“code”。 |
| client_id | 必须 | 申请QQ登录成功后,分配给应用的appid。 |
| redirect_uri | 必须 | 成功授权后的回调地址,必须是注册appid时填写的主域名下的地址,建议设置为网站首页或网站的用户中心。注意需要将url进行URLEncode。 |
| state | 必须 | client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。 |
| scope | 可选 | 请求用户授权时向用户显示的可进行授权的列表。 可填写的值是API文档中列出的接口,如果要填写多个接口名称,请用逗号隔开。 例如:scope=get_user_info,list_album,upload_pic 不传则默认请求对接口get_user_info进行授权。 建议控制授权项的数量,只传入必要的接口名称,因为授权项越多,用户越可能拒绝进行任何授权。 |
| display | 可选 | 仅PC网站接入时使用。 用于展示的样式。不传则默认展示为PC下的样式。 如果传入“mobile”,则展示为mobile端下的样式。 |
<a th:href="@{/qq/auth}"><img th:src="@{/image/qq.png}"/></a>

@RequestMapping(value = "/qq/auth")
public String oauth(HttpSession session){
//生成UUID当作唯一的值,传递给回调地址当作state,用于第三方应用防止CSRF攻击
String uuid = UUID.randomUUID().toString().replaceAll("-","");
//将生成的UUID放到session中
session.setAttribute("state",uuid);
//发送请求,获取获取Authorization Code
String url = "https://graph.qq.com/oauth2.0/authorize?response_type=code" +
"&client_id=" + QQutils.APP_ID +
"&redirect_uri=" + QQutils.REDIRECT_URL +
"&state=" + uuid;
return "redirect:" + url;
}
返回说明:
- 如果用户成功登录并授权,则会跳转到指定的回调地址,并在redirect_uri地址后带上Authorization Code和原始的state值。如:
PC网站:http://graph.qq.com/demo/index.jsp?code=9A5F**06AF&state=test
注意:此code会在10分钟内过期。
- 如果用户在登录授权过程中取消登录流程,对于PC网站,登录页面直接关闭;
错误码说明:
接口调用有错误时,会返回code和msg字段,以url参数对的形式返回,value部分会进行url编码(UTF-8)。
PC网站接入时,错误码详细信息请参见:100000-100031:PC网站接入时的公共返回码。
3.3.5 通过Authorization Code获取Access Token
请求地址:
PC网站:https://graph.qq.com/oauth2.0/token
请求方法:
GET
请求参数:
请求参数请包含如下内容:
| 参数 | 是否必须 | 含义 |
|---|---|---|
| grant_type | 必须 | 授权类型,在本步骤中,此值为“authorization_code”。 |
| client_id | 必须 | 申请QQ登录成功后,分配给网站的appid。 |
| client_secret | 必须 | 申请QQ登录成功后,分配给网站的appkey。 |
| code | 必须 | 上一步返回的authorization code。 如果用户成功登录并授权,则会跳转到指定的回调地址,并在URL中带上Authorization Code。 例如,回调地址为www.qq.com/my.php,则跳转到: http://www.qq.com/my.php?code=520DD95263C1CFEA087** 注意此code会在10分钟内过期。 |
| redirect_uri | 必须 | 与上面一步中传入的redirect_uri保持一致。 |
| fmt | 可选 | 因历史原因,默认是x-www-form-urlencoded格式,如果填写json,则返回json格式 |
/**
* 如果用户成功登录并授权,则会跳转到指定的回调地址,
* 并在redirect_uri地址后带上Authorization Code和原始的state值。
* @return
*/
@RequestMapping(value = "/qq/callback")
public String callback(HttpServletRequest request, HttpServletResponse response,Model model){
//创建HttpSession对象
HttpSession session = request.getSession();
//获取session中的code,也就是存入的uuid
String uuid = (String) session.getAttribute("state");
//获取code参数
String code = request.getParameter("code");
//获取state参数
String state = request.getParameter("state");
//判单获取的state参数和session中uuid值是否一样
if(state != null){
if(!uuid.equals(state)){
throw new RuntimeException("Q获取的state参数和session中uuid值不一样");
}
}
//通过Authorization Code获取Access Token
String url = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code" +
"&client_id=" + QQutils.APP_ID +
"&client_secret=" + QQutils.APP_KEY +
"&code=" + code +
"&redirect_uri=" + QQutils.REDIRECT_URL;
//获取token
String token = null;
try {
QQutils.getAccessToken(url);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("token = " + token);
TODO 后面还有代码...
}
调用getAccessToken(url)方法返回token
/**
* 获取Token的方法
*/
public static String getAccessToken(String url) throws IOException {
String token = null;
//创建client对象
CloseableHttpClient client = HttpClients.createDefault();
//创建HttpGet请求
HttpGet httpGet = new HttpGet(url);
//执行execute方法,返回response
HttpResponse response = client.execute(httpGet);
//获取响应体
HttpEntity entity = response.getEntity();
//判断响应体是否为空
if(entity != null){
String result = EntityUtils.toString(entity, "UTF-8");
//返回一串json字符串
//access_token=SHDJS45645FAD4ASD45
//expires_in=77766000
//refresh_token=DJFHDJSF5445SDSD
String[] all = result.split("&");
for (String s : all) {
if(s.indexOf("=") > 0){
token = s.substring(s.indexOf("=") + 1);
break;
}
}
System.out.println(result);
}
return token;
}
返回说明:
如果成功返回,即可在返回包中获取到Access Token。 如(不指定fmt时):
access_token=FE04**CCE2&expires_in=7776000&refresh_token=88E4**BE14
| 参数说明 | 描述 |
|---|---|
| access_token | 授权令牌,Access_Token。 |
| expires_in | 该access token的有效期,单位为秒。 |
| refresh_token | 在授权自动续期步骤中,获取新的Access_Token时需要提供的参数。注:refresh_token仅一次有效 |
错误码说明:
接口调用有错误时,会返回code和msg字段,以url参数对的形式返回,value部分会进行url编码(UTF-8)。
PC网站接入时,错误码详细信息请参见:100000-100031:PC网站接入时的公共返回码。
3.4 获取用户OpenID_OAuth2.0
请求地址
**
PC网站:https://graph.qq.com/oauth2.0/me
请求方法
**
GET
请求参数
**
请求参数请包含如下内容:
| 参数 | 是否必须 | 含义 |
|---|---|---|
| access_token | 必须 | 在Step1中获取到的access token。 |
| fmt | 可选 | 因历史原因,默认是jsonpb格式,如果填写json,则返回json格式 |
/**
* 如果用户成功登录并授权,则会跳转到指定的回调地址,
* 并在redirect_uri地址后带上Authorization Code和原始的state值。
* @return
*/
@RequestMapping(value = "/qq/callback")
public String callback(HttpServletRequest request, HttpServletResponse response,Model model){
//创建HttpSession对象
HttpSession session = request.getSession();
//获取session中的code,也就是存入的uuid
String uuid = (String) session.getAttribute("state");
//获取code参数
String code = request.getParameter("code");
//获取state参数
String state = request.getParameter("state");
//判单获取的state参数和session中uuid值是否一样
if(state != null){
if(!uuid.equals(state)){
throw new RuntimeException("Q获取的state参数和session中uuid值不一样");
}
}
//通过Authorization Code获取Access Token
String url = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code" +
"&client_id=" + QQutils.APP_ID +
"&client_secret=" + QQutils.APP_KEY +
"&code=" + code +
"&redirect_uri=" + QQutils.REDIRECT_URL;
//获取token
String token = null;
try {
QQutils.getAccessToken(url);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("token = " + token);
//获取用户OpenID_OAuth2.0
url = "https://graph.qq.com/oauth2.0/me?access_token=" + token;
//获取openId
String openId = null;
try {
openId = QQutils.getOpenId(url);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("openId = " + openId);
TODO 后面还有代码...
}
调用getOpenId(url)返回openId_
/**
* 根据token,得到对应用户身份的OpenID。
*/
public static String getOpenId(String url) throws IOException{
String openId = null;
//创建client对象
CloseableHttpClient client = HttpClients.createDefault();
//创建HttpGet请求
HttpGet httpGet = new HttpGet(url);
//执行execute方法,返回response
HttpResponse response = client.execute(httpGet);
//获取响应体
HttpEntity entity = response.getEntity();
//判断响应体是否为空
if(entity != null){
String result = EntityUtils.toString(entity, "UTF-8");
//返回一串json字符串
//({"cliend_id":"1056365","openId":"4687FASDAS61ER"})
//调用工具类,将json字符串转换成json对象
JSONObject ob = JSONUtils.castToJson(result);
openId = ob.getString("openid");
System.out.println(result);
}
return openId;
}
返回说明
**
PC网站接入时,获取到用户OpenID,返回包如下(如果fmt参数未指定):
callback( {“client_id”:”YOUR_APPID”,”openid”:”YOUR_OPENID”} );
openid是此网站上唯一对应用户身份的标识,网站可将此ID进行存储便于用户下次登录时辨识其身份,或将其与用户在网站上的原有账号进行绑定。
错误码说明
接口调用有错误时,会返回code和msg字段,以url参数对的形式返回,value部分会进行url编码(UTF-8)。
PC网站接入时,错误码详细信息请参见:100000-100031:PC网站接入时的公共返回码。
3.5 根据openId获取用户的信息
前提说明
1. 该appid已经开通了该OpenAPI的使用权限。
**
从API列表的接口列表中可以看到,有的接口是完全开放的,有的接口则需要提前提交申请,以获取访问权限。
2. 准备访问的资源是用户授权可访问的。
**
网站调用该OpenAPI读写某个openid(用户)的信息时,必须是该用户已经对你的appid进行了该OpenAPI的授权(例如用户已经设置了相册不对外公开,则网站是无法读取照片信息的)。
用户可以进入手机QQ->设置->隐私->授权管理,进行访问权限管理。
3. 已经成功获取到Access Token,并且Access Token在有效期内。
**
调用OpenAPI接口
QQ登录提供了用户信息等OpenAPI(详见API列表),网站需要将请求发送到某个具体的OpenAPI接口,以访问或修改用户数据。
调用所有OpenAPI时,除了各接口私有的参数外,所有OpenAPI都需要传入基于OAuth2.0协议的通用参数:
**
| 参数 | 含义 |
|---|---|
| access_token | 可通过使用Authorization_Code获取Access_Token 或来获取。 access_token有3个月有效期。 |
| oauth_consumer_key | 申请QQ登录成功后,分配给应用的appid |
| openid | 用户的ID,与QQ号码一一对应。 可通过调用https://graph.qq.com/oauth2.0/me?access_token=YOUR_ACCESS_TOKEN 来获取。 |
示例
1. 以get_user_info接口为例:
(请将access_token,appid等参数值替换为你自己的)
https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID
2. 成功返回后,即可获取到用户数据:
{
"ret":0,
"msg":"",
"nickname":"YOUR_NICK_NAME",
...
}
3.下一步
**
获取用户的Access Token和OpenID,之后通过调用OpenAPI进行获取用户信息等操作。
/**
* 如果用户成功登录并授权,则会跳转到指定的回调地址,
* 并在redirect_uri地址后带上Authorization Code和原始的state值。
* @return
*/
@RequestMapping(value = "/qq/callback")
public String callback(HttpServletRequest request, HttpServletResponse response,Model model){
//创建HttpSession对象
HttpSession session = request.getSession();
//获取session中的code,也就是存入的uuid
String uuid = (String) session.getAttribute("state");
//获取code参数
String code = request.getParameter("code");
//获取state参数
String state = request.getParameter("state");
//判单获取的state参数和session中uuid值是否一样
if(state != null){
if(!uuid.equals(state)){
throw new RuntimeException("Q获取的state参数和session中uuid值不一样");
}
}
//通过Authorization Code获取Access Token
String url = "https://graph.qq.com/oauth2.0/token?grant_type=authorization_code" +
"&client_id=" + QQutils.APP_ID +
"&client_secret=" + QQutils.APP_KEY +
"&code=" + code +
"&redirect_uri=" + QQutils.REDIRECT_URL;
//获取token
String token = null;
try {
QQutils.getAccessToken(url);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("token = " + token);
//获取用户OpenID_OAuth2.0
url = "https://graph.qq.com/oauth2.0/me?access_token=" + token;
//获取openId
String openId = null;
try {
openId = QQutils.getOpenId(url);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("openId = " + openId);
//根据openId获取用户的信息,因为openId是唯一的
url = "https://graph.qq.com/user/get_user_info?access_token=" + token +
"&oauth_consumer_key=" + QQutils.APP_ID +
"&openid=" + openId;
JSONObject object = null;
try {
object = QQutils.getUserInfo(url);
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("object = " + object);
//nickname 用户的昵称
String nickname = object.getString("nickname");
session.setAttribute("loginUser",nickname);
session.setAttribute("time",new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
return "main";
}
调用getUserInfo(url)返回用户的信息
/**
* 根据openId获取用户的信息,因为openId是唯一的
*/
public static JSONObject getUserInfo(String url) throws IOException{
JSONObject object = null;
//创建client对象
CloseableHttpClient client = HttpClients.createDefault();
//创建HttpGet请求
HttpGet httpGet = new HttpGet(url);
//执行execute方法,返回response
HttpResponse response = client.execute(httpGet);
//获取响应体
HttpEntity entity = response.getEntity();
//判断响应体是否为空
if(entity != null){
String result = EntityUtils.toString(entity, "UTF-8");
object = JSONObject.parseObject(result);
System.out.println(result);
}
return object;
}
3.6 测试

3.7 码云地址:https://gitee.com/Program_Monkey/send-email.git

