⭐前言
大家好,我是yma16,本文分享OAuth规则机制下实现个人站点接入qq三方登录。
oauth授权
OAuth是一种授权机制,用于允许用户(资源所有者)向第三方应用程序授予有限的访问权限,而不必将凭证直接提供给第三方应用程序。OAuth的目的是为了保护用户的私密数据,如社交媒体帐户、云存储、银行帐户等。它通过一个流程,将用户授权给第三方应用程序访问用户的资源,而不需要第三方应用程序获得用户的凭证信息。这样做可以减少用户数据泄露的风险。OAuth是一个开放的标准,由OAuth工作组维护,并得到许多组织的支持和使用。
oauth的发展
OAuth协议的发展历史可以追溯到2004年,当时美国国防部提出了一个名为“OpenID Connect”的开放式身份认证和授权标准,旨在解决Web 2.0中的身份认证和授权问题。OAuth1.0于2005年被RFC 5849正式标准化,OAuth2.0于2011年被RFC 6749正式标准化 。
OAuth1.0的主要特点是将用户的认证信息(如用户名和密码)与第三方应用的请求分离开来,从而提高了安全性。
OAuth2.0则在OAuth1.0基础上进一步改进,增加了更多的功能和灵活性,如授权码模式、隐式模式、密码模式等 。
效果
在个人站点实现三方qq登录
链接直达:https://yongma16.xyz
唤起三方登录url
获取qq用户账号头像和openid登入
⭐qq三方登录流程
前提条件: 存在可访问的网站,在qq互联中心创建应用审核
友情提示:网站不可使用外部cdn,可导致审核人员查看白屏而失败
💖qq互联中心创建网页应用
填写域名资料,提交审核
💖配置回调地址redirect_uri
回调地址是用户使用qq登录之后调整的地址会携带code和state的参数
💖流程分析
- 唤起qq授权登录url
- 登录qq成功获取code
- 通过code去换取access_token
- 通过access_token去换取openid
- 通过access_token和openid去换取userinfo
⭐思路分解
1.登录页面新开窗口的auth授权qq页面
2.自定义node服务去渲染回调redirect_uri,成功登录时回传url上的参数给父页面,然后用定时器关闭页面
3. 拿到code后去换取token
4. 拿到token去换取openid
5. 拿到openid去换取userinfo
6. 使用openid去注册网站用户,显示nickname网名
⭐技术选型+实现
💖技术选型:
后端:
node
前端:
vue2
后端node封装接口
💖实现
node封装接口:
api.js
const request = require('request'); const loginUrl='https://graph.qq.com/oauth2.0/authorize' const codeToTokenUrl='https://graph.qq.com/oauth2.0/token' const openIdUrl='https://graph.qq.com/oauth2.0/me' const userInfoUrl='https://graph.qq.com/user/get_user_info' const appid=自己的appid const appKey=自己的appkey const redirect_uri=自己的回调地址 const getAuthUrl=(state)=>{ return new Promise(resolve=>{ const params={ response_type:'code', client_id:appid, redirect_uri:encodeURI(redirect_uri), state:state?state:'myblog', }; const path=Object.keys(params).forEach(key=>{ return `${key}=${params[key]}` }).join('&') const url=loginUrl+'?'+path resolve(url) }) }; const getToken=(code)=>{ return new Promise(async ( resolve ,reject)=> { request( { method: 'GET' , uri: codeToTokenUrl, qs: { grant_type: 'authorization_code', client_id: appid, client_secret: appKey, code: code, redirect_uri: encodeURI(redirect_uri), fmt: 'json' } }, function (error, response) { if (!error && response.statusCode === 200) { resolve(response) } else { console.log("error",error); reject(reject) } }) } ) }; const getOpenId=(access_token)=>{ return new Promise(async ( resolve ,reject)=> { request( { method: 'GET' , uri: openIdUrl, qs: { access_token:access_token, fmt: 'json' } }, function (error, response) { if (!error && response.statusCode === 200) { resolve(response) } else { reject(error) } }) } ) }; const getUserInfo=(access_token,openId)=>{ return new Promise(async ( resolve ,reject)=> { request( { method: 'GET' , uri: userInfoUrl, qs: { access_token:access_token, oauth_consumer_key:appid, openid:openId, fmt: 'json' } }, function (error, response) { if (!error && response.statusCode === 200) { resolve(response) } else { reject(error) } }) } ) } module.exports={ getAuthUrl, getToken, getOpenId, getUserInfo };
node开放接口:
const hostname = 'localhost'; const port = 6677; const express = require("express"); const {getAuthUrl,getToken,getOpenId,getUserInfo}=require('./service/api.js'); const app = express(); app.listen(port,hostname, () => { console.log(`Server running at http://${hostname}:${port}/`); }); // server your css as static app.use(express.static(__dirname)); // views const thirdLoginDir='/views/thirdLogin/'; app.get("/getAuthUrl",async (req, res) => { try{ const {query}=req const {state}=query const url=await getAuthUrl(state) res.json({ code:authRes.statusCode, data:url, }) } catch (e) { res.json({ code:0, msg:e }) } }); app.get("/getToken",async (req, res) => { try{ const {query}=req const {code}=query console.log('code',code) const tokenRsponse=await getToken(code) res.json({ code:tokenRsponse.statusCode, data:JSON.parse(tokenRsponse.body), }) } catch (e) { res.json({ code:0, msg:e }) } }); app.get("/getOpenId",async (req, res) => { try{ const {query}=req const {access_token}=query console.log('access_token',access_token) const openidRes=await getOpenId(access_token) res.json({ code:openidRes.statusCode, data:JSON.parse(openidRes.body) }) } catch (e) { res.json({ code:0, msg:e }) } }); app.get("/quickGetOpenId",async (req, res) => { try{ const {query}=req const {code}=query console.log('code',code) const tokenRsponse=await getToken(code) const tokenBody=JSON.parse(tokenRsponse.body) const {access_token}=tokenBody const openIdRsponse=await getOpenId(access_token) res.json({ code:tokenRsponse.statusCode, data:JSON.parse(openIdRsponse.body) }) } catch (e) { res.json({ code:0, msg:e }) } }); app.get("/getUserInfo",async (req, res) => { try{ const {query}=req const {access_token,openId}=query console.log('access_token openId',access_token,openId) const userInfoRes=await getUserInfo(access_token,openId) res.json({ code:userInfoRes.statusCode, data:JSON.parse(userInfoRes.body) }) } catch (e) { res.json({ code:0, msg:e }) } }); app.get("/qq_login_callback", (req, res) => { res.sendFile(__dirname + thirdLoginDir+"qqLoginCallback.html"); }); app.get("/azure_login_callback", (req, res) => { res.sendFile(__dirname + thirdLoginDir+"azureLoginCallback.html"); });
回调html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>qqLoginCallback</title> </head> <body> qq login success! <script type="application/javascript"> function init() { console.log('qq success login') console.log('window.location.href',window.location.href) const href=window.location.href const data={} const urlArray=href.split('?') const urlLength=urlArray.length if(urlLength>1){ urlArray[urlLength-1].split('&').forEach(item=>{ const itemArray=item.split('=') const key=itemArray[0] const value=itemArray[1] data[key]=value }) } if(window.opener) { //发送data window.opener.postMessage(data,'*') //关闭 setTimeout(()=>{ window.close() },1000) } } window.onload=init </script> </body> </html>
vue前端qq登录的调用:
async qqLogin () { try { const that = this // qq const res = await that.$axios .post('/third-login/getAuthUrl') console.log('res', res) if (res.data && res.data.data) { const resData = res.data.data console.log('resData', resData) if (resData ) { let url = resData console.log('url', url) const openHandle = window.open(url, 'width:800px;height:700px', '_black') console.log('openHandle', openHandle) window.onmessage = async (res) => { const {origin, data} = res if (origin.includes('yongma16.xyz')) { const {code, state} = data console.log('code state', code, state) that.thirdLoginConfig.qCode = code that.thirdLoginConfig.qState = state const tokenRes = await that.getAccessToken(code) console.log('tokenRes', tokenRes) } } } } return new Promise(resolve => { resolve(true) }) } catch (e) { return new Promise((resolve, reject) => { reject(e) }) } }, async getAccessToken (code) { try { const tokenUrl = '/third-login/getToken' const params = { code } const tokenRes = await this.$axios.get(tokenUrl, {params}) console.log('tokenRes', tokenRes) if (tokenRes) { const {access_token, expires_in, refresh_token} = tokenRes.data.data if (access_token) { this.thirdLoginConfig.qToken = access_token await this.getOpenId(access_token) } } } catch (e) { console.log('token error', e) } }, async getOpenId (token) { try { const tokenUrl = '/third-login/getOpenId' const params = { access_token: token } const openIdRes = await this.$axios.get(tokenUrl, {params}) console.log('openIdRes', openIdRes) if (openIdRes) { const {openid} = openIdRes.data.data if (openid) { this.thirdLoginConfig.qOpenid = openid await this.getQUserinfo(this.thirdLoginConfig.qToken, openid) } } } catch (e) { console.log('token error', e) } }, async getQUserinfo (token, openId) { try { const userInfoUrl = '/third-login/getUserInfo' const params = { access_token: token, openId: openId } const userRes = await this.$axios.get(userInfoUrl, {params}) if (userRes) { this.thirdLoginConfig.qUserInfo = userRes.data.data this.registerThirdLogin() } console.log('userRes', userRes) } catch (e) { console.log('token error', e) } }
效果:
⭐结束
本文分享到这结束,如有错误或者不足之处欢迎指出!