java实现第三方qq登录(流程详解)

829 阅读3分钟
本文主要讲解系统支持第三方网页qq登录的流程以及注意的事项,与大家进行探讨,共同进步。

准备工作

首先需要大家在QQ互联平台申请开发者账号以及提交网站应用审核信息。

如下截图所示:填写如下信息

此处没有什么特殊要求,大家正常填写即可,本人猜测是人工审核,所以建议大家在工作日申请,时间很快,早上提交,下午就可以审批通过。

QQ个人开发者账号申请完成以后才可申请网站的应用,需要填写的信息如下截图:

此处需要注意自己的网站名称以及网站的备案号必须与工信部的一致,网站的图标切记不要随便找一个,否则很可能审核不通过的。

准备开发

大家可在QQ互联平台Wiki可查看相关流程,以下是代码逻辑:

graph.qq.com/oauth2.0/sh…

以上该地址在前端进行链接跳转,建议回调地址为你的网站地址比如登录页或者首页,这样在qq授权成功后该地址会增加参数信息,此时可根据该参数调用后端的接口:

数据库设计,增加用户授权表,这样可适配多个第三方登录不需要增加新的字段:

CREATE TABLE `user_auths` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `user_id` bigint NOT NULL COMMENT '用户Id',
  `identity_type` varchar(5) DEFAULT NULL COMMENT '登录类型第三方应用名称(微信 微博等):qq/weChat',
  `identifier_id` varchar(64) DEFAULT NULL COMMENT '标识Id(微信、qq用户的唯一标识,比如unionid)',
  `credential` varchar(64) DEFAULT NULL COMMENT '凭证(访问微信、qq的token)',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=100 DEFAULT CHARSET=utf8 COMMENT='用户授权信息表';
// 以下为后端接口的核心逻辑,该代码为java语言:

public ObjectResponse userLoginByQQ(String code, String state) {
	// 申请的AppId,AppKey,RedirectUri建议做成可配置的
        String qqAppId = configBeanValue.getQqAppId();
        String qqAppKey = configBeanValue.getQqAppKey();
        String qqAppRedirectUri = configBeanValue.getQqAppRedirectUri();

        // 根据授权code获取token
        String getTokenUrl = "https://graph.qq.com/oauth2.0/token?fmt=json&grant_type=authorization_code&client_id=" + qqAppId + "&client_secret=" + qqAppKey + "&redirect_uri=" + qqAppRedirectUri + "&code=" + code;
        log.info("userLoginByQQ getTokenUrl:{}", getTokenUrl);
        // 此处采用spring boot封装的HttpClient
        Map tokenMap = restTemplate.getForObject(getTokenUrl, Map.class);
        log.info("userLoginByQQ getToken result:{}", JSON.toJSONString(tokenMap));
        if (!tokenMap.containsKey("access_token")) {
            throw new MyException("授权失败,请重试!");
        }
        String accessToken = tokenMap.get("access_token").toString();

        // 根据token获取用户openid
        String getOpenIdUrl = "https://graph.qq.com/oauth2.0/me?fmt=json&access_token=" + accessToken;
        log.info("userLoginByQQ getOpenIdUrl:{}", getOpenIdUrl);
        Map openMap = restTemplate.getForObject(getOpenIdUrl, Map.class);
        log.info("userLoginByQQ getOpenIdUrl result:{}", JSON.toJSONString(openMap));
        if (!openMap.containsKey("openid")) {
            throw new MyException("授权失败,请重试!");
        }
        String openId = openMap.get("openid").toString();

        // 根据openid获取unionid、同一用户,对同一个QQ互联平台下的不同应用,unionID是相同的。所以开发者拥有多个移动应用、网站应用,可通过获取用户的unionID来区分用户的唯一性
        String getUnionIdUrl = "https://graph.qq.com/oauth2.0/me?fmt=json&unionid=1&access_token=" + accessToken;
        log.info("userLoginByQQ getUnionIdUrl:{}", getUnionIdUrl);
        Map unionMap = restTemplate.getForObject(getUnionIdUrl, Map.class);
        log.info("userLoginByQQ getUnionIdUrl result:{}", JSON.toJSONString(unionMap));
        if (!unionMap.containsKey("unionid")) {
            throw new MyException("授权失败,请重试!");
        }
        String unionId = unionMap.get("unionid").toString();

        // 该代码为伪代码,即根据QQ唯一标识和类型查询的该账号是否存在
        List<UserAuths> userAuths = userAuthsMapper.selectByUserAuths(unionId,"qq");
        // 此时没有该账号为注册
        if (CollUtil.isEmpty(userAuths)) {
            // 获取qq用户名称
            String getUserInfoUrl = "https://graph.qq.com/user/get_user_info?access_token=" + accessToken + "&oauth_consumer_key=" + qqAppId + "&openid=" + openId;
            log.info("userLoginByQQ getUserInfoUrl:{}", getUserInfoUrl);
            Map userMap = restTemplate.getForObject(getUserInfoUrl, Map.class);
            log.info("userLoginByQQ getUserInfoUrl result:{}", JSON.toJSONString(userMap));
            if (!userMap.containsKey("nickname")) {
                throw new MyException("授权失败,请重试!");
            }
            String nickName = userMap.get("nickname").toString();

            // 该代码为伪代码,没有注册过的账号,将unionId和token存储,在用户登录或者注册的时候进行存储记录
            cacheManager.set("qq"+unionId,accessToken);

            Map<String, String> resultMap = new HashMap<>();
            resultMap.put("nickName", nickName);
            resultMap.put("identifierId", unionId);
            resultMap.put("identityType", CommonEnums.UserAuthsIdentityTypeEnums.QQ.getCode());
            return new ObjectResponse("账号未注册!").data(resultMap);
        }
	// 该代码为伪代码,以下逻辑则进行登录信息的逻辑处理
        ResultUserInfoVo resultUserInfoVo = userLogin();
        
        return new ObjectResponse().rel(true).data(resultUserInfoVo);
    }

以上代码有一点注意的是我在获取账号的openid后又获取了unionid,此处考虑多个应用的情况会存在统一qq用户的openid不一样的情况。

还有一点就是大家谈论的在获取到了账号的唯一标识后还需要存储access_token吗?或者说存储有什么意义? 本人理解 该access_token在初始化获取到用户openId后,如果后期不需要获取其他信息的话,该Token对使用方来说存储无意义;但是对于授权方来说,你来拿用户的数据肯定需要凭证,肯定是需要的。

完善工作

上述交互对于已关联的用户可直接通过第三方登录,对于未注册的为了使用户体验更好,可以增加交互,比如检测到第三方账号未注册,则让用户选择注册新用户还是登录已有账户,测试通过注册的新账号或者登录的用户与该第三方账号做关联的记录。