如何从零开始对接第三方登录(Java版):QQ登录和微博登录

1 前言

个人网站最近增加了评论功能,为了方便用户不用注册就可以评论,对接了 QQ 和微博这 2 大常用软件的一键登录,总的来说其实都挺简单的,可能会有一点小坑,但不算多,完整记录下来方便后来人快速对接。

2 后台设计

在真正开始对接之前,我们先来聊一聊后台的方案设计。既然是对接第三方登录,那就免不了如何将用户信息保存。首先需要明确一点的是,用户在第三方登录成功之后,我们能拿到的仅仅是一个代表用户唯一身份的ID(微博是真实uid,QQ是加密的openId)以及用来识别身份的accessToken,当然还有昵称、头像、性别等有限资料,对接第三方登录的关键就是如何确定用户是合法登录,如果确定这次登录的和上次登录的是同一个人并且不是假冒的。

其实这个并不用我们特别操心,就以微博登录为例,用户登录成功之后会回调一个code 给我们,然后我们再拿code去微博那换取accessToken,如果这个code是用户乱填的,那这一关肯定过不了,所以,前面的担心有点多余。

另外一个问题就是如何和现有用户系统打通,有的网站在用户已经登录成功之后还要用户输入手机号和验证码,或者要用户重新注册账号和密码来绑定第三方账户,感觉这种实现用户体验非常差,碰到这种网站我一般都是直接关掉,都已经登录了还让用户注册,什么鬼!由于我做的是评论功能,我并不希望评论用户和现有用户表打通,所以就不存在这件事了,如果想打通的话,我觉得无非就是登录成功之后默认往老用户表插入一条数据,然后和OpenUser表关联起来,判断用户是否登录时把OpenUser的鉴权也加进去就OK了。

本文的后台以Java为例。

2.1 数据库设计

再来说说数据库设计,为了系统的扩展性,我有一个专门的OpenUser表用来存放第三方登录用户,主要字段如下:

这样设计理论上就可以无限扩展了。

2.2 鉴权流程

这里我只是说说我的方案,把accessToken写入cookie肯定是不安全的,因为accessToken相当于是第三方网站的临时密码,被别人窃取了就可以随意拿来干坏事了。可以在用户登录成功之后我们自己生成一个token,这样的token即使泄露了顶多就是被人拿来随意评论,损失不大,但是如果accessToken被泄露了,以微博为例,人家可以利用这个accessToken随意发微博、删微博、加关注等等,很危险。当然,如果不想token泄露的话也可以通过绑定IP等方式来限制。

鉴权的话就是首先判断cookie中是否有我们自己的token,然后判断是否合法,合法再判断第三方授权是否已过期等等。

3 QQ登录

3.1 实名认证

QQ登录我们对接的是QQ互联,地址:https://connect.qq.com ,首先需要注册成为开发者并实名认证,需要手持身份证照片,具体就不讲了。

3.2 创建应用

进入应用管理页面(https://connect.qq.com/manage.html#/)创建应用,根据实际需要是创建网站应用还是移动应用,我这里是网站应用:

第一步:

第二步:

提交完之后会自动提交审核,基本上就是审核你的资料和备案的资料是否一致,所有资料必须和备案资料一模一样,否则审核不会通过:

当然,这些资料后面还是可以修改的。申请成功之后你会得到appId和appKey。

3.3. 引导用户登录

这里可以下载一些视觉素材,在页面合适位置放一个QQ登录按钮,点击时引导用户进入授权页面:

代码:

functionopenWindow(url, width, height){width = width ||600;height = height ||400;varleft = (window.screen.width - width) /2;vartop = (window.screen.height - height) /2;window.open(url,"_blank","toolbar=yes, location=yes, directories=no, status=no, menubar=yes, scrollbars=yes, resizable=no, copyhistory=yes, left="+left+", top="+top+", width="+width+", height="+height);}functionqqLogin(){varqqAppId ='424323422';// 上面申请得到的appidvarqqAuthPath ='http://www.test.com/auth';// 前面设置的回调地址varstate ='fjdslfjsdlkfd';// 防止CSRF攻击的随机参数,必传,登录成功之后会回传,最好后台自己生成然后校验合法性openWindow(`https://graph.qq.com/oauth2.0/authorize?response_type=token&client_id=${qqAppId}&redirect_uri=${encodeURIComponent(qqAuthPath)}&state=${state}`);}

然后会打开一个授权页面,这个页面大家应该都熟悉:

need-to-insert-img

然后到了这里我就碰到一个问题了,官方文档(https://wiki.connect.qq.com)写的是登录成功之后首先会回传一个code,然后再拿code调接口换取accessToken,然后我试了很多次也换过2个账号发现每次都是直接返回了accessToken,帮我省了一步了,不知道是什么情况,郁闷。微信搜索 Web项目聚集地 获取更多实战教程。

3.4 拿到accessToken

现在假设我们都是直接拿到accessToken(因为我暂时还没搞明白QQ为啥会直接返回,跟文档说的不一样),但是授权回调时accessToken会被放在 # 后面,URL地址中的hash值好像不会被传到后台(貌似是这样,如有不正确欢迎评论指正),所以只能写一个下面这样的临时页面: 

@RequestMapping("/authqq")public void authQQ(HttpServletRequest request, HttpServletResponse response) throws Exception{    // QQ登录有点特殊,参数放在#后面,后台无法获取#后面的参数,只能用JS做中间转换String html =  "<!DOCTYPE html>" +"" +"" +"QQ登录重定向页" +"" +"" +"" +"" +                    "  location.href = location.href.replace('#', '&').replace('auth_qq', 'auth_qq_redirect');" +                    "  " +"" +"";    response.getWriter().print(html);}

3.5 获取openId 

根据accessToken调接口获取用户的openId,特别注意这个openId是相对于QQ号+appId唯一的,换句话说同一个QQ号登录2个不同appId时获取到的openId是不同的。顺便说一句,QQ登录的相关接口做的还真够“随便”的,全部都是最简单的get请求,所以对接起来非常顺利。 微信搜索 Web项目聚集地 获取更多实战教程。

直接看代码:

// 根据accessToken换取openId// 错误示例:callback( {"error":100016,"error_description":"access token check failed"} );// 正确示例:callback( {"client_id":"10XXXXX49","openid":"CF2XXXXXXXX9F4C"} );Stringresult = HttpsUtil.get("https://graph.qq.com/oauth2.0/me?access_token="+ accessToken);Map resp = parseQQAuthResponse(result);// 这个方法就是把结果转Map// 欢迎关注 Web项目聚集地 获取更多实战教程Integer errorCode = (Integer)resp.get("error");StringerrorMsg = (String)resp.get("error_description");StringopenId = (String)resp.get("openid");if(errorCode !=null)returnnewErrorResult(errorCode,"获取QQ用户openId失败:"+errorMsg);

3.6 获取用户头像昵称等信息

// 获取用户昵称、头像等信息,{ret: 0, msg: '', nickname: '', ...} ret不为0表示失败result = HttpsUtil.get("https://graph.qq.com/user/get_user_info?access_token="+accessToken+"&oauth_consumer_key="+appId+"&openid="+openId);resp = JsonUtil.parseJsonToMap(result);// 欢迎关注 Web项目聚集地 获取更多实战教程Integer ret = (Integer)resp.get("ret");Stringmsg = (String)resp.get("msg");if(ret !=0)returnnewErrorResult("获取用户QQ信息失败:"+msg);// 用户昵称可能存在4个字节的utf-8字符,MySQL默认不支持,直接插入会报错,所以过滤掉Stringnickname = StringUtil.filterUtf8Mb4((String)resp.get("nickname")).trim();// 这个方法可以自行百度// figureurl_qq_2=QQ的100*100头像,figureurl_2=QQ 100&100空间头像,QQ头像不一定有,空间头像一定有Stringavatar = (String)resp.get("figureurl_qq_2");if(StringUtil.isBlank(avatar)) avatar = (String)resp.get("figureurl_2");Stringgender = (String)resp.get("gender");

3.7 注意事项

到了这一步基本上涉及第三方的就结束了,是不是很简单?后面无非就是如何插入数据库、如何保存token、写入session等。

有几点注意事项:

需要注意数据库中是否已经有改用户,没有的添加,有的修改,不要重复添加了;QQ昵称昵称有各种奇奇怪怪的字符,包括emoji,MySQL默认没有开启utf8mb4,直接插入会报错,所以需要过滤掉;需要做好对各种错误的兼容;接口会同时返回QQ头像和空间头像,QQ头像不一定有,空间头像一定有;回调地址必须和申请的域名一致,否则会报错。QQ互联有个特大的bug,有时候显示已登录但是点击授权管理一直报错,此时只需要退出重新登录即可;授权之后用户可能会在过期之前提前取消授权;微信搜索 Web项目聚集地 获取更多实战教程。

相关文档官网已经写得比较细了,但是比较乱:http://wiki.connect.qq.com/

4 对接微博登录

4.1 实名认证

这个我就不具体讲了,登录 http://open.weibo.com/ 很容易找到相关入口,注册成为开发者,实名认证,一模一样的。

4.2 创建应用

点击链接 

http://open.weibo.com/apps/new?sort=web 

创建web应用:

need-to-insert-img

创建成果后完善相关信息,主要是下面这些:

need-to-insert-img

我就不一一介绍了,都看得懂。

微博登录不需要网站一定要备案,但对网站本身有一定要求,不能弄一个空壳网站让人家去审核,肯定审核不通过的。

有关微博的对接可以参考我好几年前写的一篇文章:

http://www.cnblogs.com/liuxianan/archive/2012/11/11/2765123.html

4.3 引导用户登录

微博视觉素材

(https://open.weibo.com/wiki/微博标识下载)

下载在这里,页面合适位置放一个登录按钮:

functionweiboLogin(){letweiboAppId ='432432';letweiboAuthPath ='http://www.test.com/authweibo';openWindow(`https://api.weibo.com/oauth2/authorize?client_id=${weiboAppId}&response_type=code&redirect_uri=${encodeURIComponent(weiboAuthPath)}`);}

微博登录有一个好处,第一次登录需要授权,后面第二次登录时只会一闪而过自动就登录成功了,都不需要点一下,用户体验非常好,看下图:

need-to-insert-img

4.4 获取accessToken

登录成功会返回一个code,根据code换取accessToken:

Stringparams ="client_id="+ appId+"&client_secret="+ appSecret+"&grant_type=authorization_code"+"&redirect_uri="+ URLUtil.encode(authPath)+"&code="+ code;// 用code换取accessTokenStringresult = HttpsUtil.post("https://api.weibo.com/oauth2/access_token", params);Map resp = JsonUtil.toObject(result,newTypeReference>(){});Integer errorCode = (Integer)resp.get("error_code");Stringerror = (String)resp.get("error");StringerrorMsg = (String)resp.get("error_description");if(errorCode !=null&& errorCode !=0)returnnewErrorResult(errorCode, error + (errorMsg==null?"":errorMsg));StringaccessToken = (String)resp.get("access_token");Stringuid = (String)resp.get("uid");// 这个uid就是微博用户的唯一用户ID,可以通过这个id直接访问到用户微博主页int expires = (Integer)resp.get("expires_in");// 有效期,单位秒

4.5. 获取用户头像等信息

// 用uid和accessToken换取用户信息Stringresult = HttpsUtil.get("https://api.weibo.com/2/users/show.json?access_token="+accessToken+"&uid="+uid);Map resp = JsonUtil.toObject(result,newTypeReference>(){});errorCode = (Integer)resp.get("error_code");error = (String)resp.get("error");errorMsg = (String)resp.get("error_description");if(errorCode !=null&& errorCode !=0)returnnewErrorResult(errorCode, error + (errorMsg==null?"":errorMsg));Stringnickname = (String)resp.get("screen_name");// 微博180*180高清头像Stringavatar = (String)resp.get("avatar_large");Stringgender = (String)resp.get("gender");gender ="m".equals(gender) ?"男": ("f".equals(gender) ?"女":"");

至此涉及第三方的东西都完了,剩下的就是用户自己保存到数据库、写入token

保存 session 以及鉴权接口开发了。

4.6 注意事项 

微博接口都有频率限制,不过一般不会超过;需做好错误兼容;微博直接返回的uid,可以根据这个uid直达用户微博主页 https://weibo.com/u/xxxxx ,所以可以把用户头像链接到这里;其实也有现成的js-sdk,可以根据自己实际需要选择是否使用;微博的接口是https,并且是post,需要注意;

5 相关链接

微博开放平台:open.weibo.com/微博登录授权机制:open.weibo.com/wiki/授权机制QQ互联:connect.qq.com/QQ授权管理页面:connect.qq.com/manage.html#/appauth/user


参考文章:https://mp.weixin.qq.com/s/m2-6kCIOJrxhhuSu-F3FeA

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容