node-uuap
Version:
baidu uuap in nodejs
183 lines (155 loc) • 4.87 kB
JavaScript
/**
* @file cas sso
* @author liulangyu(liulangyu@baidu.com)
*/
var extend = require('saber-lang').extend;
var config = require('./config');
var url = require('url');
var xmlparser = require('xmlparser');
/**
* CAS
* sso原理: http://wiki.baidu.com/pages/viewpage.action?pageId=39085248
*
* @constructor
* @public
* @param {Object} opts 构造参数
* @param {string} opts.service 调用服务的第三方地址
* @param {string=} opts.baseUrl 验证的服务地址
*/
function CAS(opts) {
extend(this, config, opts);
if (!this.service) {
throw new Error('required service address!');
}
var casUrl = url.parse(this.baseUrl);
var protocol = this.protocol = casUrl.protocol;
if (protocol !== 'http:' && protocol !== 'https:') {
throw new Error('baseUrl protocol wrong!');
}
}
var proto = CAS.prototype;
/**
* 通过ticket验证单点登录
*
* @public
* @param {string} ticket sso返回的ticket,百度而言,ticket五分钟有效,只能使用一次
* @param {function(error, username)} cb 回调
*/
proto.validate = function (ticket, cb) {
// 验证地址 baseUrl + 'serviceValidate?ticket=' + ticket + '&service=' + service;
var reqUrl = this.baseUrl + url.format({
pathname: '/serviceValidate',
query: {
ticket: ticket,
service: this.service
}
});
var req = require(this.protocol.replace(':', '')).get(reqUrl, function (res) {
var data = [];
res.on('data', function (chunk) {
data.push(chunk);
});
res.on('end', function () {
if (data.length) {
data = Buffer.concat(data).toString('utf8');
}
else {
data = '';
}
// 解析xml,需要去掉cas: :cas
data = data.replace(/:?cas:?/g, '');
var json = xmlparser.parser(data);
var casRes = json.serviceResponse;
if (casRes && casRes.authenticationSuccess) {
cb(null, casRes.authenticationSuccess.user);
}
else {
cb(casRes.authenticationFailure.code);
}
});
});
req.on('error', function (err) {
cb(err.message);
});
};
exports.CAS = CAS;
/**
* export express middleware
*
* @public
* @param {Object=} opts 验证之后的各种处理
* @param {string=} opts.service 调用服务的第三方地址
* @param {string=} opts.baseUrl 验证的服务地址
* @param {boolean=} opts.isRedirect 验证失败是否强制跳转到登陆页面,默认为false
* @param {Function=} opts.success 验证登录true回调
* @param {Function=} opts.fail 验证登录false回调
* @return {Function}
*/
exports.express = function (opts) {
opts = opts || {};
var baseUrl = opts.baseUrl || config.baseUrl;
var regTicket = /[?&]ticket=.*$/i;
/**
* get req url from request
*
* @inner
* @param {Request} req request
* @return {string}
*/
function getServiceUrl(req) {
return opts.service || req.protocol + '://' + req.get('host') + req.originalUrl.replace(regTicket, '');
}
var loginBaseUrl = baseUrl + '/login?service=';
var success = opts.success || function (req, res, next, username) {
// 默认处理
// 如果url附带ticket,重定向
var url = req.originalUrl;
if (regTicket.test(url)) {
res.redirect(getServiceUrl(req));
}
else {
next();
}
};
// 简单包装下失败处理
var fail = function (req, res, next) {
if (opts.isRedirect) {
res.redirect(loginBaseUrl + getServiceUrl(req));
}
else {
var optFail = opts.fail || function (req, res, next) {
next();
};
optFail.apply(this, Array.prototype.slice.call(arguments, 0));
}
};
return function (req, res, next) {
var sess = req.session;
// 没session,抛错
if (!sess) {
throw new Error('sorry but we need session support!');
}
if (sess.username) {
next();
return;
}
// 有ticket参数
var ticket = req.query.ticket;
if (ticket) {
var cas = new CAS({service: getServiceUrl(req), baseUrl: baseUrl});
cas.validate(ticket, function (err, username) {
if (!err) {
req.session.username = username;
success(req, res, next, username);
}
else {
req.session.username = null;
fail(req, res, next, err);
}
});
}
else {
fail(req, res, next);
}
};
};