引言

在一个网站或许运用程序中,登录注册功用的重要性显而易见。从原来繁琐的一般登录手机验证码登录邮箱登录到现在精约的QQ联合登录微信联合登录微博登录以及国外的FacebookTwitter等多种方登录方式。

第三方交际账号来登录,十分的便利,w 4 w提高了用户, t i K a 0 .的体会度,无需注册,便能够直接登录。再也不用为记不住暗码而找回暗码。

本篇文章主要共享QQ联合登录,期望能够真真切切的协助到你,助推一波又一波优异后浪们更好的前进。

OAuth2.0协议

QQ联合登陆

提到第三方登录,– q D现在一定离不开OAu r V Jth2.0规范协议的支持。咱们来看一下相关概念:

OAuth: OAuth(敞开授权)是一个敞开规范,允许用户授权第三方网站访问他们存储在另外的服务供给者上的信息,而不需要将用户名和暗码供给给第三方网站或共享他们数据的所有内容。

Q; o 8Q登录OAuth2.0:对于用户相关6 [ b * B 7 v Q F的OpenAPI(例如获取用户信息&. y * hlt;昵称,头像>,动态同步,相片H & F G # % ~ u @,日志,共享等),为了保护用户数据的安全和隐私,第三方网@ 9 = 0 T f ] O站访问用户数据前都需要显式的向用户征求授权。

QQ登录OAuth2.0采用OAuth2.0规范协议来进行用户身份验证和获取用户授权,相对于之前@ w ^的OAuth1.0协议,其认证流程更简略和安全。

留意:因为OAuth 1.0协议体系本身存在一些问题,现已被各大开发渠道逐渐抛弃@ A m Z g d = b

QQ互联 :fallen_leaf:

QQ互联: connect.qq.ch ; ` Som/

接入QQ登录前,网站需首先在QQ互联进行申请,获得对应的appid与appkey,以确保后续流程中可正确对网站与用户进行验证与授权。

QQ互联敞开渠道为第三方移动运用; f j ) Q b c K供给了丰富的API。第三方移动运用接入QQ互联敞开渠道后,即可经过调用渠道供给的API完成用户运用QQ账号登录移动运用功用,且能够获取到腾讯QQ用户的相关信息。

第三方移动运用也能够调用腾讯方供给的API完成移动运用的共享、评论、约请等功用,即移动运用的交际化功用。且能够将相关信息同步到QQ空间、腾讯朋友、腾讯微博等渠道,建立移动运用与腾讯各渠道的互动关系,运用庞大的A E [ X – H F rQQ用户群8 K k来完成移动运用的快速传达。

1.运用QQ账户登录QQ互联,填写对应接入材料完成审阅。

2.点击导航栏运用管理,能k % * N ? k 2 k r够看到开发者审阅状态。

QQ联合登陆

3.创建网站或许移动运用,填写相应的信息,提交审阅v x ` + H

  • 挑选合适寓意的域名,进行网站存案(请提前准备,初审时间d 1 C # y相对较长)

  • 网站称号与存案网站称号填写共同。

  • 规范填写网站域名网站回调域网站= O / 2 O存案号(网站回调域开发中会运用)

  • 网站已经成功布置到服务器,且有QQ登录按钮,否则审阅不经过

    QQ联合登陆
  • 依照要求填写完毕后,等待审阅经过,既能够正常运用appid与appkey。

    QQ联合登陆

代码实战

QQ登录0 9 @ = o f # ) ^OAuth2.0支持网站接入和移动运用接入,咱们以网站运用为场景,Java语言为基础实操。

SDK下载

不知道什么原因官网下架了Java版别的SDK只要PHP和JS版别的SDK,所以小编也共享一个Java版别的SDK。

Ja– . a d x wva版别:qzonestyle.gtimg.cn/qzone/vas/o…

Maven依4 i i *

<dependency&gD e J u Y K R %t;
&c V ! s l . olt;groupId>ne_ ? Z i Zt.gplatform</groupId>
<artifactId>Sdk4J</artifactId>
<version>2.0</version>
</dependency>

配置文件

app_ID = QQ互联运用 APP ID
app_Q O Q } } ~ j f ~KEY = QQ互联运用 APP Key
redirect_URI = QQ互联运用 网站回调域地址
scope = get_user_info,add_~ N g ^ R Ftopic,add_oq Q X I ! !ne_blog,addi b 4 $ *_album,upload_pic,list_album,add_share,check_page_fans,add_t,add_pic_t,del_t,get_repost_B , elist,get_info,gev A 9 6 H Y x mt_other_info,get_fanslist,get_idollist,addF , S l Q ! %_iU & j x ( $dol,del_ido,get_tenpay1 3 j c ( O 0 i (_addr(请修正此处)
base% h : ;URL = 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
getOf 3 / M penIDURL = https://graph.qq.com/oauth2.0/me
addTopicURL = https:4 v b j ,//graph.qq.com/shuoshuo/add_topic
ad+ ] | W C Q mdBlog5 7 T 9 K )URL = https://graph.qq.com/blog/add_oneH s t } s h I_blog
addAlbumURL = htA n ; Itps://graph.qq.com/photo/add_album
uploadPicURL = https://graph.qq.com/ph) l 2 * | / 0oto/upload_pic
listAlbumURL = https://graph.qq.com/photo/list_album
addShareURL = https://graph.qq.com/share/add_share
checkP/ w ( 1 x B M b :ageFansURL = https://graph.qq.com. D c D 3 p s/user/check_page_fans
addTURL = https://graph.qq.com/t/add_t
addPicTURL = https= 1 / 7 8 o _://graph.qq.com/t/add_pic_t
delTURL = https://graph.q i ! Z 0 i 5 l eq.com/t/del_t
getWeiboUserInfoURL = https://graph.qq.com/user/get_info
getWI y oeiboOtherUserInfoURL = https://graph.qq.cx : ! v u 4 W % 9om/user/get_other_info
getFb y * h : 0ansListURL = https://graph.qq.com/relation/get_fanslist
getIdols8 t t 9 A NListURL = https://graph.qq.com/relatione ) R r p F h -/getz  X @ ~_idollist
addIdolURL = https://graph.qq.com/relation/add_idol
delIdolURL = https://graph.qq.comA x 2 . S =/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.HttpServletRequ % o - @est;
import ja/ N C q l e [ O kvax.servlet.ht/ I M J B  V ?tp.HttpServletRe g P 1 C 7 d T isponse;
import java.C 6 ) p 2 & - pio.IOException;
import com.qq.connect.QQCo& N ;nnectException;
import com.qq.connect.oauth.Oauth[ ` ( V _ );
public class IndexServlet extends HttpServlet {
protected void dP = u @ x poGet(HttpServletRequest request, HttpServletResponse responsev X ^ , |) throws In R @OException {
response.setContentType("text/html;charset=utf-8");//设置编码格式为utf-8
try {
response.sendRedirect(new Oauth().getAuthorizeURL(request));
} catch (QQConnectException eI . 0 5 d 9 u E m) {
e.printStackTrace();
}
}
protected void doPost(HttpServletReq; s E * y Euest request,v C 9 * G m t t HttpServletResponse response) throws IOExceptio- / X t q * (n {
doGet(request,  responsen ! , f # g s e);
}
}

AfterLoginRedirectServlet

package com.qq.connect.demo;
import com.qq.connect.QQConnectEK ? = B G 2 lxception;
import com.qq.connect.apie D [ n n V d : Z.OpenID;
import com.qq.connect.api.qzone.PageFans;
impL 0 { o {ort com.qq.connect.api.qzone.UserInfo;
imr U # T A k 8port com.qq.connect.javabeans.AccessToken;
imw g F O s jport com.qq.connect.javabeans.qG M ^ c c N 6zone.PageFansBean;
import com.qq.connect.javabeans.qzone.UserInfoBean;
import com.qq.connect.javabeans.weibo.Company;
import com.qq.conn` G Eect.oauth.Oauth;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServlet8 k ] C d . x B MRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
public class AfterLoginRedirectServlet extends HttpServle6 N C e ft {
protected void doGet(HttpServletReD [ ? 7 V  ) Gquest request, HttpServletResponsey e Q X ! response) throws IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse reb F 3 7 - , K vsponse) throws IOExceptiu m q q 5 o # j con {
response.setContentType("text/html; charset=utf-8");
Prik E M @ntWriter out = response.getWriter();
try {
AccessToken accessTokenObj = (new Oauth()).getAccessTokenByRequest(request);
String accessToken   = null,
openID        = null;
long tokenExpireIn = 0L;
if (accessTokenObj.getAccessToken().equals("")) {
//                咱们F 8 n / w [ = W的网站被CSRF进犯了或许用户取消了授权
//                做一些数据统计作业
Syst: ; - c &em.out.priY 3 j Ent("没有获取到呼应参数");
} else {
accessToken = accessTokenObj.getAccessToken();
tokenExpiret g p U &In = accessTokenObj.getExpireIn();
request.getSession().setAttri: B E Y . 0 : k Obute("demo_access_tokT 6 b V Uen", accessToken);
request.getSession().setAtu C S 4tr( m R A Xibute("demo_token_expireinK 5 p t i", String.valueOf(tokenExpireIn));
// 运用获取到的accessToken 去获取当时用的openid -------- start
OpenID openIDObj =  new OpenID(accessTokenF D 8 d r C 0 4 m);
openID = openIDObj.getUserOpenID();
out{ b 5 g L { Z.println("欢迎你,代号为 " + openID + " 的用户!");
request.getQ # ^ 8Sessio~ ! T L an().setAttribute("demo_openid", openID);
out.println("<a href="https://juejinb r M O _ v C o ?.im/post/5f0d95256fb9a07ebe239a1f/ + "/shuoshu- ~ ` * @ g YoDemo.html" +  " target=\"_blank\">去看看发表说说的demo% k s T $ o吧</a>");
// 运用获取到的accessToken 去获取当时用户的openid --------- end
out.printZ q v e f U P cln("<p> sta` W g Urt -----------------------------------运用获取到的accesst T O ` 4Token,openid 去获取用户在Qzone的昵称等信息2 p / . D | h j Q ---------------------------- start </p>");
UserInfo qzoneUserInfo = new UserInfo(accessToken, openID);
UserInfoBean userInfoBean = qzoneUserInfo.getUa ` H [ #serInfo();
out.priW P Q { 8 R @ 6ntln("<br/>");
if (userInfoBean.getRet() == 0) {
out.println(userInfoBean.getNickn& _ 1 3 [ u { = Came() + "<br/>");
out.println(userInfoBean.getGender() + "<br ` N e + r %/&~ i 4 4gt;");
out.println("黄钻等级: " + userInfoBean.getLevel() + "<br/>");
out.println("会员 : " + userInfoBean.isVip() + "<br/>");
out.println("黄钻会员: " + use8 : / : i l W S ?rInfoBean.isYellowYearVip() + "<&  w b ) @ k # ~br/>"D | - + q o);
out.println("<image src="httpsu m ` z z 5 ) w ?://juejin.imf e [/post/5f0d95256fb9a07ebe239a1f/ + userInfb @  K ] g ( s 3oBean.getAvatar().getAvatarURL30()v - , G & + "/>&lc N jt;br/>");
out.println("<image src="https://juejin.im/post/5f0d95256fb9a07ebe239a1f. V _ J T/ + usk ~ [ K ] ( NerInfoBeaR 8 l X 7 w Yn.getA3 8 G p j ( Ivatar()W & 7 # i C } G 6.g* G ? - {etAvatarURL50() + "/><br/>");
out.println("<image src="http q D j 1 y 1 d .s://juejin.im/post/5f0d95256fb9a07ebe239a1f/ + userInfoBean.getAvatar().getAvatarURL100() + "/><br/>");
} else {
out.println("很抱愧,咱们没能正确获取到您的信息,原因是: " + userInfoBean.getMsg());
}
out.println("<p> end ---2 E 6 l 5 q---------------------------$ ^ & _ ? : = ) 3-----运用获取到的accessToken,openw A Xid 去获取用户在Qzone的昵称等信息 ---------------------------- end </p>");
out.printlnB / R d("<p> start ----------------------------------- 验证当时用户是否为认证空间的粉丝--------------------------------_ ! %---------------- start <p>");
P` / 4 - #ageFans pageFansObj = new PF } s N ? 5 pageFans(accessToken1 G 4 e, openID);
PageFansBean pageFansBea_  7 / Nn =/ z P [ N P I D pageFansObjk % .  W v J ` /.checkPageFah l = Ins("97700000");
if (pageFansBean.getRZ r n = C 6et(X @ p N K = 9 G) == 0) {
out.println("<p>验证您" + (pag, 7 - % U + 5 g JeFansBean.isFans()? / ; N d ? * B ? "是" : "* $ ) N H T不是")  + "QQ空间97700000官方认证空间的粉丝&q 6 K +lt;/p>");
}8 @ ~ w P c else {
out.println("很抱愧,咱们没能正确获取到您的信息,原因是: " + pageFansBean.getMsg());
}
out.println("&lk ` , Ht;p> end ---------------------------H . s |-------- 验证当时用户是否为认证空间的粉丝------------------------------------------------ end <p>");
out.println("&lo * A 0 qt;p> start -------, ; _ ----: = 3 / s M x x------------------------运用获取到的accessToken,openid 去获取用户在微博的昵称等信息 ---------------------------- start </p>");Z : x h z z .
com.qq.connect.api.weibo.UserII 8 1 l Y ^nfo weiboUserIn% U u ( g ` V Wfo = new com.qq.connect.api.weibo.UserInfo(ap M accessTokez z A { cn, openID);
com.qq.connect.javabeans.weibo.Ug s k 3 6 = oserInfo( 3 / O { J H J mBean weiboUserInfoBean = w6 T k 6e# c O I T biboUserInfo.h 8 # n ) 2getUserInfo();
if (weiboUserInfoBean.ge] B E = WtRet() == 0) {
//获取用户的微博头像----------------------start
out.printe u : Q o 6 ? Oln("<image src="https://juejin.im/post/5| * 8 W O [ Tf0d95256fb9a07ebe239t [ # ` K ! L ? pa1f/ + weiboUserInfoBean.getAvatar().getAvatarURL30() + "/><br/>");
out.printlnO q 3 B : z _ h %("<image src="https://juejin.im/po* d Yst/5f0d95256fb9a07s V * x ~ webe239a1f/ + weib^ ( n ! [ z &oUserInfoBean.getAvatar().getAvatarURL50() + "/><br/>");u H t
out.println("&Q c 3 S blt;image src="https://juejin.im/post/5f0d95256fb9a07ebe239a1# , Mf/ + weiboL N I u g , J TUserInfoP ! 7 + : X l . EBean.getAvatar().getAvatarURL100() + "[ W { o & g/><br/>J s q");
//获取用户的微博头像 ---------------------end
//获取用X 5 Q R [户的生日信息 --------------------start
out.println("<p>敬重的用户,你的生日是: " + weiboUsU l v merInfoBean.getBirthday().getYear()
+  "年" + weiboUserInfoBeaU ( b v  S C R Vn.getBiY ! ]rthdayu 9 4 O - !().getMonth() + "月" +
weiboUserInfoBean.getBirthday().getDay() + "日");
//获取用户的生日信息 --------------------end
StringBuffer sb = new StringBuffer();
sb.append("<p>所在地:4 S 1 6 Rw p . v G N m I" + weiboUserInfoBean.getCountryCode() + "-" + weiboUserInfoBean.getProvinceCode() + "-" + weiboUserInfoBeaH 1 ~ B ? p yn.getCityCode()
+ weiboUserInfoBean.getLocation());
//获u t ] ) t取用户的公司信息---------------------------start
Array 2  rLis A Ht<Company> companies~ ] k ) b O 3 ; J = weiboUserInfoBean.getCo8 5 _ &mpanies();
if (companies.W  7 u s 9 6size() > 0) {
//有公司信息
for (int i=0, j=companies.size(); i<j; i++) {
sb.append("<p>曾执役过的公司:公司ID-" + companies.get(i).getID() + " 称号-" +
companies.get(i).getCompanyName() + " 部门称号-" + companies.geh Q Pt(i).getDepartmentName() + " 开端作业年-" +
companies.get(i)0 M i.getBeginYeaW J 8 @r() + " 结束作业年-" + companies.get(i).getEndYear());
}
} else {
//没有公司信息
}
//获取用户的公司信息-------------------r O ,--------end
out.println(sb.toString())o h ? * @;
} el; d z S L & 0 } Qse {
out.println("很抱愧,f + { ` n Q j咱们没能正确. W m ;获取到您的信息,原; a E ! h + [ m因是: " + weiboUserInfoBean.getMsg());
}
out.println("<p> end -----------------------------------运用获取到的accF  | } M | s y gessToken,opey # ` ]nid 去获取用户在微博的昵称等信息 ---------------------------- end </p>");
}
} catch (QQConnectExcep u M Q 0 G p X ,tion e) {
}
}
}

ShuoShuoServlet

package com.qq.connect.demo;
import ce Z s S (om.qq.connect.QQConnectException;
import com.qq.connect.api.qzY 9 fone.Topic;
import com.qq.connect.java4 L m Y 1 ` $beans.GeneralResultBean;
import javax.servlet.http.HttpSeA N  . ] [  / Lrvlet;
import javax.servlet.http.HttpServletRequest;
im9 : ^ ) #port javax.servlet.http.HttpSb C &ervletResponse& p 4 A ^ O J 5 ;;
import javaT J w e ) I ox.servlet.http.HttpSession;
import java.i[ 3 co.IOException;T @ = 7 # + 8 ` p
public class ShuoShuoServlet extends HttpServlet {
@Override
protec) S P ntY 9 med void doGet(HttpServletRequest request, HttpServletResponse res* A e 3 z nponse) throE : u 8 @ r 5ws IOException{
response.setContentType("text/html;charset=utf-8");
request.setCharacterEncode U S ] wing("utf-8");
String con = request.getParameteV = d s $ | Sr("con");
HttpSession session = request.getSession();
String accessToken = (String)session.getAttribute("demo_access_token");
String opep z 9 d vnID = (String)session.getAttribute("demo_openia $ l ( ud");
System.j y k ( V j Tout.println(accessToken);
System.out.println(openID);
//请开发者自行校验获取的con值! D / f D - X是否有效
if (con != "") {
Tou L rpic topic = new Topic(accessToken, openID);
try {
GeneralResultBean grb = topic.addTopic(con);
if (grb.getRet() == 0) {
response.getWriter().pr% ? 8 N s E !intln("<a href=\"http://www.qzone.com\" target=\"_blank\">您的说说已发表r ~  [ #  w成功,请登录Qzone检查</a>");
} else {
response.getWriter().println("很遗憾的通知您,发表说说失利!原因: " + grb.getMsg());
}
} catch (QQConnectException e) {
Syz L s ! {stem.out.println("抛反常了?");
}
} else {
System.out.println("获取到的值为空?");
}
}
@Override
protected v^ 2 ( q % y 7 Y Moid doPost(HttpSerb K CvletRequest request, HttpServletResponse response) throws IOEb w H O | - z p pxception {
doGet(request, rn ~ esponse);
}
}

实战分析

数据库] & . u – ( s设计

`QQ_OPV Q f z G T w JENID` VARCHAR(50) DEFAUL- X 2 * ZT 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(= j 2 = 0 - Erequest);
String accessToken = accessTokenObj.getAcV U G T : z ~ces? C G O ysk  ~ ? [ YToken();

相关参数:

参数 是否有C – B } [必要 含义
response_type 有必要 授权类型,此值固定为“code”。
client_id 有必要 申请QQ登录成功后,分配给运用的appid。
redirect_uri 有必要 E A D {功授权后的回调地址,有必要是注册appid时填写的主域名下的地址,主张设置为网站主页Z = o ^ & [ E或网站的用户中心。留意需要将url进行URLEncode。
state 有必要 client端的状态值。用于第三方运用防止CSRF进q + @ D O [犯,成功授权后回调时会原样带回。请必须严厉依照流程检查用户与state参数状态的绑定。
scope 可选 恳求用户授权时向用户显示的可进C { p t s 5 K 3 p行授权的列表。[ u V Y L 可填写的值是API文档中列出的接口,以及一些动作型的授权(现在仅有:do_like),如果要填写多个接口称号,请用逗号隔开。 例如:scope=get_user_info,list_album,upload_pF 5 c ^ I O K k cic,do_like 不传Y h E则默许恳求对接口get_user_info进行授权。 主张操控授权项的数量,只传入必要的接口称号,因为授权项越多,用户越或许拒绝进行任何授权。
display 可选 PC网站接入时运用。 用于展现的款式。不传则默许展现为PC下的款式。 如果传入“mobile”,则展现为mobile端下的A k = ( % V款式。

获取用户OpenID

OpenID是此网站上或运用中唯一对运用户身份的标识,网站或运用可将此ID进行存储,便于用户下1 ~ W次登. g 4 ) T录时辨识其身份,或将其与用户在网站上l 3 Z或运用中的原有账号进行绑定。

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_} 2 V u xAPa S 0 GP_ID&aj r t k U dmp;openid=YOUR_OPENID
QQ联合登陆