QQ账号信任登录
概述
- QQ账号信任登录包含:PC网站应用登录和H5网站应用登录
- 每种登录方式都需要配置不同的应用ID和应用秘钥
- 总体逻辑为:发起登录 -> 用户授权 -> 获取用户信息 -> 根据用户信息进行登录
PC网站应用登录
逻辑概述
- 第三方应用(Javashop网站)发起QQ授权登录请求,QQ用户允许授权第三方应用(Javashop网站)后,微信会拉起应用或重定向到第三方网站(Javashop网站),并且带上授权临时票据code参数
- 通过code参数加上AppID和AppSecret等,通过API换取access_token
- 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作
详细逻辑可参考官方文档:网站应用接入概述
流程图

重点步骤说明:
步骤3:构建的授权URL格式如下:
https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=APPID&redirect_uri=REDIRECT_URI&state=STATEresponse_type:授权类型,此值固定为“code”
redirect_uri:成功授权后的回调地址,必须是注册appid时填写的主域名下的地址。注意需要将url进行URLEncode
state:client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。程序中设置的是uuid
步骤10-11:
调用的接口为通过code获取access_token接口,如下:
https://graph.qq.com/oauth2.0/token?grant_type=authorization_code&client_id=AppID&client_secret=AppSecret&code=AuthorizationCode&redirect_uri=REDIRECT_URI接口返回的数据格式如下:
默认是x-www-form-urlencoded格式
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仅一次有效步骤12-13:
调用的接口为获取用户openid接口,如下:
https://graph.qq.com/oauth2.0/me?access_token=AccessToken&fmt=json接口返回的数据格式如下:
{
"client_id":"YOUR_APPID",
"openid":"YOUR_OPENID"
}步骤14:根据openid查询是否已经绑定注册了会员信息(查询es_connect表,此表存放的是第三方账号和系统会员的关联信息),如果未查询到数据,需要调用会员注册业务接口注册一条会员数据
步骤15:会员登录后会创建会员token信息,由于是服务端进行的重定向跳转页面,因此跳转之前要将token信息存入cookie中方便前端读取
代码展示
Controller代码展示:
@Tag(name = "QQ统一登陆接口")
@RestController
@RequestMapping("/buyer/connect/qq")
public class LoginByQQController {
@Operation(summary = "PC发起信任登录")
@GetMapping("/pc/login")
public void pcLogin(){
//信任登录插件基类
AbstractConnectLoginPlugin connectionLogin = connectManager.getConnectionLogin(ConnectTypeEnum.QQ);
String loginUrl = connectionLogin.getLoginUrl(ClientTypeEnum.PC.name());
try {
ThreadContextHolder.getHttpResponse().sendRedirect(loginUrl);
} catch (IOException e) {
this.logger.error(e.getMessage(), e);
throw new ServiceException(MemberErrorCode.E131.name(), "联合登录失败");
}
}
}
构建授权登录URL代码展示:
@Component("qqConnectLoginPlugin")
public class QQConnectLoginPlugin extends AbstractConnectLoginPlugin {
/**
* 获取授权登录的url
*
* @param clientTypeEnum 客户端类型 {@link ClientTypeEnum}
* @return
*/
@Override
public String getLoginUrl(String clientTypeEnum) {
Map map = initConnectSetting(ThirdPlatformEnum.QQ, ClientTypeEnum.valueOf(clientTypeEnum));
String uuid = UUID.randomUUID().toString();
String callBack = this.getCallBackUrl(ConnectTypeEnum.QQ.value(), clientTypeEnum);
String url = "https://graph.qq.com/oauth2.0/authorize?" +
"response_type=code" +
"&client_id=" + map.get("app_id") +
"&redirect_uri=" + callBack +
"&state=" + uuid;
return url;
}
}
回调获取QQ用户openid代码展示:
@Component("qqConnectLoginPlugin")
public class QQConnectLoginPlugin extends AbstractConnectLoginPlugin {
/**
* 登录成功后的回调方法
*
* @param client 客户端类型 {@link ClientTypeEnum}
* @return
*/
@Override
public Auth2Token loginCallback(String client) {
debugger.log("进入QQConnectLoginPlugin回调");
//获取PC端QQ账号信任登录配置参数信息
Map map = initConnectSetting(ThirdPlatformEnum.QQ, ClientTypeEnum.valueOf(client));
HttpServletRequest request = ThreadContextHolder.getHttpRequest();
//通过Authorization Code获取Access Token
String code = request.getParameter("code");
String redirectUri = this.getCallBackUrl(ConnectTypeEnum.QQ.value(), client);
//获取accessToken信息
String url = "https://graph.qq.com/oauth2.0/token?" +
"grant_type=authorization_code" +
"&client_id=" + map.get("app_id") +
"&client_secret=" + map.get("app_secret") +
"&code=" + code +
"&redirect_uri=" + redirectUri;
String content = HttpUtils.doGet(url, "UTF-8", 1000, 1000);
String accessToken = "";
Matcher matcher = accessTokenPattern.matcher(content);
while (matcher.find()) {
accessToken = matcher.group(1);
}
//获取QQ用户的openid
url = "https://graph.qq.com/oauth2.0/me?access_token=" + accessToken+"&unionid=1&fmt=json";
content = HttpUtils.doGet(url, "UTF-8", 1000, 1000);
String unionId = "";
Matcher unionIdMatcher = unionidPattern.matcher(content);
while (unionIdMatcher.find()) {
unionId = unionIdMatcher.group(1);
}
String openid = "";
Matcher openidMatcher = openidPattern.matcher(content);
while (openidMatcher.find()) {
openid = openidMatcher.group(1);
}
Auth2Token auth2Token = new Auth2Token();
auth2Token.setUnionid(unionId);
auth2Token.setOpneId(openid);
auth2Token.setAccessToken(accessToken);
return auth2Token;
}
}
H5网站应用登录
逻辑概述
- 前端引用腾讯提供的JS SDK的JavaScript文件,此文件封装了QQ登录流程与API列表中的所有OpenAPI调用方法
- 通过SDK中封装的方法拉取授权登录页面,用户确认跳转至回调页面后,获取access_token
- 通过access_token进行接口调用,获取用户基本数据资源或帮助用户实现基本操作
具体逻辑可参考官方文档:网站应用接入概述
流程图

重点步骤说明:
步骤5:由前端调用SDK中的方法发起授权登录请求,如下:
QC.Login.showPopup({
appId: that.qq_app_id,
redirectURI: that.redirect_uri
})appId是通过步骤2中调用API获取的
redirectURI是由前端定义的页面地址,用户确认授权登录后会跳转到该地址
步骤8:在步骤5跳转值回调页面时,回调地址上会携带access_token
步骤10-11:
调用的接口为获取用户openid接口,如下:
https://graph.qq.com/oauth2.0/me?access_token=AccessToken&fmt=json接口返回的数据格式如下:
{
"client_id":"YOUR_APPID",
"openid":"YOUR_OPENID"
}步骤12-13:
调用的接口为获取QQ用户信息接口,如下:
https://graph.qq.com/user/get_user_info?
access_token=AccessToken&oauth_consumer_key=AppID&openid=OpenId&format=json接口返回的数据格式如下:
{
"ret":0,
"msg":"",
"nickname":"Peter",
"figureurl":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/30",
"figureurl_1":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/50",
"figureurl_2":"http://qzapp.qlogo.cn/qzapp/111111/942FEA70050EEAFBD4DCE2C1FC775E56/100",
"figureurl_qq_1":"http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/40",
"figureurl_qq_2":"http://q.qlogo.cn/qqapp/100312990/DE1931D5330620DBD07FB4A5422917B6/100",
"gender":"男"
}返回参数说明:

代码展示
Controller代码展示:
@Tag(name = "QQ统一登陆接口")
@RestController
@RequestMapping("/buyer/connect/qq")
public class LoginByQQController {
@Operation(summary = "获取appid")
@GetMapping("/h5/getAppid")
public String getAppId(){
return loginQQManager.getAppid();
}
@Operation(summary = "H5登陆")
@GetMapping("/h5/login")
public Map h5Login(String access_token, String uuid){
return loginQQManager.qqWapLogin(access_token,uuid);
}
}
业务接口代码展示:
@Service
public class LoginQQManagerImpl implements LoginQQManager {
/**
* 获取H5端QQ信息登录参数appid
* @return
*/
@Override
public String getAppid() {
Map<String, String> map = this.thirdPlatformAppManager.getConfigOnConnect(ThirdPlatformEnum.QQ, ClientTypeEnum.H5);
return map.get("app_id");
}
/**
* H5端QQ账号信任登录
*
* @param accessToken 接口调用凭证
* @param uuid 本次请求唯一标识
* @return
*/
@Override
public Map qqWapLogin(String accessToken, String uuid) {
LoginUserDTO loginUserDTO = new LoginUserDTO();
loginUserDTO = getUnionInfo(loginUserDTO,accessToken);
loginUserDTO.setUuid(uuid);
loginUserDTO.setTokenOutTime(null);
loginUserDTO.setRefreshTokenOutTime(null);
loginUserDTO.setOpenType(ConnectTypeEnum.QQ_OPENID);
loginUserDTO.setUnionType(ConnectTypeEnum.QQ);
loginUserDTO = getQQUserInfo(loginUserDTO,accessToken);
return loginManager.loginByUnionId(loginUserDTO);
}
/**
* 获取用户openid信息
*
* @param loginUserDTO 登录用户信息
* @param accessToken 接口调用凭证
* @return
*/
private LoginUserDTO getUnionInfo(LoginUserDTO loginUserDTO,String accessToken){
StringBuffer unionIdBuffer = new StringBuffer("https://graph.qq.com/oauth2.0/me?");
unionIdBuffer.append("access_token=").append(accessToken);
unionIdBuffer.append("&unionid=1&fmt=json");
String retJson = HttpUtils.doGet(unionIdBuffer.toString(), "UTF-8", 1000, 1000);
if (retJson.indexOf("unionid")==-1){
throw new ServiceException("403","fail to getById unionid",retJson);
}
JSONObject jsonObject = JSONObject.fromObject(retJson);
loginUserDTO.setUnionid(jsonObject.getString("unionid"));
loginUserDTO.setOpenid(jsonObject.getString("openid"));
return loginUserDTO;
}
/**
* 获取QQ用户信息
*
* @param loginUserDTO 登录用户信息
* @param accessToken 接口调用凭证
* @return
*/
private LoginUserDTO getQQUserInfo(LoginUserDTO loginUserDTO, String accessToken){
Map<String, String> map = this.thirdPlatformAppManager.getConfigOnConnect(ThirdPlatformEnum.QQ, ClientTypeEnum.H5);
StringBuffer userBuffer = new StringBuffer("https://graph.qq.com/user/get_user_info?");
userBuffer.append("access_token=").append(accessToken);
userBuffer.append("&openid=").append(loginUserDTO.getOpenid());
userBuffer.append("&oauth_consumer_key=").append(map.get("app_id"));
userBuffer.append("&format=json");
String retJson = HttpUtils.doGet(userBuffer.toString(), "UTF-8", 1000, 1000);
JSONObject jsonObject = JSONObject.fromObject(retJson);
if (jsonObject.getInt("ret")!=0){
throw new ServiceException("403","获取用户信息失败",retJson);
}
loginUserDTO.setHeadimgurl(jsonObject.getString("figureurl_qq"));
loginUserDTO.setNickName(jsonObject.getString("nickname"));
if ("男".equals(jsonObject.getString("gender"))){
loginUserDTO.setSex(1);
}else{
loginUserDTO.setSex(0);
}
loginUserDTO.setProvince(jsonObject.getString("province"));
loginUserDTO.setCity(jsonObject.getString("city"));
return loginUserDTO;
}
}