首页 > 技术文章 > 为网站的内部页面添加Basic认证

puncha 2013-07-03 15:31 原文

我为网站添加了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!");
        }
    });
}


这个是app.js,使用了那个中间件:

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不成功(当然不托管能行)






推荐阅读