Hawk Authentication 认证介绍
Hawk Authentication 是一种基于HMAC(哈希消息认证码)的HTTP认证方法,由Eran Hammer开发,提供了防止请求篡改、重放攻击以及消息认证的能力。它在每个HTTP请求中包含一个加密签名,确保请求的完整性和真实性,同时不泄露凭证。
Hawk Authentication 的工作原理
Hawk认证的基本工作流程如下:
- 凭证共享:客户端和服务器预先共享凭证,包括一个ID和一个密钥(key)。
- 请求生成:客户端创建HTTP请求,并使用共享密钥计算请求的签名。
- 请求发送:客户端将签名和其他必要信息(如nonce和时间戳)放入Authorization头中,发送给服务器。
- 签名验证:服务器接收到请求后,使用相同的算法和共享密钥计算签名,并与客户端发送的签名进行比较。
- 响应处理:如果签名匹配,服务器处理请求并返回数据;如果不匹配,则返回401 Unauthorized状态码。
Hawk Authentication 的组成部分
Hawk认证的Authorization头包含以下几个主要组成部分:
- id:客户端标识符,服务器用它来查找对应的共享密钥。
- ts:UNIX时间戳,用于防止重放攻击。
- nonce:客户端生成的随机字符串,与时间戳一起用于防止重放攻击。
- mac:请求的HMAC签名,基于请求方法、URI、主机、端口、时间戳、nonce等计算得出。
- ext(可选):额外的应用特定数据。
- hash(可选):请求正文的哈希值,用于验证请求正文的完整性。
- app(可选):应用标识符,用于多应用场景。
- dlg(可选):委托标识符,用于授权委托。
如何使用 Hawk Authentication
实施Hawk认证
以下是使用Hawk认证的详细步骤:
-
获取凭证
客户端需要从服务器获取Hawk凭证,通常包括ID和密钥。这可能通过安全的带外方式(如初始注册过程)或其他认证方法(如OAuth)完成。
-
生成Authorization头
客户端需要计算签名并构建Authorization头:
Authorization: Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="
生成这个头需要以下步骤:
- 生成时间戳和nonce
- 构建用于签名的字符串(包括HTTP方法、主机、路径等)
- 使用共享密钥计算HMAC签名
- 将所有组件组合成Authorization头
-
发送请求
将生成的Authorization头添加到HTTP请求中:
GET /resource/1?b=1&a=2 HTTP/1.1
Host: example.com
Authorization: Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=" -
服务器验证
服务器收到请求后执行以下验证:
- 查找匹配的凭证
- 检查时间戳是否在可接受范围内(通常是几分钟)
- 确保nonce未被重复使用
- 重新计算签名并与请求中的签名比较
- 如果所有检查都通过,处理请求
使用JavaScript实现Hawk认证(使用hawk库)
const Hawk = require('hawk');
const axios = require('axios');
// 客户端凭证
const credentials = {
id: 'dh37fgj492je',
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256' // 默认是sha256
};
// 发送带Hawk认证的请求
async function makeHawkRequest(url, method = 'GET', data = null) {
try {
// 创建请求URI对象
const uri = new URL(url);
const port = uri.port || (uri.protocol === 'https:' ? '443' : '80');
// 生成Hawk头
const requestOptions = {
credentials,
method,
host: uri.hostname,
port,
resource: `${uri.pathname}${uri.search}`,
ts: Math.floor(Date.now() / 1000), // 当前时间戳
nonce: Hawk.utils.randomString(6), // 随机nonce
ext: 'some-app-specific-data' // 可选的应用数据
};
// 如果请求有正文,计算正文哈希
if (data && method !== 'GET') {
const payload = JSON.stringify(data);
requestOptions.payload = payload;
requestOptions.contentType = 'application/json';
}
// 生成Hawk头
const { header } = Hawk.client.header(url, method, requestOptions);
// 发送请求
const response = await axios({
url,
method,
headers: {
'Authorization': header,
'Content-Type': data ? 'application/json' : undefined
},
data
});
return response.data;
} catch (error) {
console.error('Hawk请求失败:', error.message);
throw error;
}
}
// 验证服务器响应
function validateServerResponse(response, credentials) {
const { headers, request } = response;
const serverAuthHeader = headers['server-authorization'];
if (!serverAuthHeader) {
console.warn('响应中没有server-authorization头');
return false;
}
try {
const artifacts = {
method: request.method,
host: request.host,
port: request.port || (request.protocol === 'https:' ? '443' : '80'),
resource: request.path,
ts: headers['x-hawk-ts'],
nonce: headers['x-hawk-nonce']
};
Hawk.client.authenticate(response, credentials, artifacts, { payload: response.data });
return true;
} catch (error) {
console.error('服务器响应验证失败:', error.message);
return false;
}
}
// 使用示例
async function exampleHawkRequest() {
try {
const url = 'https://api.example.com/resource?param=value';
const data = { name: 'example' };
// GET请求示例
const getResult = await makeHawkRequest(url);
console.log('GET响应:', getResult);
// POST请求示例
const postResult = await makeHawkRequest(url, 'POST', data);
console.log('POST响应:', postResult);
} catch (error) {
console.error('请求失败:', error);
}
}
exampleHawkRequest();
服务器端实现(Node.js示例)
const Hawk = require('hawk');
const express = require('express');
const bodyParser = require('body-parser');
const app = express();
app.use(bodyParser.json());
// 模拟用户凭证存储
const credentials = {
'dh37fgj492je': {
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256',
user: 'John Doe'
}
};
// 存储已使用的nonce,防止重放攻击
const usedNonces = new Map();
// Hawk认证中间件
function hawkAuth(req, res, next) {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith('Hawk ')) {
return res.status(401).json({ error: '缺少Hawk认证头' });
}
try {
// 验证请求
const { credentials: creds, artifacts } = Hawk.server.authenticate(
{
method: req.method,
url: req.url,
host: req.hostname,
port: req.connection.localPort,
authorization: authHeader,
payload: req.body ? JSON.stringify(req.body) : '',
contentType: req.headers['content-type']
},
function(id) {
// 查找凭证回调
const cred = credentials[id];
if (!cred) return null;
return { key: cred.key, algorithm: cred.algorithm };
},
{
// 时间戳偏差容忍度(秒)
timestampSkewSec: 60,
// nonce检查回调
nonceFunc: function(key, nonce, ts) {
// 检查nonce是否已被使用
const now = Math.floor(Date.now() / 1000);
if (ts > now + 60 || ts < now - 60) {
return false; // 时间戳超出允许范围
}
const nonceKey = `${key}:${nonce}:${ts}`;
if (usedNonces.has(nonceKey)) {
return false; // nonce已被使用
}
// 存储nonce(实际应用中可能需要使用Redis等分布式存储)
usedNonces.set(nonceKey, true);
// 设置nonce的过期时间
setTimeout(() => usedNonces.delete(nonceKey), 60000);
return true;
}
}
);
// 认证成功,附加用户信息
req.user = credentials[creds.id].user;
// 生成服务器认证响应头
const serverHeader = Hawk.server.header(creds, artifacts, {
payload: JSON.stringify({ status: 'success' }),
contentType: 'application/json'
});
// 设置响应头
res.setHeader('Server-Authorization', serverHeader);
res.setHeader('X-Hawk-TS', artifacts.ts);
res.setHeader('X-Hawk-Nonce', artifacts.nonce);
next();
} catch (error) {
console.error('Hawk认证失败:', error.message);
res.status(401).json({ error: `认证失败: ${error.message}` });
}
}
// 受保护的路由
app.get('/resource', hawkAuth, (req, res) => {
res.json({ message: `Hello, ${req.user}!`, status: 'success' });
});
app.post('/resource', hawkAuth, (req, res) => {
res.json({
message: `Data received from ${req.user}`,
data: req.body,
status: 'success'
});
});
// 启动服务器
app.listen(3000, () => {
console.log('服务器运行在 http://localhost:3000');
});
Hawk Authentication 的优势
- 消息完整性:Hawk确保请求在传输过程中未被篡改。
- 防止重放攻击:通过时间戳和nonce机制防止请求被重放。
- 凭证保护:不像Basic认证,Hawk不会在每个请求中发送密钥,只发送基于密钥计算的签名。
- 有状态无需服务器存储:不像会话Cookie,服务器不需要存储会话状态(只需存储最近使用的nonce)。
- 无需TLS:虽然推荐使用TLS,但Hawk本身提供了一定的安全保障,可以在不使用TLS的情况下运行。
- 负载验证:可以验证请求和响应正文的完整性。
安全建议
在使用Hawk认证时,应注意以下安全措施:
- 使用HTTPS:尽管Hawk提供了消息完整性保护,但建议仍然使用HTTPS来保护通信的机密性。
- 安全存储密钥:确保密钥安全存储,避免泄露。
- 实施nonce验证:服务器应验证nonce的唯一性,防止重放攻击。
- 检查时间戳:验证请求的时间戳是否在可接受的时间窗口内。
- 定期轮换凭证:定期更换Hawk凭证,减少长期凭证泄露的风险。
- 设置适当的时间窗口:设置合理的时间偏差容忍度,平衡安全性和用户体验。
常见问题
-
Hawk和OAuth的区别是什么?
Hawk主要关注请求的身份验证和完整性,而OAuth则是一个授权框架,允许第三方应用代表用户访问资源。Hawk可以与OAuth结合使用,作为访问受保护资源的认证机制。
-
Hawk是否可以替代TLS?
Hawk提供了消息完整性和身份验证,但不提供传输加密。它可以在没有TLS的环境中使用,但仍然建议结合TLS使用,以提供完整的安全保护。
-
Hawk与HMAC-SHA认证有什么不同?
Hawk是一个完整的认证协议,使用HMAC-SHA算法进行签名,但还包括了nonce、时间戳和其他安全特性。它提供了更全面的安全保障,如防止重放攻击和有效的凭证管理。