我为网站添加了Graphite,awstats的统计,所以就需要做一个页面,存放一些链接,以方便访问。但是这个页面不希望被其他人访问到,所以就需要做一些简单的验证--Basic Auth。
Nodejs的验证模块,比较有名的是connect-auth,不过这个太重量级的,所以我用的是很有针对性的,轻量级的认证模块,connect-basic-auth。这个模块只有一个源文件。。很简单。。
好,闲话少说。
1) 安装:npm install connect-basic-auth --save。
2) 从名字就可以看出,他是ExpressJs/ConnectJs的一个中间件,这是他的所有源代码:
module.exports = function (callback, realm) {
if (!callback || typeof callback != 'function') {
throw new Error('You must provide a function ' +
'callback as the first parameter');
}
realm = realm ? realm : 'Authorization required.';
function unauthorized(res, sendResponse) {
res.statusCode = 401;
res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
if (sendResponse) {
res.end('Unauthorized');
}
}
return function(req, res, next) {
req.requireAuthorization = function(req, res, next) {
var authorization = req.headers.authorization;
if (req.remoteUser) return next();
if (!authorization) return unauthorized(res, true);
var parts = authorization.split(' ');
var scheme = parts[0];
if ('Basic' != scheme) {
return next(new Error('Authorization header ' +
'does not have the correct scheme. \'Basic\' ' +
'scheme was expected.'));
}
var _credentials = new Buffer(parts[1], 'base64').toString().split(':');
var credentials = { username: _credentials[0],
password: _credentials[1] };
callback(credentials, req, res, function(err) {
if (err) {
unauthorized(res);
next(err);
return;
}
req.remoteUser = credentials.username;
next();
});
};
next();
};
};
这代码其实和connect-auth差不多,不知道有没有渊源。他其实很简单,通过module.export返回一个函数,运行这个函数就可以获得中间件需要的函数了。我又在他的基础上,封装了验证的逻辑,用户名密码我是明文的,而且hard-coding,用basic-auth就是求简单,而且网站是给内部使用的,所以就不矫情了(middlewares.js):
var should = require("should")
, basicAuth = require('connect-basic-auth');
// Validate user's password
exports.basicAuth = function () {
return basicAuth(function (credentials, req, res, next) {
if (credentials && credentials.username == "cer" && credentials.password == "site") {
next();
}
else {
if (!credentials) console.log("credentials not provided");
if (credentials && credentials.username) console.log("credentials-username:" + credentials.username);
if (credentials && credentials.password) console.log("credentials-password:" + credentials.username);
next("Unautherized!");
}
});
}
var express = require('express')
, http = require('http')
, path = require('path')
, util = require('util')
, middlewares = require('./middlewares');
var app = express();
app.configure(function () {
app.set('env', 'production');
app.set('port', process.env.PORT || 80);
app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.bodyParser());
app.use(express.methodOverride());
app.use(middlewares.basicAuth());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
app.use(express.errorHandler());
});
注意,上面那个use,没有实际作用的,看上面源代码就知道,他不过是添加了一个req.requireAuthorization()的成员函数,所以,只有你显示调用这个函数,才会真正起到验证效果!这个是非常棒的!也就是说,你可以选择性的为某些页面添加验证,其他页面还是公开访问!这正是我所需要的~。看代码:
var _ = require('underscore')
, fs = require('fs')
, spawn = require('child_process').spawn
, util = require('util')
, should = require('should')
, async = require('async');
///////////////////////////
// Exports
///////////////////////////
var g_app;
exports.initRoutes = function (app) {
g_app = app;
var pageRequests = [
{ method: "get", request: /^\/internal(\/.*)?/, handler: "auth" },
{ method: "get", request: "/internal", handler: "index" },
{ method: "get", request: "/internal/visisted_users", handler: "visistedUsers" },
{ method: "get", request: "/internal/integration_test", handler: "integrationTest" },
];
_.each(pageRequests, function (pageRequest) {
var request, handler, method;
request = pageRequest.request;
handler = exports[pageRequest.handler];
method = app[pageRequest.method] || app.get;
method.call(app, request, handler);
});
}
exports.auth = function (req, res, next) {
req.requireAuthorization(req, res, next);
}
exports.index = function (req, res, next) {
res.render("page_internal");
}
exports.visistedUsers = function (req, res) {
......
}
exports.integrationTest = function (req, res) {
......
}
在路由的最上面,我用正则表达式加了一个验证auth的hanlder(因为回调函数有next参数,也算是中间件吧),凡是/internal/xxx的都需要经过验证。auth()的实现很简单,就是调用req.requireAuthorization(),看源代码就知道,他首先检查客户端有没有Authorization首部,没有的话,回送一个401(需要验证),这个时候浏览器就会弹出一个验证框,让你输入用户名密码,然后再次发送请求。有的话,就调用最初设置进去的回调函数,让你验证用户的credentical信息,成功你就调用next(),继续调用下一个handler,反之调用next("error")来结束路由。
所以,整体代码还是很简单的。
-----------------------------------------
更新:因为iisnode托管,启用了域认证,所以basic auth不成功(当然不托管能行)