为网站的内部页面添加Basic认证
我为网站添加了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不成功(当然不托管能行)