传统方法是,QQ用户将自己的QQ号和密码,告诉百度,后者就可以读取用户的相册了。这样的做法有以下几个严重的缺点
OAuth就是为了解决上面这些问题而诞生的。
OAuth的作用就是让"客户端"安全可控地获取"用户"的授权,与"服务商提供商"进行互动。
它的步骤如下:
简化模式(implicit grant type)不通过第三方应用程序的服务器,直接在浏览器中向认证服务器申请令牌,跳过了"授权码"这个步骤,因此得名。所有步骤在浏览器中完成,令牌对访问者是可见的,且客户端不需要认证。
它的步骤如下:
它的步骤如下:
它的步骤如下:
apt update
apt upgrade
wget -qO- https://raw.githubusercontent.com/creationix/nvm/v0.33.11/install.sh | bash
source /root/.bashrc
nvm install stable
npm install pm2 -g
apt install nginx
https://graph.qq.com/oauth2.0/authorize?response_type=code&client_id=101499238&redirect_uri=http%3A%2F%2Ffront.zhufengpeixun.cn%2Fuser%2Fcallback&scope=get_user_info"
const client_id='101499238';
const client_secret="1af7ef89da1cbb3dae41465a1865a523";
const redirect_uri='http://front.zhufengpeixun.cn/user/callback';
const code='57C038D3AD7C5588570FF8F723EB57DE';
const request = require('then-jsonp');
const axios=require('axios');
const querystring=require('querystring');
const access_token='0CF4BD6B1BA83606E20218E6BA16FF4A';
const refresh_token='7E3C2110B9813BF714BA212094699F7F';
const openid='0E302B779D8D4C72D21E3172C015682B';
const options={
grant_type: 'authorization_code',
client_id,
client_secret,
code,
redirect_uri
}
const query=querystring.stringify(options);
(async function () {
const response=await axios.get(`https://graph.qq.com/oauth2.0/token?${query}`);
const data=response.data;
const result=querystring.parse(data);
return result;
})().then(ret => console.log(ret));
(async function () {
const response=await request('GET',`https://graph.qq.com/oauth2.0/me?access_token=${access_token}`,{callbackName:"callback"});
return response;
})().then(ret => console.log(ret));
(async function () {
const options={
grant_type: 'refresh_token',
client_id,
client_secret,
refresh_token
}
const query=querystring.stringify(options);
const response=await axios.get(`https://graph.qq.com/oauth2.0/token?${query}`);
const data=response.data;
const result=querystring.parse(data);
return result;
})().then(ret => console.log(ret));
(async function () {
const options={
access_token,
oauth_consumer_key:client_id,
openid
}
const query=querystring.stringify(options);
const response=await axios.get(`https://graph.qq.com/user/get_user_info?${query}`);
const data=response.data;
return data;
})().then(ret => console.log(ret));
module.exports={
appId: '38aa47d5-da52-417c-a742-1a341a689fcc',
appKey: 'e49b9b51-7348-4d1e-be59-7bb3125d9337',
redirect_uri:'http://localhost:3000/user/callback',
fetchAccessTokenUrl: 'http://localhost:4000/oauth2.0/token',
fetchOpenIdUrl: 'http://localhost:4000/oauth2.0/me',
getUserInfoUrl:'http://localhost:4000/user/get_user_info'
}
let {parse,format}=require('url');
exports.addQueryParamsToUrl=function (url,options) {
let {protocol,host,pathname,query}=parse(url,true);
Object.assign(query,options);
return format({protocol,host,pathname,query});
}
let mongoose=require('mongoose');
//https://github.com/Automattic/mongoose/issues/6880
mongoose.set('useFindAndModify',false);
const opts = { useNewUrlParser: true };
let conn=mongoose.createConnection('mongodb://localhost/client',opts);
let Schema=mongoose.Schema;
let UserSchema=new Schema({
access_token: {type: String,required: true},
refresh_token:String,
username: {type:String,required:true},
avatar:{type:String,required:true}
});
exports.User=conn.model('User',UserSchema);
const express=require('express');
const path=require('path');
const session=require('express-session');
const bodyParser=require('body-parser');
const app=express();
const logger=require('morgan');
const oauth=require('./routes/oauth2');
const user=require('./routes/user');
app.set('view engine','html');
app.set('views',path.resolve(__dirname,'views'));
app.engine('html',require('ejs').__express);
app.use(bodyParser.urlencoded({extends: true}));
app.use(logger('dev'));
app.use(session({
secret: 'zfpx',
saveUninitialized: true,
resave:true
}));
app.use(function (req,res,next) {
res.createError=function (status,message) {
let error=new Error(message);
error.code=1;
error.status=status;
error.message=message;
return error;
}
req.session.user={
"_id": "5b7f6f189d384c73a7ee9038",
"username": "zhangsan",
"password": "4",
"avatar":"http://www.gravatar.com/avatar/93e9084aa289b7f1f5e4ab6716a56c3b"
};
next();
});
app.use('/oauth2.0',oauth);
app.use('/user',user);
app.use(function (err,req,res,next) {
res.status(err.status||500).json({code:err.code,message:err.message});
});
app.listen(4000,() => {
console.log(`服务已经在4000端口上启动`);
});
let express=require('express');
const axios=require('axios');
const {User}=require('../model');
const querystring = require('querystring');
const {appId,appKey,redirect_uri,fetchAccessTokenUrl,fetchOpenIdUrl,getUserInfoUrl}=require('../config');
const {addQueryParamsToUrl}=require('../utils');
let router=express.Router();
router.get('/login',function (req,res) {
res.render('login');
});
router.get('/callback',async function (req,res) {
let {code}=req.query;
let options={
grant_type: 'authorization_code',
client_id: appId,
client_secret: appKey,
code,
redirect_uri
}
let url=addQueryParamsToUrl(fetchAccessTokenUrl,options);
let result=await axios.get(url);
let {access_token,expires_in,refresh_token}=querystring.parse(result.data);
url=addQueryParamsToUrl(fetchOpenIdUrl,{
access_token
});
result=await axios.get(url);
let start=result.data.indexOf('{');
let end=result.data.lastIndexOf('}');
result.data=result.data.slice(start,end+1);
let {client_id,openid}=JSON.parse(result.data);
url=addQueryParamsToUrl(getUserInfoUrl,{
access_token,
oauth_consumer_key: client_id,
openid
});
result=await axios.get(url);
let {username,avatar}=result.data;
let user = await User.create({
access_token,
refresh_token,
username,
avatar
});
req.session.user=user;
res.redirect('/');
});
module.exports=router;
<%include header.html%>
<div class="row">
<div class="col-md-12">
<a href="http://localhost:4000/oauth2.0/authorize?response_type=code&client_id=5b7f704beacb0274b068da17&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fuser%2Fcallback&scope=list_album,get_user_info">
<img src="/images/qq.png">
</a>
</div>
</div>
<%include footer.html%>
<%include header.html%>
<div class="row">
<div class="col-md-12">
你已经登录,欢迎你 <%=user.username%>
<img src="<%=user.avatar%>"/>
</div>
</div>
<%include footer.html%>
let mongoose=require('mongoose');
//https://github.com/Automattic/mongoose/issues/6880
mongoose.set('useFindAndModify',false);
const opts = { useNewUrlParser: true };
let conn=mongoose.createConnection('mongodb://localhost/oauth',opts);
let Schema=mongoose.Schema;
let ObjectId=Schema.Types.ObjectId;
let ApplicationSchema=new Schema({
appId: {type:String,required:true}, //appId
appKey: {type:String,required:true}, //appKey
website: {type: String,required: true}, //网站名称
redirect_uri: {type: String,required: true}//回调地址
});
exports.Application=conn.model('Application',ApplicationSchema);
let UserSchema=new Schema({
username: {type:String,required:true}, //appId
password: {type: String,required: true}, //appKey
avatar:{type:String,required:true}
});
exports.User=conn.model('User',UserSchema);
let AuthorizationCodeSchema=new Schema({
client_id: {type: String,required: true},
user: {type: ObjectId,ref: 'User',required:true},
permissions: {type: [{type:ObjectId,ref:'Permission'}],required: true},
createAt:{type:Date,default:Date.now}//10分钟内过期
});
exports.AuthorizationCode=conn.model('AuthorizationCode',AuthorizationCodeSchema);
let PermissionSchema=new Schema({
name: {type:String,required:true},
scope: {type: String,required: true}
});
exports.Permission=conn.model('Permission',PermissionSchema);
let AccessTokenSchema=new Schema({
client_id: {type: String,required: true},
user: {type: ObjectId,ref: 'User',required:true},
permissions: {type: [{type: ObjectId,ref: 'Permission'}],required: true},
refresh_token:{type:String},
createAt:{type:Date,default:Date.now}//10分钟内过期
});
exports.AccessToken=conn.model('AccessToken',AccessTokenSchema);
let uuid=require('uuid');
let {Application,User,AuthorizationCode,Permission}=require('./model');
/* User.create({username: 'zhangsan',password: '4'},(err,doc) => {
console.log(doc);
}); */
let appId=uuid.v4();// 38aa47d5-da52-417c-a742-1a341a689fcc
let appKey=uuid.v4();// e49b9b51-7348-4d1e-be59-7bb3125d9337
let redirect_uri='http://localhost:3000/user/callback';
let encode_redirect_uri=encodeURIComponent(redirect_uri);
console.log(encode_redirect_uri);
//let name='珠峰培训';
/* Application.create({appId,appKey,redirect_uri,name},(err,doc) => {
console.log(doc);
}); */
//let name='获得您的昵称、头像、性别';
let name="获取登录用户的相册列表";
//let scope='get_user_info';//
let scope='list_album';
/* Permission.create({name,scope},(err,doc) => {
console.log(doc);
}); */
const express=require('express');
const path=require('path');
const session=require('express-session');
const bodyParser=require('body-parser');
const app=express();
const logger=require('morgan');
const oauth=require('./routes/oauth2');
const user=require('./routes/user');
app.set('view engine','html');
app.set('views',path.resolve(__dirname,'views'));
app.engine('html',require('ejs').__express);
app.use(bodyParser.urlencoded({extends: true}));
app.use(logger('dev'));
app.use(session({
secret: 'zfpx',
saveUninitialized: true,
resave:true
}));
app.use(function (req,res,next) {
res.createError=function (status,message) {
let error=new Error(message);
error.code=1;
error.status=status;
error.message=message;
return error;
}
req.session.user={
"_id": "5b7f6f189d384c73a7ee9038",
"username": "zhangsan",
"password": "4",
"avatar":"http://www.gravatar.com/avatar/93e9084aa289b7f1f5e4ab6716a56c3b"
};
next();
});
app.use('/oauth2.0',oauth);
app.use('/user',user);
app.use(function (err,req,res,next) {
res.status(err.status||500).json({code:err.code,message:err.message});
});
app.listen(4000,() => {
console.log(`服务已经在4000端口上启动`);
});
let express=require('express');
let uuid=require('uuid');
const querystring=require('querystring');
const {Application,Permission,AuthorizationCode,AccessToken}=require('../model');
let router=express.Router();
router.get('/authorize',async function (req,res,next) {
//http://localhost:4000/oauth2.0/authorize?response_type=code&client_id=5b7f704beacb0274b068da17&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fuser%2Fcallback
let {response_type='code',client_id,redirect_uri,scope='get_user_info'}=req.query;
if (!client_id) {
return next(res.createError(400,'缺少 client_id 参数'));
}
if (!redirect_uri) {
return next(res.createError(400,'缺少 redirect_uri 参数'));
}
redirect_uri=decodeURIComponent(redirect_uri);
let client=await Application.findById(client_id);
if (client.redirect_uri !== redirect_uri) {
return next(res.createError(400,'传入的回调地址不匹配'));
}
let query={$or: scope.split(',').map(item => ({scope: item}))};
let permissions=await Permission.find(query);
res.render('authorize',{
user:req.session.user,
client,
permissions
});
});
router.post('/authorize',async function (req,res,next) {
let {client_id,redirect_uri}=req.query;
let {permissions}=req.body;
if (!Array.isArray(permissions)) {
permissions=[permissions]
}
let authorizationCode=await AuthorizationCode.create({
client_id,
user: req.session.user._id,
permissions
});
res.redirect(`${redirect_uri}?code=${authorizationCode._id}`);
});
router.get('/token',async function (req,res,next) {
let {grant_type,client_id,client_secret,code,redirect_uri}=req.query;
let authorizationCode=await AuthorizationCode.findById(code);
if (!authorizationCode) {
return next(res.createError(400,'授权码错误'));
}
let accessToken=await AccessToken.create({
client_id: authorizationCode.client_id,
user:authorizationCode.user,
refresh_token: uuid.v4(),
permissions:authorizationCode.permissions
});
let result={access_token: accessToken._id.toString(),expires_in: 60*60*24*90};
res.send(querystring.stringify(result));
});
router.get('/me',async function (req,res,next) {
let {access_token}=req.query;
if (!access_token) {
return next(res.createError(400,'access_token未提供'));
}
let {client_id,user:openid} = await AccessToken.findById(access_token);
let result={
client_id,
openid
}
res.send(`callback(${JSON.stringify(result)})`);
});
module.exports=router;
let express = require('express');
const {User} = require('../model');
let router=express.Router();
router.get('/get_user_info',async function (req,res) {
let {access_token,oauth_consumer_key,openid}=req.query;
let user = await User.findById(openid);
res.json(user);
});
module.exports=router;
<%include header.html%>
<div class="row">
<div class="col-md-12">
<form method="POST">
<div class="row">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h4 class="text-center">快速登录</h3>
</div>
<div class="panel-body text-center">
<img src="<%=user.avatar%>" style="display:inline-block" class="img-responsive" alt="头像">
</div>
<div class="panel-footer text-center">
<input type="submit" class="btn btn-primary" value="授权并登录">
</div>
</div>
</div>
<div class="col-md-6">
<ul class="list-group">
<li class="list-group-item"><%=client.website%>将获得以下权限:</li>
<li class="list-group-item">
<div class="checkbox">
<label>
<input type="checkbox">全选
</label>
</div>
<%permissions.forEach(function(permission){%>
<div class="checkbox">
<label>
<input type="checkbox" name="permissions" value="<%=permission._id%>"><%=permission.name%>
</label>
</div>
<%})%>
</li>
<li class="list-group-item">授权后表明你已同意 QQ登录服务协议</li>
</ul>
</div>
</div>
</form>
</div>
</div>
<%include footer.html%>