服务端开发流程文档
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-vars
const { code, state } = ctx.query;
// ctx.body = {
// status: 200,
// code, state,
// };
// redirectUrl 1
const 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);
};