hapi
保护Web资源通常是一项艰巨而艰巨的任务。 如此之多,以至于它常常被留到开发的最后阶段,然后匆忙地完成了。 这是可以理解的。 安全是开发中一个非常专业的领域,大多数人只是想了一下:“是的,这应该是安全的……”因此,开发人员很快就采用了一种临时的安全方法:
if (password === "password1") {
setCookie();
}
else {
send(401);
}
并在产品出厂时充满安全漏洞。 希望该代码片段过分简化,但这一点仍然有效。
值得庆幸的是,那里的开发人员花费大量时间尝试保护网站和Web资源的安全,我们可以依靠他们的专业知识来帮助我们保护自己的项目,而不必重新发明轮子。
在本文中,我们将逐步使用OAuth令牌通过其GitHub凭据对用户进行身份验证。 所有这些单词在一起听起来似乎非常困难,但是由于有一些文档齐全的模块,我想您会惊讶它的真正实现如此简单。
先决条件
假定读者:
1.具有使用hapi服务器框架的功能理解。
2.过去已经建立了Web资源。
3.对cookie有基本的了解。
4.有一个GitHub帐户。
5.对誓言的含义及其用途有基本的了解(您可以先阅读有关它的Wikipedia文章 )。
如果这些假设中的任何一个都不成立, 强烈建议您先处理列出的前提条件,然后再回来学习有关保护网页安全的知识。
入门
您需要做的第一件事是创建一个GitHub应用程序。 此过程将为您提供ClientID
和ClientSecret
–这两个值都是在Web服务器上设置OAuth所需的。
- 登录您的GitHub帐户并转到设置页面(https://github.com/settings/profile)
- 点击“应用程序”
- 按下“ Generate new application”按钮,您将被导航到一个新屏幕,如下所示:
- 应用程序名称和应用程序描述可以是任何您想要的。 对于Homepage URL和Authorization回调URL ,我们将它们设置为将要使用的本地服务器。 在我的示例中,我将使用端口9001,因此将两个值都设置为“ http:// localhost:9001”。 我的完整设置如下所示:
- 按下“注册应用程序”后,您将被重定向到一个新屏幕,其中将列出
ClientID
和ClientSecret
。 记下这些值以备后用。
摘要
此步骤纯属行政管理。 我们创建了一个新的GitHub应用程序,当用户尝试登录到您的网站时,系统将询问该用户。 我们不会信任GitHub凭据来信任http:// localhost:9001,而是会信任GitHub应用程序来对用户进行身份验证,然后在完成后回叫我们的网站。
规划服务器
在开始编码之前,让我们大致概述一下我们希望服务器执行的操作。 为了简单起见,我们将从四种路由开始:家庭路由,帐户信息路由,登录路由和注销路由。
在家庭路由中,如果用户已通过身份验证,请打印其名称,否则打印一条通用消息。 对于帐户路由,我们将显示GitHub发送给我们的所有信息。 如果用户未经过身份验证就请求帐户页面,我们将以正确的状态码401进行响应。登录路由将到达GitHub,请该用户授予其许可以允许我们的GitHub应用程序访问其一些帐户信息,然后返回到我们的本地Web服务器。 最后,登出路线将使用户退出我们的网站。
服务器骨架
让我们首先获得样板并路由配置。
var Hapi = require('hapi');
var server = new Hapi.Server();
server.connection({ port: 9001 });
server.register([], function (err) {
if (err) {
console.error(err);
return process.exit(1);
}
server.route([{
method: 'GET',
path: '/login',
config: {
handler: function (request, reply) {
// Reach out to GitHub, ask the user for permission for their information
// if granted, response with their name
reply();
}
}
}, {
method: 'GET',
path: '/account',
config: {
handler: function (request, reply) {
// Show the account information if the have logged in already
// otherwise, send a 491
reply();
}
}
}, {
method: 'GET',
path: '/',
config: {
handler: function (request, reply) {
// If the user is authenticated reply with their user name
// otherwise, replay back with a generic message.
reply();
}
}
}, {
method: 'GET',
path: '/logout',
config: {
handler: function (request, reply) {
// Clear the session information
reply.redirect();
}
}
}
]);
server.start(function (err) {
if (err) {
console.error(err);
return process.exit(1);
}
console.log('Server started at %s', server.info.uri);
});
});
清单1最基本的hapi服务器
摘要
上面的代码创建了一个服务器,并在9001
端口上建立了连接,并添加了一些带有处理程序功能存根的路由。 您会注意到server.register([], function() {...}
,我们正在传递一个空数组,继续,我们将开始向hapi中添加插件,但是对于最初的样板,我们将其保留我们正在使用server.route
来指定我们要构建的四个路由,并将它们传递给path
和method
字符串以及一个config
对象,该config
对象将在下一部分中大量使用。每条路由都带有空响应。如果启动服务器,应该看到:
Server started at http://hostname.local:9001
您应该能够对所有已定义的路由进行GET
请求,并收到200个空响应。
如果您过去使用过hapi,那么此样板中的内容都不会让人感到惊讶。 如果没有,请转到此处的文档站点以帮助清除问题。
插入
hapi最好的部分之一是插件系统。 插件允许将hapi应用程序的各个段细分为小型便携式模块。 您可以使用hapi服务器对象执行几乎所有操作,也可以使用插件进行操作。 您可以添加路由,扩展点,侦听事件,创建缓存段; 甚至注册主服务器对象中唯一的视图引擎。 有关插件的更多信息,请查看hapijs.com上的教程 。
对于此示例,我们将使用bell和hapi-auth-cookie插件。
钟
bell是一个hapi插件,旨在处理与第三方OAuth提供程序集成所需的大部分繁琐的握手。 它内置了对最常用的OAuth客户端(Facebook,Twitter,GitHub和Google等)的支持。 这意味着,OAuth与GitHub集成的大部分繁重工作已经完成。 我们只需要配置我们的hapi服务器即可使用它。
bell处理OAuth所需的所有来回操作, 仅在成功验证用户身份后才调用关联的hapi处理函数。 否则,hapi将以401响应。需要特别注意的一件事是bell没有任何用户会话概念。 意味着一旦单个请求已通过第三方认证,该认证将对后续请求丢失。 您可以使用bell来保护所有路由,但是用户对您的网站提出的每个单个请求都将需要OAuth跳舞,这会非常低效。 我们需要一种创建包含OAuth会话信息的安全cookie并使用该安全cookie认证将来的请求的方法。
hapi-auth-cookie
免费学习PHP!
全面介绍PHP和MySQL,从而实现服务器端编程的飞跃。
原价$ 11.95 您的完全免费
hapi-auth-cookie提供了易于使用的cookie会话管理。 用户必须通过其他方式进行身份验证; hapi-auth-cookie所做的只是提供一个API来获取和设置加密的cookie。 它具有其他一些实用程序功能,但重要的是要了解它自己不会进行任何身份验证。
hapi-auth-cookie通过通过request.auth.session
添加方法来扩展hapi request
对象; 特别是request.auth.session.set
和request.auth.session.clear
。 set
用于创建安全会话cookie并clear
以将其删除。 这些方法被添加到“ onPreAuth” 服务器扩展点内 。
对于我们的服务器,bell将负责所有OAuth协商,一旦成功,请使用hapi-auth-cookie通过request.auth.session.set
设置加密的cookie。
配置插件
在下一个代码部分中,我们将填充空register
功能,并为从图1开始的服务器配置两个插件。
var Hapi = require('hapi');
var Bell = require('bell');
var AuthCookie = require('hapi-auth-cookie');
//... refer to Listing 1
server.register([Bell, AuthCookie], function (err) {
if (err) {
console.error(err);
return process.exit(1);
}
var authCookieOptions = {
password: 'cookie-encryption-password', //Password used for encryption
cookie: 'sitepoint-auth', // Name of cookie to set
isSecure: false
};
server.auth.strategy('site-point-cookie', 'cookie', authCookieOptions);
var bellAuthOptions = {
provider: 'github',
password: 'github-encryption-password', //Password used for encryption
clientId: 'huU4KjEpMK4TECW',//'YourAppId',
clientSecret: 'aPywVjShm4aWub7eQ3ub3FbADvTvz9',//'YourAppSecret',
isSecure: false
};
server.auth.strategy('github-oauth', 'bell', bellAuthOptions);
server.auth.default('site-point-cookie');
//... refer to Listing 1
清单2配置bell和hapi-auth-cookie插件
代码说明
server.register
是将插件添加到hapi服务器的入口点。 它支持几种不同的函数签名,但是出于我们的需要,我们将传递一个对象数组。 每个对象必须实现一个register
函数,该函数将被调用并提供当前的hapi服务器对象。 一旦所有插件都已注册,便会执行回调。
我们需要在这里稍作绕道,以说明hapi如何处理身份验证。 使用hapi进行身份验证可分为两个概念: 模式和策略。 该文档在这里对其进行了最佳描述:
将方案视为一般身份验证类型,例如“基本”或“摘要”。 另一方面,策略是方案的预配置和命名实例。
除了非常具体和高级的情况以外,您将使用预建方案并配置适合您的应用程序的特定策略。 身份验证策略将在整个应用程序中使用以保护资源,这是方案的“实例”。 方案是认证请求的一种手段。 bell和hapi-auth-cookie都通过server.auth.scheme
注册新方案; “响铃”和“ cookie”方案。
方案名称是server.auth.strategy
的第二个参数。 在注册使用该方案的策略之前,必须先将该方案注册到hapi服务器。 这就是为什么我们需要首先注册插件,然后通过server.auth.strategy
设置策略。
站点cookie
在清单2中,我们首先注册一个“ cookie”策略,并将其命名为“ site-point-cookie”。 在整个代码中,我们将引用“ site-point-cookie”来引用此已配置的cookie策略。 在此处可以找到所有可用选项的完整说明。 在我们的示例中,我们仅使用password
, cookie
和isSecure
。 password
应该是一个很强的字符串,因为Iron模块将使用它来加密和解密cookie。 cookie
是cookie
的名称, isSecure
设置结果Set-Cookie标头的'Secure'选项。 这意味着该cookie仅通过HTTPS连接传输。 我们现在将其设置为false
,以使使用本示例更加容易,但通常应将其设置为true
。
github-oauth
第二种更有趣的策略是名为“ github-oauth”的“钟”类型。 与“ site-point-cookie”注册类似,我们传递名称,方案和选项对象。 响铃策略选项的完整列表可以在此处的响铃仓库中找到 。 provider
设置为“ github”,因为bell内置了对GitHub OAuth集成的支持。 如果您尝试与未知的提供程序集成,也可以将其设置为对象。 password
是在协议授权步骤期间用于加密临时 cookie的字符串。 该cookie仅在授权步骤期间持续存在,之后被销毁。 clientId
和clientSecret
是我们在“入门”部分中创建的值。 清单2中的值不会工作,因为他们是这个例子只是随机胡言乱语,你将需要插入你自己的价值观到代码。 最后, isSecure
与“ site-point-cookie”具有相同的功能。
最后,我们为整个服务器设置默认身份验证,以使用名为“ site-point-cookie”的cookie策略。 这只是一个方便的设置。 它告诉hapi对添加到server.route
每个路由使用“ site-point-cookie”策略对请求进行身份验证。 这大大减少了每条路由所需的重复配置选项的数量。
使它工作
我们终于完成了所有的配置和设置! 剩下的只是几行逻辑,将所有内容连接在一起。 一旦看到所需的代码量,您就会看到hapi实际上是一个以配置为中心的框架。 让我们遍历清单1中的每条路线,并将配置对象和处理程序更新为起作用。
登录路线
登录路由是需要联系GitHub服务器并进行OAuth跳舞的路由。 清单3显示了更新的route config选项:
method: 'GET',
path: '/login',
config: {
auth: 'github-oauth',
handler: function (request, reply) {
if (request.auth.isAuthenticated) {
request.auth.session.set(request.auth.credentials);
return reply('Hello ' + request.auth.credentials.profile.displayName);
}
reply('Not logged in...').code(401);
}
}
清单3登录路由更新
此处仅config
选项已更改。 首先,我们要将auth
选项设置为'github-oauth'。 此值引用了清单2中创建的名为“ github-oauth”的“响铃”策略。 这告诉hapi在尝试验证此路由时使用'github-oauth'策略。 如果我们忽略此选项,则hapi将回退并使用清单2中指定的默认策略。 'site-point-cookie'。 可用的auth
选项的完整列表不在本文讨论范围之内,但是您可以在此处阅读有关它们的更多信息。
在处理程序函数中,我们检查request的request.auth.isAuthenticated
值。 request.auth
添加到仅在启用了身份验证的路由上进行request
。 如果isAuthenticated
为true,我们想设置一个cookie来表明这一点。 记住,hapi-auth-cookie使用set
和clear
函数向request.auth
添加了一个session
对象。 因此,既然用户已经通过GitHub进行了身份验证,我们希望创建一个会话cookie,以将其与request.auth.session.set
一起在整个应用程序中使用,并传递从GitHub返回给我们的凭据对象。 根据我们传递给hapi-auth-cookie的选项,这将创建一个名为“ sitepoint-auth”的加密cookie。 最后,我们想回应一则消息,显示GitHub显示名称。
如果用户未通过身份验证或拒绝GitHub OAuth访问,我们将以一条消息和401状态代码进行响应。
帐户路径
如果用户已登录,则帐户路由应显示用户GitHub信息,如果未登录,则响应401。更新的配置和处理程序代码如下清单4所示。
method: 'GET',
path: '/account',
config: {
handler: function (request, reply) {
reply(request.auth.credentials.profile);
}
}
清单4帐户路由更新
这条路线没有太多变化。 因为我们没有覆盖config
对象中的任何auth
值,所以此路由使用默认的cookie策略。 当请求帐户路由时,hapi将查找“ sitepoint-auth” Cookie,并确保它存在并且对于该请求是有效的Cookie。 如果是,将调用处理程序,否则响应将为request.auth.credentials
是我们在清单3的登录路线中设置的cookie值,而profile
是GitHub存储大多数用户帐户信息的位置。
在这一点上,您应该能够测试我们添加的两条路由(“ / login”和“ / account”),并查看它们如何协同工作以及如何响应。
本国路线
像大多数网站一样,我们应该在网站的根部有一条路由。 回顾我们希望该路由执行的操作,应根据用户身份验证状态来定制响应。 如果用户未登录,则不应收到401,而是应该看到非定制的主页。 如果他们已登录,我们希望以自定义的消息欢迎他们回来。
method: 'GET',
path: '/',
config: {
auth: {
mode: 'optional'
},
handler: function (request, reply) {
if (request.auth.isAuthenticated) {
return reply('welcome back ' + request.auth.credentials.profile.displayName);
}
reply('hello stranger!');
}
}
清单5本地路由更新
清单5为auth
设置引入了一个新概念; mode
。 mode
值可以采用三个字符串值之一; “必需”,“可选”和“尝试”。 “必需”表示请求必须具有当前有效的身份验证。 “可选”意味着请求不需要有身份验证,但如果这样做,它必须是有效的。 最后,“尝试”与“可选”相同,但是身份验证不必有效。
此路由具有清单2中设置的默认cookie策略,因此我们所需要做的就是设置mode
,该strategy
将为“ site-point-cookie”。 在处理程序中,我们可以像清单3一样检查请求的auth
状态。如果为true,则用户具有有效的“ sitepoint-auth” cookie,我们可以通过响应请求来响应request.auth.credentials
存储的信息request.auth.credentials
; 就像清单4一样。如果auth
状态为false,那么我们对用户一无所知,处理程序函数将以一条通用消息进行回复。 尝试将mode
更改为“必需”,然后清除cookie以查看“必需”和“可选”之间的区别。
登出路线
最后,让我们更新注销路由以删除会话cookie。
method: 'GET',
path: '/logout',
config: {
auth: false,
handler: function (request, reply) {
request.auth.session.clear();
reply.redirect('/');
}
}
清单6注销路由更新
因为我们对所有路由都有默认的身份验证策略,所以我们想对此路由禁用auth
以允许任何请求通过。 记住如果使用默认策略,这将很有用。 否则,您将最终对服务器的每个请求进行身份验证,并且您可能不希望这样做。 特别是对于静态资源。 在处理程序中,我们调用request.auth.session.clear()
来取消设置“ sitepoint-auth” cookie,最后将用户重定向回站点的根目录。 如果用户没有“ sitepoint-auth” Cookie,则此代码本质上是“无操作”,但不会造成任何伤害,并且完全安全。
摘要
这似乎是很多话,但是其中大部分是在解释配置选项以及一些hapi身份验证内部工作原理。 hapi将身份验证分为两个概念; 计划和策略。 方案是身份验证的一般类型,策略是方案的配置实例。 我们使用bell与GitHub进行OAuth跳舞,并使用hapi-auth-cookie将用户的GitHub信息保存到名为“ sitepoint-auth”的加密cookie中。 我们在整个应用程序的其余部分中都使用了此cookie,以确定身份验证状态。
实际的路由处理程序中的大多数代码极其琐碎,因为繁重的工作大部分是由hapi插件完成的。 在登录路径中,我们设置了一个安全cookie,其中包含从GitHub发送的所有信息。 在帐户资源中,cookie的当前内容作为JSON发送回用户。 在本地路由中,我们更改了身份验证mode
以允许no auth和auth的混合使用,这是根资源的非常常见的情况,并做出了相应的响应。 最后,我们完全禁用了注销路由的身份验证,并清除了“ sitepoint-auth” Cookie,并将用户重定向到主页。
希望在阅读本文之后,您将看到所需的大部分工作只是在配置中。 除了基本的hapi样板之外,几乎没有代码。 我鼓励您在此处查看完整的工作代码,并使用不同的选项和身份验证设置自行进行试验。
如果您想了解有关Hapi.js的更多信息,请观看我们的带有Hapi.js的构建插件迷你课程的示例视频。 在本课程中,您将通过一系列视频介绍Hapi的基础知识,这些视频包括路由,视图,请求生命周期以及Hapi强大的插件系统。
翻译自: https://www.sitepoint.com/oauth-integration-using-hapi/
hapi