OAuth 什麼是OAuth? OAuth(開放受權)是一個開放標準,容許用戶讓第三方應用訪問該用戶在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用戶名和密碼提供給第三方應用。html
目前主流是2.0node
OAuth 2.0是OAuth協議的下一版本,但不向後兼容OAuth 1.0。git
OAuth 2.0關注客戶端開發者的簡易性,同時爲Web應用,桌面應用和手機,和起居室設備提供專門的認證流程。2012年10月,OAuth 2.0協議正式發佈爲RFC 6749[1] 。github
主要是apiweb
微博api twitter facebook github qq、微信 原理redis
www.ruanyifeng.com/blog/2014/0…數據庫
serverexpress
-(A)用戶打開客戶端之後,客戶端要求用戶給予受權。 -(B)用戶贊成給予客戶端受權。 -(C)客戶端使用上一步得到的受權,向認證服務器申請令牌。 -(D)認證服務器對客戶端進行認證之後,確認無誤,贊成發放令牌。 -(E)客戶端使用令牌,向資源服務器申請獲取資源。 -(F)資源服務器確認令牌無誤,贊成向客戶端開放資源。npm
OAuth in nodejs 實現json
github.com/thomseddon/… Simple, unobtrusive authentication for Node.js. 微信OAuth說明 若是用戶在微信客戶端中訪問第三方網頁,公衆號能夠經過微信網頁受權機制,來獲取用戶基本信息,進而實現業務邏輯。
關於網頁受權回調域名的說明
一、在微信公衆號請求用戶網頁受權以前,開發者須要先到公衆平臺官網中的開發者中心頁配置受權回調域名。請注意,這裏填寫的是域名(是一個字符串),而不是URL,所以請勿加http://等協議頭; 二、受權回調域名配置規範爲全域名,好比須要網頁受權的域名爲:www.qq.com,配置之後此域名下面的頁面http://www.qq.com/music.html 、 www.qq.com/login.html 均可以進行OAuth2.0鑑權。但http://pay.qq.com 、 music.qq.com 、 qq.com沒法進行OAuth2.0鑑權 三、若是公衆號登陸受權給了第三方開發者來進行管理,則沒必要作任何設置,由第三方代替公衆號實現網頁受權便可
關於網頁受權的兩種scope的區別說明
一、以snsapi_base爲scope發起的網頁受權,是用來獲取進入頁面的用戶的openid的,而且是靜默受權並自動跳轉到回調頁的。用戶感知的就是直接進入了回調頁(每每是業務頁面) 二、以snsapi_userinfo爲scope發起的網頁受權,是用來獲取用戶的基本信息的。但這種受權須要用戶手動贊成,而且因爲用戶贊成過,因此無須關注,就可在受權後獲取該用戶的基本信息。 三、用戶管理類接口中的「獲取用戶基本信息接口」,是在用戶和公衆號產生消息交互或關注後事件推送後,才能根據用戶OpenID來獲取用戶基本信息。這個接口,包括其餘微信接口,都是須要該用戶(即openid)關注了公衆號後,才能調用成功的。
關於網頁受權access_token和普通access_token的區別
一、微信網頁受權是經過OAuth2.0機制實現的,在用戶受權給公衆號後,公衆號能夠獲取到一個網頁受權特有的接口調用憑證(網頁受權access_token),經過網頁受權access_token能夠進行受權後接口調用,如獲取用戶基本信息; 二、其餘微信接口,須要經過基礎支持中的「獲取access_token」接口來獲取到的普通access_token調用。
關於UnionID機制
一、請注意,網頁受權獲取用戶基本信息也遵循UnionID機制。即若是開發者有在多個公衆號,或在公衆號、移動應用之間統一用戶賬號的需求,須要前往微信開放平臺(open.weixin.qq.com)綁定公衆號後,纔可利用UnionID機制來知足上述需求。 二、UnionID機制的做用說明:若是開發者擁有多個移動應用、網站應用和公衆賬號,可經過獲取用戶基本信息中的unionid來區分用戶的惟一性,由於同一用戶,對同一個微信開放平臺下的不一樣應用(移動應用、網站應用和公衆賬號),unionid是相同的。
關於特殊場景下的靜默受權
一、上面已經提到,對於以snsapi_base爲scope的網頁受權,就靜默受權的,用戶無感知; 二、對於已關注公衆號的用戶,若是用戶從公衆號的會話或者自定義菜單進入本公衆號的網頁受權頁,即便是scope爲snsapi_userinfo,也是靜默受權,用戶無感知。
具體而言,網頁受權流程分爲四步:
一、引導用戶進入受權頁面贊成受權,獲取code 二、經過code換取網頁受權access_token(與基礎支持中的access_token不一樣) 三、若是須要,開發者能夠刷新網頁受權access_token,避免過時 四、經過網頁受權access_token和openid獲取用戶基本信息(支持UnionID機制)
install github.com/node-webot/…
npm install wechat-oauth 建立routes/oauth.js var express = require('express'); var router = express.Router();
var OAuth = require('wechat-oauth'); var client = new OAuth('wx1627638f56ff80a5', 'd9df5debf4e37def18cc5e37326b604e');
/* GET users listing. */ router.get('/', function (req, res, next) { var domain = "topfio-3001-rsqevg.box.myide.io" var auth_callback_url = domain + "/oauth/callback" var url = client.getAuthorizeURL(auth_callback_url, '', 'snsapi_userinfo'); console.log(url); // 重定向請求到微信服務器 res.redirect(url); });
router.get('/callback', function (req, res, next) { var code = req.query.code; client.getAccessToken(code, function (err, result) { console.log(result) var accessToken = result.data.access_token; var openid = result.data.openid;
client.getUser(openid, function (err, result) {
var userInfo = result;
// save or other opration
res.json(userInfo)
});
});
複製代碼
});
module.exports = router; callback以後 。。。
以前的版本
這是樸靈寫的一個模塊
受權流程 網頁受權流程分爲四步:
一、引導用戶進入受權頁面贊成受權,獲取code 二、經過code換取網頁受權access_token(與基礎支持中的access_token不一樣) 三、若是須要,開發者能夠刷新網頁受權access_token,避免過時 四、經過網頁受權access_token和openid獲取用戶基本信息(支持UnionID機制) 簡單點
一、根據app_id和app_secret和受權後回調url 二、在回調裏處理業務邏輯,好比獲取用戶信息,保存或更新 準備工做 var OAuth = require('wechat-oauth'); var client = new OAuth('your appid', 'your secret'); 可是有一個問題,好比多臺機器集羣或多進程時,token須要全局維護,如下爲保存token的接口。
if (req.wx) { req.wx_client = new OAuth(req.wx.app_id, req.wx.app_secret, function (openid, callback) { // 傳入一個根據openid獲取對應的全局token的方法 // 在getUser時會經過該方法來獲取token fs.readFile(openid +':access_token.txt', 'utf8', function (err, txt) { if (err) {return callback(err);} callback(null, JSON.parse(txt)); }); }, function (openid, token, callback) { // 請將token存儲到全局,跨進程、跨機器級別的全局,好比寫到數據庫、redis等 // 這樣才能在cluster模式及多機狀況下使用,如下爲寫入到文件的示例 // 持久化時請注意,每一個openid都對應一個惟一的token! fs.writeFile(openid + ':access_token.txt', JSON.stringify(token), callback); }); } OAuth認證 簡單點說就是根據app_id和app_secret和受權後回調url,去微信的網關去認證
認證成功後就會跳到回調url
// 主頁,主要是負責OAuth認證 router.get('/oauth', c, wx_config, wx_option, function(req, res) { console.log('req.query'); var qs = require('qs') var query_json = qs.stringify(req.query); console.log(query_json);
var auth_url = req.wx.domain + req.wx.callback.url + "/" + query_json console.log('auth_url = ' + auth_url); var url = req.wx_client.getAuthorizeURL(auth_url, '', 'snsapi_userinfo');
// 重定向請求到微信服務器 res.redirect(url); }); 核心方法就是getAuthorizeURL方法
參數
'redirectUrl' 'state' 'scope' 獲取用戶信息getUser(openid) 獲取用戶信息getUserByCode weixin.js 這樣說太抽象,仍是看代碼吧
var express = require('express'); var router = express.Router(); var check_session = require('../middleware/check_session_is_expired'); var OAuth = require('wechat-oauth'); var signature = require('wx_jsapi_sign');
// 讀取配置項 var config = require('config'); var app_id = config.get('wx.app_id'); var app_secret = config.get('wx.app_secret'); var domain = config.get('domain');
// 微信受權和回調 var client = new OAuth(app_id, app_secret);
// 主頁,主要是負責OAuth認真 router.get('/', function(req, res) { var url = client.getAuthorizeURL('http://' + domain + '/weixin/callback','','snsapi_userinfo');
// 重定向請求到微信服務器
res.redirect(url);
複製代碼
})
/**
認證受權後回調函數
根據openid判斷是否用戶已經存在
client.getAccessToken(code, function (err, result) { console.dir(err); console.dir(result); var accessToken = result.data.access_token; var openid = result.data.openid; var unionid = result.data.unionid;
console.log('token=' + accessToken);
console.log('openid=' + openid);
console.log('unionid=' + unionid);
User.find_by_unionid(unionid, function(err, user){
console.log('微信回調後,User.find_by_unionid(unionid) 返回的user = ' + user)
if(err || user == null){
console.log('通過unionid查詢無結果');
client.getUser(openid, function (err, get_by_openid) {
console.log(get_by_openid);
var oauth_user = get_by_openid;
var _user = new User(oauth_user);
_user.username = oauth_user.nickname;
_user.save(function(err, user_save) {
if (err) {
console.log('User save error ....' + err);
} else {
console.log('User save sucess ....' + err);
req.session.current_user = void 0;
res.redirect('/users/' + user_save._id + '/verify');
}
});
});
}else{
console.log('根據unionid查詢,用戶已經存在')
// if phone_number exist,go home page
if(user.is_valid == true){
req.session.current_user = user;
res.redirect('/mobile/')
}else{
//if phone_number exist,go to user detail page to fill it
req.session.current_user = void 0;
res.redirect('/users/' + user._id + '/verify');
}
}
});
複製代碼
}); });
router.post('/getsignature', function(req, res) { var url = req.body.url; var re = //$/;
if(!re.test(url)) {
url = url + '/'
}
console.log('\033[32m'+url+'\033[39m');
var config = {
cache_json_file: req.server_path,
appId: app_id,
appSecret: app_secret,
appToken: 'mengxiaoban.com'
};
signature.getSignature(config)(url, function(error, result) {
console.log(result);
if (error) {
res.json({
'error': error
});
} else {
res.json(result);
}
});
複製代碼
});
module.exports = router; 在關注者與公衆號產生消息交互後,公衆號可得到關注者的OpenID(加密後的微信號,每一個用戶對每一個公衆號的OpenID是惟一的。對於不一樣公衆號,同一用戶的openid不一樣)。 摘自《微信公衆平臺開發者文檔》
流程 1)var client = new OAuth(app_id, app_secret); 2)若是受權成功,就會跳到callback url地址,即認證受權後回調函數 3)獲取code,即var code = req.query.code; 4)獲取微信用戶信息,client.getAccessToken(code,cb) 5)根據微信用戶信息裏的openid或unionid查詢當前數據庫裏是否有該用戶,即 User.find_by_unionid(unionid, function(err, user) 6)若是有,登陸成功,建立session,重定向主頁面 req.session.current_user = user; res.redirect('/mobile/') 7)若是沒有,須要建立用戶,根據openid獲取用戶信息 client.getUser(openid, function (err, get_by_openid) 8)保存用戶 var _user = new User(oauth_user); _user.username = oauth_user.nickname;
_user.save(function(err, user_save) 9)若是保存用戶成功,跳轉到6)
10)保存失敗,顯示錯誤頁面便可
user.js model var LOCK_TIME, MAX_LOGIN_ATTEMPTS, SALT_WORK_FACTOR, Schema, UserSchema, bcrypt, mongoose;
var mongoose = require('mongoose'); var Schema = mongoose.Schema; var Promise = require('bluebird'); var autoIncrement = require('mongoose-auto-increment');
autoIncrement.initialize(mongoose.connection);
Schema = mongoose.Schema;
SALT_WORK_FACTOR = 10;
MAX_LOGIN_ATTEMPTS = 5;
LOCK_TIME = 2 * 60 * 60 * 1000;
UserSchema = new Schema({ username: {// 真實姓名 type: String }, unionid : String, openid: {// from weixin openid type: String, required: true, index: { unique: true } }, nickname : String,// from weixin 暱稱 sex : String,// from weixin 性別 0->女 1->男 language : String,// from weixin 語言 city : String,// from weixin 城市 province : String,// from weixin country : String,// from weixin headimgurl : String,// from weixin 頭像路徑 privilege : [], // from weixin created_at : { type: Date, "default": Date.now } });
UserSchema.virtual('is_valid').get(function(){ console.log('phone_number = ' +this.phone_number) if(this.phone_number == undefined | this.invite_code == undefined){ return false; } return this.invite_code.length >= 2 && this.phone_number > 0 });
// 檢查是否填寫過邀請信息 UserSchema.virtual('is_invited').get(function(){ if(typeof this.weixin_name == 'undefined'){ return false; } });
UserSchema.methods.is_exist = function(cb) { var query; query = { username: this.username, password: this.password }; return this.model('UserModel').findOne(query, cb); };
UserSchema.statics.findAll = function(cb) { return this.find().sort({ created_at: 'asc'}).exec(cb); };
UserSchema.methods.save_necessary = function(cb) { var update = { invite_code: this.invite_code, phone_number: this.phone_number, address:this.address };
return this.model('UserModel').findByIdAndUpdate(this.id, update, cb); };
UserSchema.statics.find_by_openid = function(openid, cb) { return this.findOne({ openid: openid }, cb); };
UserSchema.statics.find_by_unionid = function(unionid, cb) { return this.findOne({ unionid: unionid }, cb); };
UserSchema.statics.find_by_nickname = function(nickname, cb) { return this.findOne({ nickname: nickname }, cb); };
UserSchema.plugin(autoIncrement.plugin, { model: 'XbmId', field: 'xbm_id', startAt: 10000, incrementBy: 1 });
var UserModel = mongoose.model('UserModel', UserSchema);
Promise.promisifyAll(UserModel); Promise.promisifyAll(UserModel.prototype);
module.exports = UserModel; 此處按需設置便可
技巧點
mongoose用法 索引 virtual plugin statics methods promise/bluebird用法