引言 🏂
在一个网站或者应用程序中,登录注册功能的重要性不言而喻。从原来繁琐的普通登录
、手机验证码登录
、邮箱登录
到现在简约的QQ联合登录
、微信联合登录
、微博登录
以及国外的Facebook
、Twitter
等多种方登录方式。
第三方社交账号来登录,十分的方便,提高了用户的体验度,无需注册,便可以直接登录。再也不用为记不住密码而找回密码。
本篇文章主要分享QQ联合登录
,希望可以真真切切的帮助到你,助推一波又一波优秀后浪们更好的前进。
OAuth2.0协议 📜
说到第三方登录,现在一定离不开OAuth2.0标准协议的支持。我们来看一下相关概念:
OAuth: OAuth(开放授权)是一个开放标准,允许用户授权第三方网站访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方网站或分享他们数据的所有内容。
QQ登录OAuth2.0:对于用户相关的OpenAPI(例如获取用户信息<昵称,头像>,动态同步,照片,日志,分享等),为了保护用户数据的安全和隐私,第三方网站访问用户数据前都需要显式的向用户征求授权。
QQ登录OAuth2.0采用OAuth2.0标准协议来进行用户身份验证和获取用户授权,相对于之前的OAuth1.0协议,其认证流程更简单和安全。
注意:由于OAuth 1.0协议体系本身存在一些问题,现已被各大开发平台逐渐废弃。
QQ互联 🍂
QQ互联: https://connect.qq.com/
接入QQ登录前,网站需首先在QQ互联进行申请,获得对应的appid与appkey,以保证后续流程中可正确对网站与用户进行验证与授权。
QQ互联开放平台为第三方移动应用提供了丰富的API。第三方移动应用接入QQ互联开放平台后,即可通过调用平台提供的API实现用户使用QQ账号登录移动应用功能,且可以获取到腾讯QQ用户的相关信息。
第三方移动应用也可以调用腾讯方提供的API实现移动应用的分享、评论、邀请等功能,即移动应用的社交化功能。且可以将相关信息同步到QQ空间、腾讯朋友、腾讯微博等平台,建立移动应用与腾讯各平台的互动关系,利用庞大的QQ用户群来实现移动应用的快速传播。
1.使用QQ账户登录QQ互联,填写对应接入资料完成审核。
2.点击导航栏应用管理,可以看到开发者审核状态。
3.创建网站或者移动应用,填写相应的信息,提交审核。
-
选择合适寓意的域名,进行网站备案(请提前准备,初审时间相对较长)
-
网站名称与备案网站名称填写一致。
-
规范填写
网站域名
、网站回调域
、网站备案号
(网站回调域开发中会使用) -
网站已经成功部署到服务器,且有QQ登录按钮,否则审核不通过
-
按照要求填写完毕后,等待审核通过,既可以正常使用appid与appkey。
代码实战 🚀
QQ登录OAuth2.0支持网站接入和移动应用接入,我们以网站应用为场景,Java语言为基础实操。
SDK下载
不知道什么原因官网下架了Java版本的SDK只有PHP和JS版本的SDK,所以小编也分享一个Java版本的SDK。
Java版本:http://qzonestyle.gtimg.cn/qzone/vas/opensns/res/doc/qqConnect_Server_SDK_java_v2.0.zip
Maven依赖
<dependency><groupId>net.gplatform</groupId><artifactId>Sdk4J</artifactId><version>2.0</version></dependency>
配置文件
app_ID = QQ互联应用 APP ID
app_KEY = QQ互联应用 APP Key
redirect_URI = QQ互联应用 网站回调域地址
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
自带案例
有兴趣的可以看一看
IndexServlet
package com.qq.connect.demo;import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;import com.qq.connect.QQConnectException;
import com.qq.connect.oauth.Oauth;public class IndexServlet extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {response.setContentType("text/html;charset=utf-8");//设置编码格式为utf-8try {response.sendRedirect(new Oauth().getAuthorizeURL(request));} catch (QQConnectException e) {e.printStackTrace();}}protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {doGet(request, response);}
}
AfterLoginRedirectServlet
package com.qq.connect.demo;import com.qq.connect.QQConnectException;
import com.qq.connect.api.OpenID;
import com.qq.connect.api.qzone.PageFans;
import com.qq.connect.api.qzone.UserInfo;
import com.qq.connect.javabeans.AccessToken;
import com.qq.connect.javabeans.qzone.PageFansBean;
import com.qq.connect.javabeans.qzone.UserInfoBean;
import com.qq.connect.javabeans.weibo.Company;
import com.qq.connect.oauth.Oauth;import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;public class AfterLoginRedirectServlet extends HttpServlet {protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {doPost(request, response);}protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {response.setContentType("text/html; charset=utf-8");PrintWriter out = response.getWriter();try {AccessToken accessTokenObj = (new Oauth()).getAccessTokenByRequest(request);String accessToken = null,openID = null;long tokenExpireIn = 0L;if (accessTokenObj.getAccessToken().equals("")) {
// 我们的网站被CSRF攻击了或者用户取消了授权
// 做一些数据统计工作System.out.print("没有获取到响应参数");} else {accessToken = accessTokenObj.getAccessToken();tokenExpireIn = accessTokenObj.getExpireIn();request.getSession().setAttribute("demo_access_token", accessToken);request.getSession().setAttribute("demo_token_expirein", String.valueOf(tokenExpireIn));// 利用获取到的accessToken 去获取当前用的openid -------- startOpenID openIDObj = new OpenID(accessToken);openID = openIDObj.getUserOpenID();out.println("欢迎你,代号为 " + openID + " 的用户!");request.getSession().setAttribute("demo_openid", openID);out.println("<a href=" + "/shuoshuoDemo.html" + " target=\"_blank\">去看看发表说说的demo吧</a>");// 利用获取到的accessToken 去获取当前用户的openid --------- endout.println("<p> start -----------------------------------利用获取到的accessToken,openid 去获取用户在Qzone的昵称等信息 ---------------------------- start </p>");UserInfo qzoneUserInfo = new UserInfo(accessToken, openID);UserInfoBean userInfoBean = qzoneUserInfo.getUserInfo();out.println("<br/>");if (userInfoBean.getRet() == 0) {out.println(userInfoBean.getNickname() + "<br/>");out.println(userInfoBean.getGender() + "<br/>");out.println("黄钻等级: " + userInfoBean.getLevel() + "<br/>");out.println("会员 : " + userInfoBean.isVip() + "<br/>");out.println("黄钻会员: " + userInfoBean.isYellowYearVip() + "<br/>");out.println("<image src=" + userInfoBean.getAvatar().getAvatarURL30() + "/><br/>");out.println("<image src=" + userInfoBean.getAvatar().getAvatarURL50() + "/><br/>");out.println("<image src=" + userInfoBean.getAvatar().getAvatarURL100() + "/><br/>");} else {out.println("很抱歉,我们没能正确获取到您的信息,原因是: " + userInfoBean.getMsg());}out.println("<p> end -----------------------------------利用获取到的accessToken,openid 去获取用户在Qzone的昵称等信息 ---------------------------- end </p>");out.println("<p> start ----------------------------------- 验证当前用户是否为认证空间的粉丝------------------------------------------------ start <p>");PageFans pageFansObj = new PageFans(accessToken, openID);PageFansBean pageFansBean = pageFansObj.checkPageFans("97700000");if (pageFansBean.getRet() == 0) {out.println("<p>验证您" + (pageFansBean.isFans() ? "是" : "不是") + "QQ空间97700000官方认证空间的粉丝</p>");} else {out.println("很抱歉,我们没能正确获取到您的信息,原因是: " + pageFansBean.getMsg());}out.println("<p> end ----------------------------------- 验证当前用户是否为认证空间的粉丝------------------------------------------------ end <p>");out.println("<p> start -----------------------------------利用获取到的accessToken,openid 去获取用户在微博的昵称等信息 ---------------------------- start </p>");com.qq.connect.api.weibo.UserInfo weiboUserInfo = new com.qq.connect.api.weibo.UserInfo(accessToken, openID);com.qq.connect.javabeans.weibo.UserInfoBean weiboUserInfoBean = weiboUserInfo.getUserInfo();if (weiboUserInfoBean.getRet() == 0) {//获取用户的微博头像----------------------startout.println("<image src=" + weiboUserInfoBean.getAvatar().getAvatarURL30() + "/><br/>");out.println("<image src=" + weiboUserInfoBean.getAvatar().getAvatarURL50() + "/><br/>");out.println("<image src=" + weiboUserInfoBean.getAvatar().getAvatarURL100() + "/><br/>");//获取用户的微博头像 ---------------------end//获取用户的生日信息 --------------------startout.println("<p>尊敬的用户,你的生日是: " + weiboUserInfoBean.getBirthday().getYear()+ "年" + weiboUserInfoBean.getBirthday().getMonth() + "月" +weiboUserInfoBean.getBirthday().getDay() + "日");//获取用户的生日信息 --------------------endStringBuffer sb = new StringBuffer();sb.append("<p>所在地:" + weiboUserInfoBean.getCountryCode() + "-" + weiboUserInfoBean.getProvinceCode() + "-" + weiboUserInfoBean.getCityCode()+ weiboUserInfoBean.getLocation());//获取用户的公司信息---------------------------startArrayList<Company> companies = weiboUserInfoBean.getCompanies();if (companies.size() > 0) {//有公司信息for (int i=0, j=companies.size(); i<j; i++) {sb.append("<p>曾服役过的公司:公司ID-" + companies.get(i).getID() + " 名称-" +companies.get(i).getCompanyName() + " 部门名称-" + companies.get(i).getDepartmentName() + " 开始工作年-" +companies.get(i).getBeginYear() + " 结束工作年-" + companies.get(i).getEndYear());}} else {//没有公司信息}//获取用户的公司信息---------------------------endout.println(sb.toString());} else {out.println("很抱歉,我们没能正确获取到您的信息,原因是: " + weiboUserInfoBean.getMsg());}out.println("<p> end -----------------------------------利用获取到的accessToken,openid 去获取用户在微博的昵称等信息 ---------------------------- end </p>");}} catch (QQConnectException e) {}}
}
ShuoShuoServlet
package com.qq.connect.demo;import com.qq.connect.QQConnectException;
import com.qq.connect.api.qzone.Topic;
import com.qq.connect.javabeans.GeneralResultBean;import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;public class ShuoShuoServlet extends HttpServlet {@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException{response.setContentType("text/html;charset=utf-8");request.setCharacterEncoding("utf-8");String con = request.getParameter("con");HttpSession session = request.getSession();String accessToken = (String)session.getAttribute("demo_access_token");String openID = (String)session.getAttribute("demo_openid");System.out.println(accessToken);System.out.println(openID);//请开发者自行校验获取的con值是否有效if (con != "") {Topic topic = new Topic(accessToken, openID);try {GeneralResultBean grb = topic.addTopic(con);if (grb.getRet() == 0) {response.getWriter().println("<a href=\"http://www.qzone.com\" target=\"_blank\">您的说说已发表成功,请登录Qzone查看</a>");} else {response.getWriter().println("很遗憾的通知您,发表说说失败!原因: " + grb.getMsg());}} catch (QQConnectException e) {System.out.println("抛异常了?");}} else {System.out.println("获取到的值为空?");}}@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {doGet(request, response);}
}
实战分析
数据库设计
`QQ_OPENID` VARCHAR(50) DEFAULT NULL COMMENT 'QQ联合登陆id',
获取Access_Token
https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=[YOUR_APPID]&redirect_uri=[YOUR_REDIRECT_URI]&state=[THE_STATE]
AccessToken accessTokenObj = (new Oauth()).getAccessTokenByRequest(request);
String accessToken = accessTokenObj.getAccessToken();
相关参数:
参数 | 是否必须 | 含义 |
---|---|---|
response_type | 必须 | 授权类型,此值固定为“code”。 |
client_id | 必须 | 申请QQ登录成功后,分配给应用的appid。 |
redirect_uri | 必须 | 成功授权后的回调地址,必须是注册appid时填写的主域名下的地址,建议设置为网站首页或网站的用户中心。注意需要将url进行URLEncode。 |
state | 必须 | client端的状态值。用于第三方应用防止CSRF攻击,成功授权后回调时会原样带回。请务必严格按照流程检查用户与state参数状态的绑定。 |
scope | 可选 | 请求用户授权时向用户显示的可进行授权的列表。 可填写的值是API文档中列出的接口,以及一些动作型的授权(目前仅有:do_like),如果要填写多个接口名称,请用逗号隔开。 例如:scope=get_user_info,list_album,upload_pic,do_like 不传则默认请求对接口get_user_info进行授权。 建议控制授权项的数量,只传入必要的接口名称,因为授权项越多,用户越可能拒绝进行任何授权。 |
display | 可选 | 仅PC网站接入时使用。 用于展示的样式。不传则默认展示为PC下的样式。 如果传入“mobile”,则展示为mobile端下的样式。 |
获取用户OpenID
OpenID是此网站上或应用中唯一对应用户身份的标识,网站或应用可将此ID进行存储,便于用户下次登录时辨识其身份,或将其与用户在网站上或应用中的原有账号进行绑定。
OpenID openIDObj = new OpenID(accessToken);
String openId = openIDObj.getUserOpenID();
调用API获取用户信息
根据OpenID进行相关的操作
https://graph.qq.com/user/get_user_info?access_token=YOUR_ACCESS_TOKEN&oauth_consumer_key=YOUR_APP_ID&openid=YOUR_OPENID