QQ第三方登陆实现

最近由于项目需要,我用到了QQ的网站和移动端的第三方登陆,这里我只着重介绍网站的。

一、QQ第三方登陆申请历程

如果你和我的需求相同,同时需要移动端和网站的应用,请一定要按照以下方式申请,可以保证你网站和移动端的appId相同,此处我多次咨询客服,不得不吐槽下qq互联网站的垃圾,登陆反映慢而且几乎没有任何内容提示,出错也不好改,导致我多次申请,且审核很慢,以下是客服回复的关键内容:

如果同一个应用有网站和app都需要接入QQ登录功能,
请先到https://connect.qq.com/创建网站应用,
然后再到http://open.qq.com/创建移动应用。
创建移动应用的时候可以根据提示关联网站的appid,关联后两个应用的appid一致。

若同一个android应用需要接入IOS版本,需要先在管理中心里打开这个android应用,
然后从这个界面右上角的平台信息里选择 IOS应用 进行申请。
该操作可以保持申请的IOS应用与android应用的APP ID一致。
(反之若先申请的IOS应用,关联操作步骤也一样) 

网站应用部分应用信息填写参考说明:
网站名称:ICP备案信息中的网站名称(或含有备案名称关键词)
网站地址:ICP备案中的域名地址(或者含有相关主域名)
主办单位名称:ICP备案中的主办单位名称
网站备案号:ICP备案号 

请即将申请的朋友一定要参考以上的良言,而且安卓应用申请是需要软著(无奈到现在还没申请通过,目前iOS 和网站应用已经成功运行)。
上面的文档基本也介绍了申请的网站和申请的步骤,我就不再赘述了,反正比较繁琐,我只强调一点:qq互联上申请网站应用的时候需要先获取认证资质,然后再申请网站应用,填写基本信息和网站信息

注意:
1)网站信息里填写的回调地址要写成你的最终地址,这个我在后面会详细介绍;
2)审核通过后,系统是有测试环境的,但是当时初次申请就是用的正式环境的域名,害怕修改了qq互联上已经审核通过的网站信息后会导致再审核,影响线上的第三方登录,这里就并没在测试环境测试;
3)”网站应用”,我在qq开放平台里也看到有,不过具体是否可以都在上面申请我没有做尝试,当时开发项目紧,另一方面也不敢过多尝试(碰壁次数太多审核过程太长),我最终还是参考了客服的建议。有喜欢尝试的可以试下,如果成功,请务必在评论区告知下,先谢了!

贴出我申请通过的图片供参考下:
这里写图片描述
应用详情:
这里写图片描述
下面是开放平台的图片(安卓的目前还没申请通过 [尴尬]):
这里写图片描述
这里写图片描述

二、PC端开发

1.设计数据库

项目使用mysql数据库

-- ----------------------------
-- Table structure for qq_info
-- ----------------------------
DROP TABLE IF EXISTS `qq_info`;
CREATE TABLE `qq_info` (
  `openid` varchar(100) NOT NULL COMMENT 'openid',
  `nickname` varchar(100) DEFAULT NULL COMMENT 'QQ空间的昵称',
  `gender` varchar(2) DEFAULT NULL COMMENT '性别',
  `figureurl` varchar(255) DEFAULT NULL COMMENT 'QQ头像',
  `create_time` datetime DEFAULT NULL COMMENT '创建日期',
  `update_time` datetime DEFAULT NULL COMMENT '修改日期',
  PRIMARY KEY (`openid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Table structure for third_user
-- ----------------------------
DROP TABLE IF EXISTS `third_user`;
CREATE TABLE `third_user` (
  `user_id` varchar(50) NOT NULL COMMENT '主键id',
  `local_user_id` bigint(30) NOT NULL COMMENT '本地用户编号',
  `login_name` varchar(50) DEFAULT NULL COMMENT '登陆用户名/号码',
  `user_type` varchar(5) NOT NULL COMMENT '用户类型 QQ-QQ用户 WX-微信用户',
  `create_time` datetime DEFAULT NULL COMMENT '创建日期',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='第三方用户表';

2.互联接口调用

请先参考qq互联官方文档,虽然文档写的有些low不过还是有参考价值的。

1) 先引入qq互联所使用的jar包
这里我要着重说下:这个jar包里缺少qq登陆所使用的bean对象,虽然qq空间的那个bean对象(com.qq.connect.javabeans.qzone.UserInfoBean)可以使用但是里面缺少qq头像的字段(只有空间头像字段),如果使用此bean可以获取用户的openid和qq空间的头像,为此我花了些时间自己写了bean和调用类,加入到jar包里,同时还删除了里面的log4j的配置文件,保证日志打印地方和项目统一,我加入的两个类分别是:
com.qq.connect.api.BaseUserInfo 和 com.qq.connect.javabeans.BaseUserInfoBean
具体内容大家可下载jar包通过jd-gui工具查看

这里写图片描述

整理后的JAR包下载地址:qq_connect_api-1.3.jar
如果希望使用官方的JAR请下载:官方原版的javaSDK(点击下载)
如maven项目且有自己的私服,就可以将此jar包上传到私服里,供自己使用,上传方法自己问度娘吧;如果没有私服,可以加入本地仓库里,方法请 此博客的结尾 ,怎么加入jar包到本地仓库

2)qq互联接口调用介绍
项目的resource目录下增加qq互联的配在文件,名称为qqconnectconfig.properties,如果你有闲心修改jar包的源码,你可以改了这个配置文件名的名称

app_ID = *************
app_KEY = *******************************
redirect_URI = https://******************************/qqRedirectWebInf.do
scope = get_user_info
#,add_topic,add_one_blog,add_album,upload_pic,list_album,add_share,check_page_fans,add_t,add_pic_t,del_t,get_repost_list,get_info,get_other_info,get_fanslist,get_idollist,add_idol,del_ido,get_tenpay_addr
baseURL = https://graph.qq.com/
getUserInfoURL = https://graph.qq.com/user/get_user_info
accessTokenURL = https://graph.qq.com/oauth2.0/token
authorizeURL = https://graph.qq.com/oauth2.0/authorize
getOpenIDURL = https://graph.qq.com/oauth2.0/me
addTopicURL = https://graph.qq.com/shuoshuo/add_topic
addBlogURL = https://graph.qq.com/blog/add_one_blog
addAlbumURL = https://graph.qq.com/photo/add_album
uploadPicURL = https://graph.qq.com/photo/upload_pic
listAlbumURL = https://graph.qq.com/photo/list_album
addShareURL = https://graph.qq.com/share/add_share
checkPageFansURL = https://graph.qq.com/user/check_page_fans
addTURL = https://graph.qq.com/t/add_t
addPicTURL = https://graph.qq.com/t/add_pic_t
delTURL = https://graph.qq.com/t/del_t
getWeiboUserInfoURL = https://graph.qq.com/user/get_info
getWeiboOtherUserInfoURL = https://graph.qq.com/user/get_other_info
getFansListURL = https://graph.qq.com/relation/get_fanslist
getIdolsListURL = https://graph.qq.com/relation/get_idollist
addIdolURL = https://graph.qq.com/relation/add_idol
delIdolURL = https://graph.qq.com/relation/del_idol
getTenpayAddrURL = https://graph.qq.com/cft_info/get_tenpay_addr
getRepostListURL = https://graph.qq.com/t/get_repost_list
version = 2.0.0.0

你要改动的只有前三行,第一个是你申请通过的app_ID,第二个是app_KEY,第三个是回调地址,要跟你在qq互联上填写的一致,定位到某个http请求
贴下源码里的调用吧
这里写图片描述

3)项目中引用
啥也不说先上代码!
a. 生成authorizeURL:

try {
            //QQ第三方地址:
            String qqOauthUrl = new Oauth().getAuthorizeURL(request);
            mv.addObject("qOauthUrl", qqOauthUrl);
        } catch (QQConnectException e) {
            e.printStackTrace();
            log.error("QQ获取第三方登陆oauthUrl异常!");
        }

这里的request是HttpServletRequest,通过Oauth对象的getAuthorizeURL方法获取到authorizeURL,此链接在浏览器中点击是会调起qq的第三方登陆认证页面
如果用用户确定通过,qq互联会调起回调地址(qqRedirectWebInf.do),并带上生成的authorizationCode数据,
注意:此处使用new Oauth().getAuthorizeURL(request)方法时会在session加入一个名为qq_connect_state的值,用户通过request获取accessToken对象时会对此作不太严谨的验证
如果想更深入了解请反编译源码查看com.qq.connect.oauth.Oauth类

b. 前台页面展示qq图标,并加上链接:

<a href="javascript:gotoQQ();">
    <img src="${pageContext.request.contextPath}/common/images/login-qq.png" />
</a>
<script type="text/javascript">
    function gotoQQ(){
        var url = "${qOauthUrl}";
        window.location.href = url;
    }
</script>

如下图(这里其实我没有按照api的说明使用推荐的图标,因为太丑了):
这里写图片描述
如果喜欢这个图标的可以自己另存下 : 这里写图片描述

c. 服务端处理回调接口

   /**
     * 
     * @Description: QQ网页端第三方登陆回调接口
     * @author: tianpengw
     * @param req
     * @return
     */
    @RequestMapping("qqRedirectWebInf.do")
    public void qqRedirectWebInf(HttpServletRequest req,HttpServletResponse resp){
        try {
            log.info("进入QQ第三方回调,authorizationCode="+req.getParameter("code"));
            //分析源码,此对象可以通过多种方式获得,此处需采用上送HttpServletRequest对象,感兴趣的可以尝试使用其他方式
            AccessToken  accessToken = new Oauth().getAccessTokenByRequest(req);
            if(null != accessToken && !StringUtil.isEmpty(accessToken.getAccessToken())){
                log.info("根据accessToken获取用户openId,accessToken=" + accessToken.getAccessToken());
                String openId = new OpenID(accessToken.getAccessToken()).getUserOpenID();
                if(!StringUtil.isEmpty(openId)){
                    BaseUserInfoBean userInfo = new BaseUserInfo(accessToken.getAccessToken(),openId).getUserInfo();
                    log.info("userInfoBean:"+userInfo.toString());
                    QQUserInfo info0 = qqUserInfoService.selectByOpenId(openId);
                    //这里的意思是默认保存高分辨率的头像(100*100),如果此值为空,采用低分辨头像(40*40),api文档里说高分辨率头像不能保证必须有,但是低分辨头像是必须有的
                    String figureUrl = StringUtil.isEmpty(userInfo.getFigureurl_qq_2()) ? userInfo.getFigureurl_qq_1() : userInfo.getFigureurl_qq_2();
                    if(null != info0){
                        info0.setGender(userInfo.getGender());
                        info0.setNickname(userInfo.getNickname());
                        info0.setFigureurl(figureUrl);
                        info0.setUpdateTime(new Date());
                        qqUserInfoService.update(info0);
                    }else{
                        QQUserInfo info = new QQUserInfo();
                        info.setOpenid(openId);
                        info.setGender(userInfo.getGender());
                        info.setNickname(userInfo.getNickname());
                        info.setFigureurl(figureUrl);
                        info.setCreateTime(new Date());
                        qqUserInfoService.insert(info);
                    }
                    ThirdUser tu = thirdUserService.findBeanById(openId);
                    if(null != tu){
                        if(tu.getUserType().equals(Constants.user_type_qq) && !StringUtil.isEmpty(tu.getUserId())){
                            LocalUser u = userService.findUserById(tu.getLocalUserId());
                            if(null != u){
                                //shiro登陆
                                Subject sbj = SecurityUtils.getSubject();
                                CustomizedToken ct = new CustomizedToken(u.getUserLoginName(),u.getPassword(),"thirdLogin");
                                ct.setRememberMe(false);
                                sbj.login(ct);
                                if (sbj.isAuthenticated()) {        
                                    //增加登陆记录
                                    LocalUser user1 = new LocalUser();
                                    user1.setUserId(u.getUserId());
                                    user1.setLastLoginTime(new Date());
                                    int loginTimes = (u.getLoginTimes() == null? 0 : u.getLoginTimes()) + 1;
                                    user1.setLoginTimes(loginTimes);
                                    userService.updateByPrimaryKeySelective(user1);
                                    SessionUtil.addUserToSession(req, userService.findUserByLoginName(u.getUserLoginName()));
                                    resp.sendRedirect(Constants.serverDomain);
                                    log.info("PC端通过QQ登陆系统!userId="+tu.getLocalUserId());
                                    return;
                                }
                            }else{//根据第三方id,并未查询到本地用户信息
                                log.warn("未查询到关联本地表记录数据有误,清除关联表数据并进入关联界面!");
                                thirdUserService.delete(openId);
                                resp.sendRedirect(Constants.serverDomain + "/common/thirdUserConnect.do?thirdId="+openId+"&userType="+Constants.user_type_qq);
                                return;
                            }
                        }else{//查询的第三方表记录数据有误
                            log.warn("查询的第三方表记录数据有误,清除关联表数据并进入关联界面!");
                            thirdUserService.delete(openId);
                            resp.sendRedirect(Constants.serverDomain + "/common/thirdUserConnect.do?thirdId="+openId+"&userType="+Constants.user_type_qq);
                            return;
                        }
                    }else{//未查询到第三方记录,首次绑定
                        log.warn("首次绑定,进入关联界面!");
                        resp.sendRedirect(Constants.serverDomain + "/common/thirdUserConnect.do?thirdId="+openId+"&userType="+Constants.user_type_qq);
                        return;
                    }
                }else{
                    log.warn("获取openId为空,重新回到登陆界面!");
                    resp.sendRedirect(Constants.serverDomain + "/common/localLogin.do");
                    return;
                }
            }else{
                log.warn("获取accessToken为空,重新回到登陆界面!");
                resp.sendRedirect(Constants.serverDomain + "/common/localLogin.do");
                return;
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("PC端通过QQ登陆系统失败!返回登陆页面!");
            try {
                resp.sendRedirect(Constants.serverDomain + "/common/localLogin.do");
            } catch (IOException e1) {
            }
        }
    }

解释:
QQUserInfo :qq信息实体类,第一步里对应的qq_info表
qqUserInfoService:对用的service类,玩java的都懂得
StringUtil.isEmpty():字符串非空判断工具方法
ThirdUser:第三方登陆实体,第一步里对应的third_user表
thirdUserService:第三方登陆service类
LocalUser:本地用户实体类
userService:本地用户service类

说明:
生成授权地址并展示在页面中;
根据授权地址引导用户进行登陆授权,授权通过后qq那边会携带authorizationCode将请求重定向到回调地址上;
根据authorizationCode获取AccessToken对象,失败返回登陆界面,成功请看下一条;
根据token值获取用户基本信息,失败返回登陆界面,成功请看下一条;
新增或修改qq信息表数据,根据qq的openid查看第三方登陆表是否有此关联数据,如果没有进入本地用户关联页面,如果有请看下一条;
根据第三方登陆记录查到本地用户id并查看本地登录用户信息,如果未查询到说明是无效数据,清除此第三方关联记录并进入本地用户关联页面(重新关联),如果有请看下一条;
模拟shiro登陆,并更新用户最后登陆时间和等次数等数据,将用户信息存入session完成登陆跳转首页。

注:此方式可以实现多个qq绑定同一个本地用户,如果你需要限制,请做稍加修改限制即可。

到此为止,qq第三方登陆介绍完毕,如果错误或不明白的地方,请在评论区留言

发布了22 篇原创文章 · 获赞 15 · 访问量 3万+

猜你喜欢

转载自blog.csdn.net/niaoer2010/article/details/80183817