我正在努力寻找一种安全地保存访问令牌的方法,该访问令牌是我的Web应用程序在用户授权应用程序后从DiscordAPI检索到的。
我正在为Discord Bot创建一个Web界面。在此重要的是,并非每个人都可以使用它。仅允许特定Discord服务器上的服务器主持人等访问网站的大部分内容。为此,我使用了来自Discord的OAuth2资料来检索访问令牌,通过该令牌我可以获取用户信息,例如其唯一ID。 然后使用该ID来检查他们在Discord服务器上所扮演的角色。
现在,获取访问令牌的部分已编写脚本,并且似乎可以正常工作。我可以使用令牌从Discord API等中查询数据。
我主要关心的是这里的安全性。我已经阅读了几篇不同的文章,但是每个人似乎对此都有不同的看法。
我经常读到的一个“解决方案”(甚至在Auth2网站上)是将令牌保存在cookie中。
在这里,我不确定这是否完全安全。我不能仅将令牌仅存储在服务器上,因为用户必须在令牌生存期内保持登录状态。但是,将其存储为cookie会使它容易遭受攻击(我对此尚不熟悉,无法防范)。
我目前在收到令牌时有以下一行:
res.cookie('authToken', response.access_token);
我还将行调整为以下内容,因为这样做是为了删除通过脚本读取cookie的所有方式(足够吗?):
res.cookie('authToken', response.access_token, { httpOnly: true });
当访问Web界面的其他部分时,我检查cookie是否存在,并尝试向Discord询问有关它的用户信息。如果用户信息正确返回,则认为用户已通过正确的身份验证:
router.get('/', catchAsync(async (req, res, next) => {
if(req.cookies.authToken === undefined) {
// Render index view
res.render('index', {
authenticated: false
});
}
else {
// Grab the token
const localToken = req.cookies.authToken;
// Use the token to query user information
const response = await discordapi.GetDiscordUserDetails(localToken);
if(response.id) {
// Open the Index page and pass the username over
res.render('index', {
authenticated: true,
username: response.username,
userid: response.id,
avatarid: response.avatar
});
} else {
res.render('index', {
authenticated: false
});
}
}
}));
(我传递的“经过身份验证的”布尔值仅更改了html(快速车把)文档中登录按钮的可见性。您无法对其进行任何其他操作。)
现在,由于我对此知识的了解有限,我几乎认为这是最糟糕的方法,我想对此加以改进。
另一种解决方案是将访问令牌存储在Web服务器本身的数据库中。因此,基本上根本不会以任何方式将其显示给用户。 但是,我需要一种将该令牌与用户匹配的方式,因此他们仍然需要某种cookie,其中包含我可以用来获取令牌的信息。 我认为,如果令牌将在数据库中加密并且用户具有解密它的密钥,那将是最佳选择。 但是,我不确定这是否再次安全,因为如果您读出cookie,您仍然可以访问令牌,或者至少表现得像特定用户一样。
那么保持Cookie 100%安全是唯一的方法吗?我对此很陌生,即使这很可能只是一个简单的不和谐服务器的一个小型Web界面,我仍然希望正确执行此操作。
我还读到,将令牌作为某种密码来处理并将其包装到另一层“会话”中是一种方法,但是听起来很复杂,实际上我不知道从哪里开始。 / p>
我希望有人能对此有所了解,因为安全是我真正担心的事情。
感谢您的时间!
答案 0 :(得分:1)
首先,我希望看到网上银行应用程序以纯文本形式发送身份验证令牌,并在POST中附加为查询字符串。
话虽这么说,开发人员必须做出的大多数安全考虑都必须旨在保护后端结构,而不是防止用户被客户端黑客。 (显然,您将尽一切可能使最后一个假设变得不太合理,但从道德和技术上讲,这是一个限制。我的意思是,如果我连接到一个非安全网络,并且有人拦截了我的通信并设法对其进行解码,我认为这将是我的错,而不是别人。)无论如何,我将不再是哲学家,但是最后一次已知的观察将永远不会为任何问题提供100%安全的解决方案。
使用httpOnly cookie是传输和存储身份验证令牌的最简单,最安全的方法,但是如果这还不够的话,还可以实现其他一些安全层。这只是一些想法,可能还会更多!
减少令牌的生存时间,并在一段时间不活动之后关闭服务器端的会话。您将必须记录活动会话,每个会话都包含其开始时间和活动令牌等。
IP检查。如果会话以美国的IP地址开始,而五分钟后该IP地址似乎来自菲律宾,那么您可能必须采取一些措施。
使用外部身份验证服务,例如AWS Cognito。但这无能为力,您无法独自完成。
实施多重身份验证。
类似于IP检查,您可以使用用户代理字符串作为种子来计算哈希并将其存储。如果您对客户身份有疑问,请进行检查。
谈论哈希,您可以存储令牌并将哈希发送给用户。唯一的改进是,它可以防止某人直接调用Discord API(如果该API没有任何过滤器)。例如,密码始终存储为哈希。
上一个的任何组合。
该列表可能会无限增加,在我看来,经过一段时间后,您将只会不必要地浪费时间和资源。只需记住在URL中发送令牌的银行,您就会感觉好多了。
我最近写了这个JavaScript函数来生成会话ID。如果您认为它有用,请随意对其进行修改。
使用大小定义输出长度,然后键入以下内容:
function randomID(size, type) {
// SEEDS
const special = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~`!@#$%^&*()_-+={[}]|:;<,>.?/0123456789";
const alphaNum = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
const alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
const num = "0123456789";
let x, y, z;
let currentIndex;
let randomIndex;
let temporaryValue;
let i;
if (type === 'special') {
x = special;
} else if (type === 'alphaNum' || type === 'unique') {
x = alphaNum;
} else if (type === 'alpha') {
x = alpha;
} else {
x = num;
}
x = x.split();
currentIndex = x.length;
// WHILE THERE ARE ELEMENTS TO SHUFFLE
while (currentIndex !== 0) {
// PICK A REMAINING ELEMENT
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// AND SWAP IT WITH THE CURRENT ELEMENT
temporaryValue = x[currentIndex];
x[currentIndex] = x[randomIndex];
x[randomIndex] = temporaryValue;
}
x = x.toString();
y = '';
if (type === 'unique') {
z = new Date();
y += x.charAt(x.length - 1 - z.getDate());
y += x.charAt(x.length - 1 - z.getHours());
y += z.getFullYear().toString();
y += x.charAt(x.length - 1 - z.getMonth());
y += x.charAt(x.length - 1 - z.getMinutes());
y += x.charAt(x.length - 1 - z.getSeconds());
y = y.split('').reverse().join('');
y += '-';
}
for (i = 0; i < size; i++) {
y += x.charAt(Math.floor(Math.random() * x.length));
}
return y;
}