UNPKG

node-uuap

Version:
183 lines (155 loc) 4.87 kB
/** * @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); } }; };