使用 Node 和 Express 4 进行基本 HTTP 身份验证

Posted

技术标签:

【中文标题】使用 Node 和 Express 4 进行基本 HTTP 身份验证【英文标题】:Basic HTTP authentication with Node and Express 4 【发布时间】:2014-06-30 05:58:20 【问题描述】:

使用 Express v3 实现基本 HTTP 身份验证似乎很简单:

app.use(express.basicAuth('username', 'password'));

版本 4(我使用的是 4.2)删除了 basicAuth 中间件,所以我有点卡住了。我有以下代码,但它不会导致浏览器提示用户输入凭据,这是我想要的(以及我想象的旧方法所做的):

app.use(function(req, res, next) 
    var user = auth(req);

    if (user === undefined || user['name'] !== 'username' || user['pass'] !== 'password') 
        res.writeHead(401, 'Access invalid for user', 'Content-Type' : 'text/plain');
        res.end('Invalid credentials');
     else 
        next();
    
);

【问题讨论】:

无耻插件:我维护了一个相当流行的模块,它使这变得容易并且具有您需要的大多数标准功能:express-basic-auth 我最近 fork @LionC 的包,因为我必须在超短的时间内为公司项目调整它(启用上下文感知授权):npmjs.com/package/spresso-authy @LionC 从express-basic-auth 的文档中不清楚如何将其应用于一条路线。 【参考方案1】:

我使用原始basicAuth的代码来寻找答案:

app.use(function(req, res, next) 
    var user = auth(req);

    if (user === undefined || user['name'] !== 'username' || user['pass'] !== 'password') 
        res.statusCode = 401;
        res.setHeader('WWW-Authenticate', 'Basic realm="MyRealmName"');
        res.end('Unauthorized');
     else 
        next();
    
);

【讨论】:

此模块已被弃用,请改用jshttp/basic-auth(相同的api,因此答案仍然适用)【参考方案2】:

许多中间件在 v4 中从 Express 核心中提取出来,并放入单独的模块中。基本认证模块在这里:https://github.com/expressjs/basic-auth-connect

您的示例只需要更改为:

var basicAuth = require('basic-auth-connect');
app.use(basicAuth('username', 'password'));

【讨论】:

该模块声称已弃用(尽管它建议的替代方案似乎不令人满意) ^^ 绝对不令人满意,因为在密集无证。用作中间件的零示例,它可能适用,但调用不可用。他们给出的示例非常适合通用性,但不适用于使用信息。 是的,这个已经被弃用了,虽然推荐的文档很少,但代码很简单github.com/jshttp/basic-auth/blob/master/index.js 我已经在this answer中描述了如何使用basic-auth 基于在代码中以明文形式输入密码,整个模块如何存在??至少通过在 base64 中进行比较来掩盖它似乎稍微好一些。【参考方案3】:

我在express 4.0中更改了http-auth的基本认证,代码是:

var auth = require('http-auth');

var basic = auth.basic(
        realm: "Web."
    , function (username, password, callback)  // Custom authentication method.
        callback(username === "userName" && password === "password");
    
);

app.get('/the_url', auth.connect(basic), routes.theRoute);

【讨论】:

这是真正的即插即用。很好。【参考方案4】:

似乎有多个模块可以做到这一点,有些已被弃用。

这个看起来很活跃:https://github.com/jshttp/basic-auth

这是一个使用示例:

// auth.js

var auth = require('basic-auth');

var admins = 
  'art@vandelay-ind.org':  password: 'pa$$w0rd!' ,
;


module.exports = function(req, res, next) 

  var user = auth(req);
  if (!user || !admins[user.name] || admins[user.name].password !== user.pass) 
    res.set('WWW-Authenticate', 'Basic realm="example"');
    return res.status(401).send();
  
  return next();
;




// app.js

var auth = require('./auth');
var express = require('express');

var app = express();

// ... some not authenticated middlewares

app.use(auth);

// ... some authenticated middlewares

确保将auth 中间件放在正确的位置,之前的任何中间件都不会被验证。

【讨论】:

我实际上赞成'basic-auth-connect',名字不好但功能上它比'basic-auth'好。后者所做的只是解析授权标头。您仍然需要自己 implement 协议(也就是发送正确的标头) 完美!这次真是万分感谢。这很有效,很好地解释了一切。 我试过这个,但它只是一直要求我通过连续循环登录。【参考方案5】:

使用原生 javascript 的简单基本身份验证(ES6)

app.use((req, res, next) => 

  // -----------------------------------------------------------------------
  // authentication middleware

  const auth = login: 'yourlogin', password: 'yourpassword' // change this

  // parse login and password from headers
  const b64auth = (req.headers.authorization || '').split(' ')[1] || ''
  const [login, password] = Buffer.from(b64auth, 'base64').toString().split(':')

  // Verify login and password are set and correct
  if (login && password && login === auth.login && password === auth.password) 
    // Access granted...
    return next()
  

  // Access denied...
  res.set('WWW-Authenticate', 'Basic realm="401"') // change this
  res.status(401).send('Authentication required.') // custom message

  // -----------------------------------------------------------------------

)

注意:这个“中间件”可以在任何处理程序中使用。只需删除 next() 并反转逻辑即可。请参阅下面的 1-statement 示例,或此答案的 edit history。

为什么?

req.headers.authorization 包含值“Basic <base64 string>”,但它也可以为空,我们不希望它失败,因此 || '' 的奇怪组合 节点不知道atob()btoa(),因此Buffer

ES6 -> ES5

const 只是 var .. 有点(x, y) => ... 只是 function(x, y) ...const [login, password] = ...split() 只是两个 var 作业合二为一

source of inspiration (uses packages)


以上是一个超级简单示例,旨在超级短并快速部署到您的游乐场服务器。但正如 cmets 中所指出的,密码也可以包含冒号字符:。要从 b64auth 中正确提取它,您可以使用它。
  // parse login and password from headers
  const b64auth = (req.headers.authorization || '').split(' ')[1] || ''
  const strauth = Buffer.from(b64auth, 'base64').toString()
  const splitIndex = strauth.indexOf(':')
  const login = strauth.substring(0, splitIndex)
  const password = strauth.substring(splitIndex + 1)

  // using shorter regex by @adabru
  // const [_, login, password] = strauth.match(/(.*?):(.*)/) || []

一个语句中的基本身份验证

...另一方面,如果您只使用一个或很少的登录名,这是您需要的最低要求:(您甚至根本不需要解析凭据)

function (req, res) 
//btoa('yourlogin:yourpassword') -> "eW91cmxvZ2luOnlvdXJwYXNzd29yZA=="
//btoa('otherlogin:otherpassword') -> "b3RoZXJsb2dpbjpvdGhlcnBhc3N3b3Jk"

  // Verify credentials
  if (  req.headers.authorization !== 'Basic eW91cmxvZ2luOnlvdXJwYXNzd29yZA=='
     && req.headers.authorization !== 'Basic b3RoZXJsb2dpbjpvdGhlcnBhc3N3b3Jk')        
    return res.status(401).send('Authentication required.') // Access denied.   

  // Access granted...
  res.send('hello world')
  // or call next() if you use it as middleware (as snippet #1)


PS:您是否需要同时拥有“安全”和“公共”路径?考虑改用express.router

var securedRoutes = require('express').Router()

securedRoutes.use(/* auth-middleware from above */)
securedRoutes.get('path1', /* ... */) 

app.use('/secure', securedRoutes)
app.get('public', /* ... */)

// example.com/public       // no-auth
// example.com/secure/path1 // requires auth

【讨论】:

不要使用.split(':'),因为它会阻塞至少包含一个冒号的密码。此类密码根据RFC 2617有效。 冒号部分也可以使用正则表达式const [_, login, password] = strauth.match(/(.*?):(.*)/) || [] 使用!== 比较密码会使您容易受到定时攻击。 en.wikipedia.org/wiki/Timing_attack 确保使用恒定时间字符串比较。 使用Buffer.from() // for stringsBuffer.alloc() // for numbers 因为Buffer() 因安全问题而被弃用。 @Qwerty 我没有一篇关于该主题的好文章(***有帮助吗?请参阅@hraban 分享的en.wikipedia.org/wiki/Timing_attack)。您的观点是正确的——网络因素使定时攻击不太实用。但依赖此将是一个“通过默默无闻的安全”的案例——它可能会在这里和那里有所帮助,但它不如实现恒定时间比较那样提供保护。请参阅nvd.nist.gov/vuln/detail/CVE-2015-7576,了解 Rails 中的一个类似漏洞,以及它的严重程度如何评定。【参考方案6】:

Express 已删除此功能,现在建议您使用 basic-auth 库。

这里是一个如何使用的例子:

var http = require('http')
var auth = require('basic-auth')

// Create server
var server = http.createServer(function (req, res) 
  var credentials = auth(req)

  if (!credentials || credentials.name !== 'aladdin' || credentials.pass !== 'opensesame') 
    res.statusCode = 401
    res.setHeader('WWW-Authenticate', 'Basic realm="example"')
    res.end('Access denied')
   else 
    res.end('Access granted')
  
)

// Listen
server.listen(3000)

要向此路由发送请求,您需要包含格式为基本身份验证的Authorization header。

首先发送 curl 请求,您必须采用 name:pass 的 base64 编码,或者在这种情况下 aladdin:opensesame 等于 YWxhZGRpbjpvcGVuc2VzYW1l

您的 curl 请求将如下所示:

 curl -H "Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l" http://localhost:3000/

【讨论】:

【参考方案7】:

我们可以在不需要任何模块的情况下实现基本授权

//1.
var http = require('http');

//2.
var credentials = 
    userName: "vikas kohli",
    password: "vikas123"
;
var realm = 'Basic Authentication';

//3.
function authenticationStatus(resp) 
    resp.writeHead(401,  'WWW-Authenticate': 'Basic realm="' + realm + '"' );
    resp.end('Authorization is needed');

;

//4.
var server = http.createServer(function (request, response) 
    var authentication, loginInfo;

    //5.
    if (!request.headers.authorization) 
        authenticationStatus (response);
        return;
    

    //6.
    authentication = request.headers.authorization.replace(/^Basic/, '');

    //7.
    authentication = (new Buffer(authentication, 'base64')).toString('utf8');

    //8.
    loginInfo = authentication.split(':');

    //9.
    if (loginInfo[0] === credentials.userName && loginInfo[1] === credentials.password) 
        response.end('Great You are Authenticated...');
         // now you call url by commenting the above line and pass the next() function
    else

    authenticationStatus (response);



);
 server.listen(5050);

来源:-http://www.dotnetcurry.com/nodejs/1231/basic-authentication-using-nodejs

【讨论】:

【参考方案8】:

TL;DR:

express.basicAuth 消失了☒ basic-auth-connect 已弃用☒ basic-auth 没有任何逻辑☒ http-auth 是矫枉过正☑ express-basic-auth 是什么你想要的

更多信息:

既然您使用的是 Express,那么您可以使用 express-basic-auth 中间件。

查看文档:

https://www.npmjs.com/package/express-basic-auth

例子:

const app = require('express')();
const basicAuth = require('express-basic-auth');
 
app.use(basicAuth(
    users:  admin: 'supersecret123' ,
    challenge: true // <--- needed to actually show the login dialog!
));

【讨论】:

花了我一段时间才弄清楚challenge: true 选项 @VitaliiZurian 好点 - 我将它添加到答案中。感谢您指出。 @rsp 你知道如何仅将其应用于特定路线吗? 如果不想添加其他依赖的话,在一行上手写basic auth就很简单了…… 客户端 url 会是什么样子?【参考方案9】:
function auth (req, res, next) 
  console.log(req.headers);
  var authHeader = req.headers.authorization;
  if (!authHeader) 
      var err = new Error('You are not authenticated!');
      res.setHeader('WWW-Authenticate', 'Basic');
      err.status = 401;
      next(err);
      return;
  
  var auth = new Buffer.from(authHeader.split(' ')[1], 'base64').toString().split(':');
  var user = auth[0];
  var pass = auth[1];
  if (user == 'admin' && pass == 'password') 
      next(); // authorized
   else 
      var err = new Error('You are not authenticated!');
      res.setHeader('WWW-Authenticate', 'Basic');      
      err.status = 401;
      next(err);
  

app.use(auth);

【讨论】:

希望它能解决问题,但请添加对代码的解释,以便用户完全理解他/她真正想要的。【参考方案10】:

安装 express-basic-auth 依赖项:

 npm i express-basic-auth

需要您在其中创建应用的身份验证包

const app = require('express')();
const basicAuth = require('express-basic-auth');

并像这样设置中间件:

app.use(basicAuth(
    users:  'my-username': 'my-password' ,
    challenge: true,
));

【讨论】:

以上是关于使用 Node 和 Express 4 进行基本 HTTP 身份验证的主要内容,如果未能解决你的问题,请参考以下文章

Node js之使用应用生成器来搭建第一个基于express的应用

Node.js 有条件地使用 csurf 和 express 4

Node 和 Express:如何实现基本的 webhook 服务器

express基本使用

express基本使用

node + express +mongoose 实现基本登录注册