基于HTTP 协议认证介绍与实现(Nodejs版)
基于HTTP 协议认证介绍与实现(Nodejs版)
##导言## 一直对http 的头认证有兴趣,就是路由器的那种弹出对话框输入账号密码怎么实现一直不明白,最近,翻了一下http 协议,发现这是一个RFC 2617的实现,所以写篇文章介绍一下吧. <!–more–>
###Http基本认证### 这是一个用于web浏览器或其他客户端在请求时提供用户名和密码的登录认证,要实现这个认证很简单:
我们先来看下协议里面怎么定义这个认证的.
-
编码: 将用户名 追加一个 冒号(’:’)接上密码,把得出的结果字符串在用Base64算法编码.
-
请求头: Authorization: 认证类型 编码字符串
来看一下客户端如何发起请求 例如,有一个用户名为:tom, 密码为:123456 怎么认证呢?
步骤如下
- 编码
Base64(‘tom:123456’) == dG9tOjEyMzQ1Ng==;
- 把编码结果放到请求头当中
Authorization: Basic dG9tOjEyMzQ1Ng==
请求样例 客户端
GET / HTTP/1.1
Host: localhost
Authorization: Basic dG9tOjEyMzQ1Ng
服务端应答
HTTP/1.1 200 OK
Date: Thu, 13 Jun 2013 20:25:37 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 53
如果没有认证信息
HTTP/1.1 401 Authorization Required
Date: Thu, 13 Jun 2013 20:25:37 GMT
WWW-Authenticate: Basic realm="Users"
验证失败的时候,响应头加上WWW-Authenticate: Basic realm=“请求域”.
这种http 基本实现,几乎目前所有浏览器都支持.不过,大家可以发现,直接把用户名和密码只是进行一次base64 编码实际上是很不安全的,因为对base64进行反编码十分容易,所以这种验证虽然简便,但是很少会在公开访问的互联网使用,一般多用在小的私有系统,例如,你们家里头的路由器,多用这种认证方式.
###Http 摘要认证### 这个认证可以看做是基本认证的增强版本,使用随机数+密码进行md5,防止通过直接的分析密码MD5防止破解. 摘要访问认证最初由 RFC 2069 (HTTP的一个扩展:摘要访问认证)中被定义 加密步骤:
后来发现,就算这样还是不安全(md5 可以用彩虹表进行攻击),所以在RFC 2617入了一系列安全增强的选项;“保护质量”(qop)、随机数计数器由客户端增加、以及客户生成的随机数。这些增强为了防止如选择明文攻击的密码分析。
- 如果 qop 值为“auth”或未指定,那么 HA2 为
- 如果 qop 值为“auth-int”,那么 HA2 为
- 如果 qop 值为“auth”或“auth-int”,那么如下计算 response:
- 如果 qop 未指定,那么如下计算 response:
好了,知道加密步骤,下面我们用文字来描述一下;
最后,我们的response 由三步计算所得.
- 对用户名、认证域(realm)以及密码的合并值计算 MD5 哈希值,结果称为 HA1。
HA1 = MD5( “tom:Hi!:123456” ) = d8ae91c6c50fabdac442ef8d6a68ae8c
- 对HTTP方法以及URI的摘要的合并值计算 MD5 哈希值,例如,“GET” 和 “/index.html”,结果称为 HA2。
HA2 = MD5( “GET:/” ) = 71998c64aea37ae77020c49c00f73fa8
- 最后生成的响应码
Response = MD5(“d8ae91c6c50fabdac442ef8d6a68ae8c:L4qfzASytyQJAC2B1Lvy2llPpj9R8Jd3:00000001:c2dc5b32ad69187a<br />:auth:71998c64aea37ae77020c49c00f73fa8”) = 2f22e6d56dabb168702b8bb2d4e72453;
RFC2617 的安全增强的主要方式:
发起请求的时候,服务器会生成一个密码随机数(nonce)(而这个随机数只有每次"401"相应后才会更新),为了防止攻击者可以简单的使用同样的认证信息发起老的请求,于是,在后续的请求中就有一个随机数计数器(cnonce),而且每次请求必须必前一次使用的打.这样,服务器每次生成新的随机数都会记录下来,计数器增加.在RESPONSE 码中我们可以看出计数器的值会导致不同的值,这样就可以拒绝掉任何错误的请求.
请求样例(服务端 qop 设置为"auth")
客户端 无认证
GET / HTTP/1.1
Host: localhost
服务器响应(qop 为 ‘auth’)
HTTP/1.1 401 Authorization Required
Date: Thu, 13 Jun 2013 20:25:37 GMT
WWW-Authenticate: Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"
客户端请求(用户名: “tom”, 密码 “123456”)
GET / HTTP/1.1
Host: localhost
Authorization: Digest username="tom",
realm="Hi!",
nonce="L4qfzASytyQJAC2B1Lvy2llPpj9R8Jd3",
uri="/",
qop=auth,
nc=00000001,
cnonce="c2dc5b32ad69187a", response="2f22e6d56dabb168702b8bb2d4e72453"
服务端应答
HTTP/1.1 200 OK
Date: Thu, 13 Jun 2013 20:25:37 GMT
Content-Type: application/json; charset=utf-8
Content-Length: 53
注意qop 设置的时候慎用:auth-int,因为一些常用浏览器和服务端并没有实现这个协议.
图片的话,移步到我的博客:http://blog.gfdsa.net/2013/06/15/webhttpauth/
基于HTTP 协议认证介绍与实现(Nodejs版)
导言
我一直对HTTP的头部认证很感兴趣,尤其是那种在路由器上弹出对话框让用户输入账号密码的功能。最近,我翻阅了HTTP协议,发现这是RFC 2617的实现。因此,我决定写一篇文章来介绍HTTP的基本认证和摘要认证。
HTTP基本认证
HTTP基本认证是一种用于Web浏览器或其他客户端在请求时提供用户名和密码进行登录认证的方式。其实现非常简单:
- 编码:将用户名追加一个冒号(
:
)接上密码,然后将结果字符串用Base64算法编码。 - 请求头:在请求头中添加
Authorization: Basic <编码后的字符串>
。
举个例子,假设用户名为tom
,密码为123456
,则认证过程如下:
-
编码:
const crypto = require('crypto'); const username = 'tom'; const password = '123456'; const authString = `${username}:${password}`; const encodedAuth = Buffer.from(authString).toString('base64'); // 输出: dG9tOjEyMzQ1Ng==
-
请求头:
GET / HTTP/1.1 Host: localhost Authorization: Basic dG9tOjEyMzQ1Ng==
如果客户端没有提供认证信息,服务器会返回401 Unauthorized
状态码,并在响应头中包含WWW-Authenticate: Basic realm="Users"
。
以下是Node.js中的示例代码,演示如何实现基本认证:
const http = require('http');
const crypto = require('crypto');
const users = {
tom: '123456'
};
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Basic ')) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Users"' });
res.end('Unauthorized');
return;
}
const base64Credentials = authHeader.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('ascii');
const [username, password] = credentials.split(':');
if (users[username] && users[username] === password) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Welcome, ' + username);
} else {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Users"' });
res.end('Unauthorized');
}
} else {
res.writeHead(404);
res.end();
}
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
HTTP摘要认证
HTTP摘要认证是基本认证的一种增强形式,通过引入随机数和密码的MD5哈希值来提高安全性。RFC 2617定义了摘要认证的具体实现细节。
摘要认证涉及以下步骤:
- 服务器生成一个随机数(nonce)。
- 客户端计算三个哈希值:HA1、HA2和最终的响应码(response)。
- 客户端将这三个哈希值发送给服务器进行验证。
以下是一个简化的示例代码,展示如何实现HTTP摘要认证:
const http = require('http');
const crypto = require('crypto');
const users = {
tom: '123456'
};
const server = http.createServer((req, res) => {
if (req.method === 'GET' && req.url === '/') {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Digest ')) {
res.writeHead(401, { 'WWW-Authenticate': 'Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"' });
res.end('Unauthorized');
return;
}
const digestParams = parseDigestHeader(authHeader);
const ha1 = crypto.createHash('md5').update(`${digestParams.username}:${digestParams.realm}:${users[digestParams.username]}`).digest('hex');
const ha2 = crypto.createHash('md5').update(`${req.method}:${digestParams.uri}`).digest('hex');
const response = crypto.createHash('md5').update([ha1, digestParams.nonce, digestParams.nc, digestParams.cnonce, digestParams.qop, ha2].join(':')).digest('hex');
if (response === digestParams.response) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Welcome, ' + digestParams.username);
} else {
res.writeHead(401, { 'WWW-Authenticate': 'Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"' });
res.end('Unauthorized');
}
} else {
res.writeHead(404);
res.end();
}
});
function parseDigestHeader(header) {
const params = header.split(', ').reduce((acc, param) => {
const [key, value] = param.split('=');
acc[key.replace(/"/g, '')] = value.replace(/"/g, '');
return acc;
}, {});
return params;
}
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
这两种认证方式各有优缺点,基本认证简单易用但不够安全,而摘要认证提供了更高的安全性,但在实际应用中需要更多的实现细节。希望这篇文章能帮助你更好地理解HTTP认证的实现。
干货啊,收藏了,社区改版了几次,图片也不能传了,这次连md预览也没有了
基于HTTP 协议认证介绍与实现(Nodejs版)
HTTP协议提供了两种主要的认证机制:基本认证和摘要认证。
1. 基本认证
基本认证是一种简单但不够安全的方式。它通过Base64编码用户名和密码来实现。
客户端请求示例:
GET / HTTP/1.1
Host: localhost
Authorization: Basic dG9tOjEyMzQ1Ng==
服务端应答示例:
HTTP/1.1 401 Authorization Required
Date: Thu, 13 Jun 2013 20:25:37 GMT
WWW-Authenticate: Basic realm="Users"
示例代码:
const http = require('http');
const crypto = require('crypto');
function authenticateBasic(req, res) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Basic ')) {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Users"' });
res.end('Unauthorized');
return false;
}
const encodedCredentials = authHeader.split(' ')[1];
const credentials = Buffer.from(encodedCredentials, 'base64').toString('utf-8');
const [username, password] = credentials.split(':');
if (username === 'tom' && password === '123456') {
res.writeHead(200);
res.end('Authorized');
} else {
res.writeHead(401, { 'WWW-Authenticate': 'Basic realm="Users"' });
res.end('Unauthorized');
}
}
http.createServer((req, res) => {
authenticateBasic(req, res);
}).listen(3000, () => console.log('Server running on port 3000'));
2. 摘要认证
摘要认证是一种更安全的认证方式,它通过MD5哈希函数对用户名、密码和随机数进行加密。
客户端请求示例:
GET / HTTP/1.1
Host: localhost
Authorization: Digest username="tom",
realm="Hi!",
nonce="L4qfzASytyQJAC2B1Lvy2llPpj9R8Jd3",
uri="/",
qop=auth,
nc=00000001,
cnonce="c2dc5b32ad69187a",
response="2f22e6d56dabb168702b8bb2d4e72453"
服务端应答示例:
HTTP/1.1 401 Authorization Required
Date: Thu, 13 Jun 2013 20:25:37 GMT
WWW-Authenticate: Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"
示例代码:
const crypto = require('crypto');
function createDigestResponse(username, password, nonce, uri, cnonce, nc) {
const ha1 = crypto.createHash('md5').update(`${username}:Hi!:${password}`).digest('hex');
const ha2 = crypto.createHash('md5').update(`GET:${uri}`).digest('hex');
const response = crypto.createHash('md5')
.update(`${ha1}:${nonce}:${nc}:${cnonce}:auth:${ha2}`)
.digest('hex');
return response;
}
function authenticateDigest(req, res) {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Digest ')) {
res.writeHead(401, { 'WWW-Authenticate': 'Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"' });
res.end('Unauthorized');
return false;
}
const params = {};
authHeader.replace(/(\w+)="?([^",]+)"?,?/g, (_, key, value) => { params[key] = value; });
const ha1 = crypto.createHash('md5').update(`tom:Hi!:123456`).digest('hex');
const ha2 = crypto.createHash('md5').update(`GET:/`).digest('hex');
const expectedResponse = crypto.createHash('md5')
.update(`${ha1}:${params.nonce}:${params.nc}:${params.cnonce}:auth:${ha2}`)
.digest('hex');
if (params.response === expectedResponse) {
res.writeHead(200);
res.end('Authorized');
} else {
res.writeHead(401, { 'WWW-Authenticate': 'Digest realm="Hi!", nonce="HSfb5dy15hKejXAbZ2VXjVbgNC8sC1Gq", qop="auth"' });
res.end('Unauthorized');
}
}
http.createServer((req, res) => {
authenticateDigest(req, res);
}).listen(3000, () => console.log('Server running on port 3000'));
以上代码实现了HTTP的基本认证和摘要认证。在实际应用中,你可能需要将用户密码存储在数据库中,并在验证过程中查询数据库以确保安全性。