使用 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 strings
或Buffer.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