服务端开发流程文档
https://wiki.connect.qq.com/%e5%bc%80%e5%8f%91%e6%94%bb%e7%95%a5_server-side
- 开发者注册
- 放置QQ登录按钮
- 获取Access Token
- 获取用户的OpenID
- 调用OpenAPI访问修改用户信息

进入 /login页面,点击 QQ登录
export const qqAuthUrl = 'https://graph.qq.com/oauth2.0/authorize';export const qqAuthOptions = {response_type: 'code',client_id: appId,redirect_uri,state,scope: 'get_user_info,list_album', // 获取的权限}
跳转到 QQ的授权页面
输入用户名和密码后,callbackUrl 返回的 code&state 10分内过期
http://localhost:7001/user/callback?code=0B70FD281699B4085A572695777F5DAD&state=1638614181739
用 code去获取 accessToken
{access_token: '7EE2C16CE8515D181132F3F8B160536C',expires_in: '7776000',refresh_token: '59D3B59675D6A25CB403B9399F0CF065'}
fetchMe,用 code 和 accessToken获取 qq的 openid
{"client_id":"101499238","openid":"F98*****************************"}
用 openid + access_token 获取用户的信息
QQ登录流程
https://wiki.connect.qq.com/%e5%87%86%e5%a4%87%e5%b7%a5%e4%bd%9c_oauth2-0
1 放置 QQ登录按钮
https://wiki.connect.qq.com/%e6%94%be%e7%bd%aeqq%e7%99%bb%e5%bd%95%e6%8c%89%e9%92%ae_oauth2-0
进入 /login页面,点击 QQ登录
https://graph.qq.com/oauth2.0/show?which=Login&display=pc&client_id=101423631&redirect_uri=https://account.geekbang.org/account/oauth/callback?type=qq&ident=062771&login=0&cip=0&redirect=https%3A%2F%2Faccount.geekbang.org%2Fthirdlogin%3Fremember%3D1%26type%3Dqq%26is_bind%3D0%26platform%3Dtime%26redirect%3Dhttps%253A%252F%252Ftime.geekbang.org%252F%26failedurl%3Dhttps%3A%2F%2Faccount.geekbang.org%2Fsignin%3Fredirect%3Dhttps%253A%252F%252Ftime.geekbang.org%252F&scope=get_user_info&response_type=code&state=52ff2fbc282fb13d02da9f0f95b576c1c8bf8f5c
2 跳转到 QQ授权页面

// url 三次解码https://graph.qq.com/oauth2.0/show?which=Login&display=pc&client_id=101423631&redirect_uri=https%3A%2F%2Faccount.geekbang.org%2Faccount%2Foauth%2Fcallback%3Ftype%3Dqq%26ident%3Da48ff0%26login%3D0%26cip%3D0%26redirect%3Dhttps%253A%252F%252Faccount.geekbang.org%252Fthirdlogin%253Fremember%253D1%2526type%253Dqq%2526is_bind%253D0%2526platform%253Dtime%2526redirect%253Dhttps%25253A%25252F%25252Ftime.geekbang.org%25252F%2526failedurl%253Dhttps%253A%252F%252Faccount.geekbang.org%252Fsignin%253Fredirect%253Dhttps%25253A%25252F%25252Ftime.geekbang.org%25252F&scope=get_user_info&response_type=code&state=83fafc298feeb4a77256c01cb56ccb900944c2db// 第一次解码decodeURIComponent(url)https://graph.qq.com/oauth2.0/show?which=Login&display=pc&client_id=101423631&redirect_uri=https://account.geekbang.org/account/oauth/callback?type=qq&ident=062771&login=0&cip=0&redirect=https%3A%2F%2Faccount.geekbang.org%2Fthirdlogin%3Fremember%3D1%26type%3Dqq%26is_bind%3D0%26platform%3Dtime%26redirect%3Dhttps%253A%252F%252Ftime.geekbang.org%252F%26failedurl%3Dhttps%3A%2F%2Faccount.geekbang.org%2Fsignin%3Fredirect%3Dhttps%253A%252F%252Ftime.geekbang.org%252F&scope=get_user_info&response_type=code&state=52ff2fbc282fb13d02da9f0f95b576c1c8bf8f5c// 第二次解码decodeURIComponent(redirect_uri)https://account.geekbang.org/account/oauth/callback?type=qq&ident=062771&login=0&cip=0&redirect=https://account.geekbang.org/thirdlogin?remember=1&type=qq&is_bind=0&platform=time&redirect=https%3A%2F%2Ftime.geekbang.org%2F&failedurl=https://account.geekbang.org/signin?redirect=https%3A%2F%2Ftime.geekbang.org%2F&scope=get_user_info&response_type=code&state=52ff2fbc282fb13d02da9f0f95b576c1c8bf8f5c// 第三次解码decodeURIComponent(redirect)https://time.geekbang.org/&failedurl=https://account.geekbang.org/signin?redirect=https://time.geekbang.org/&scope=get_user_info&response_type=code&state=52ff2fbc282fb13d02da9f0f95b576c1c8bf8f5c
Authorization Code
PC网站:https://graph.qq.com/oauth2.0/authorize
请求方法:GET
https://wiki.connect.qq.com/%e4%bd%bf%e7%94%a8authorization_code%e8%8e%b7%e5%8f%96access_token
3 code授权码
输入QQ账号和密码后,返回 callback的 URL,并返回 code, state
code授权码只能用一次,用完销毁,再用报错
第一次重定向是获取 code 第二次重定向是获取 token

文档 https://wiki.connect.qq.com/%e4%bd%bf%e7%94%a8authorization_code%e8%8e%b7%e5%8f%96access_token
4 拿到授权码去获取 token

5 access_token获取 refresh_token

6 通过 access_token获取 openid

https://wiki.connect.qq.com/%e8%8e%b7%e5%8f%96%e7%94%a8%e6%88%b7openid_oauth2-0
7 通过 openid获取到用户的信息
https://graph.qq.com/user/get_user_info
获取用户信息文档
https://wiki.connect.qq.com/get_user_info
服务端代码
eggjs服务端代码
login.js
Controller/login.js
'use strict';const { Controller } = require('egg');const querystring = require('querystring');const axios = require('axios');const appId = '申请的 appId';const appKey = '申请的 appKey';const authorizeUrl = 'https://graph.qq.com/oauth2.0/authorize?';// 输入用户名和密码后,会跳转到回调地址const redirect_uri = 'http://front.zhufengpeixun.cn/user/callback';const state = Date.now();// 获取 token的 重定向const tokenUrl = 'https://graph.qq.com/oauth2.0/token?';const meUrl = 'https://graph.qq.com/oauth2.0/me?';const getUserInfo = 'https://graph.qq.com/user/get_user_info?';class LoginController extends Controller {async index() {const { ctx } = this;const options = {response_type: 'code',client_id: appId,redirect_uri,state,scope: 'get_user_info,list_album', // 获取的权限};const query = querystring.stringify(options);const ssoQQUrl = `${authorizeUrl}${query}`;const grantUrl = {wechat: 'https://wx.qq.com/',ding: 'https://im.dingtalk.com/',qq: ssoQQUrl,};await ctx.render('login.html', grantUrl);}async callback() {const { ctx } = this;console.log(10000000, ctx.query);// eslint-disable-next-line no-unused-varsconst { code, state } = ctx.query;// ctx.body = {// status: 200,// code, state,// };// redirectUrl 1const options = {grant_type: 'authorization_code',client_id: appId,client_secret: appKey,code,redirect_uri,fmt: 'json',};const fetchTokenUrl = tokenUrl + querystring.stringify(options);const res = await axios.get(fetchTokenUrl);// ctx.body = {// status: 200,// code,// state,// data: res.data,// };// redirectUrl 2// eslint-disable-next-line no-unused-vars// const { access_token, expires_in, refresh_token } = querystring.parse(res.data);const { refresh_token } = res.data;const options2 = {grant_type: 'refresh_token',client_id: appId,client_secret: appKey,refresh_token,fmt: 'json',};const refreshTokenUrl = tokenUrl + querystring.stringify(options2);const res2 = await axios.get(refreshTokenUrl);// ctx.body = {// status: 200,// access_token, expires_in,// data: res2.data,// };// 通过 access_token 获取用户信息const { access_token } = res2.data;const options3 = {access_token,fmt: 'json',};const openidUrl = meUrl + querystring.stringify(options3);const res3 = await axios.get(openidUrl);// ctx.body = {// status: 200,// data: res3.data,// };// 通过 openid 获取用户信息const { openid } = res3.data;const options4 = {access_token,oauth_consumer_key: appId,openid,};const getUserUrl = getUserInfo + querystring.stringify(options4);const res5 = await axios.get(getUserUrl);ctx.body = {status: 200,data: res5.data,};}}module.exports = LoginController;
router.js
'use strict';module.exports = app => {const { router, controller } = app;// const userExist = app.middleware.userExist();router.get('/', controller.home.index);// SSO登录router.get('/login', controller.login.index);router.get('/user/callback', controller.login.callback);// 受保护的路由,必须登录才能访问// router.post('/order', userExist, controller.orders.order);router.get('/404', controller.error.notFound);};
