发布的尼尔·布鲁克斯2018年12月18日

使用AWS Cognito管理Symfony项目中的身份验证

我们的一个前端工程师Sebastian最近一直在做一些兼职项目,其中一个项目包括在AWS Cognito处理他的用户管理。当他带我参观他一直在做的事情时,这让我思考。”把Symfony的认证也交给Cognito有多容易?”。

为什么Cognito ?

如果您不熟悉Cognito,可以将其总结如下:它提供了一种中心机制,用于登录和管理用户,并在web应用程序中对用户进行身份验证。

为了简化这一点,可以将Cognito看作是应用程序当前的简化版本用户表,但保存在数据库外部。

美妙之处在于你可以访问它用户' table '直接从PHP后端,Python Lambda函数,或React前端。您甚至不需要构建自己的自定义身份验证API端点。

Cognito还内置支持多因素认证,密码重置,电子邮件和短信确认,社交登录(Facebook, Twitter等),以及更多。这意味着您可以为自己的用户提供更多的选择来注册您的服务。

为什么我要把用户转移到Cognito?

没有明确的理由让你需要将用户移出应用程序数据库。事实上,在很多情况下,我会建议你这么做让他们待在原地.然而,将用户移出数据库可以带来很多好处:

  • 您的数据库/ PHP模型结构可能会得到改进,一旦您不再考虑用户S和更多与你的商业模式相关的东西;作者年代,客户端年代,等等
  • 您可以将身份验证从应用程序的数据关注点中移开
  • 让Amazon去操心数据安全、密码哈希等问题,而不是自己去做

此外,如果您通过在线搜索找到此页面PHP / Symfony AWS Cognito在美国,你可能已经很清楚Cognito是什么,以及你为什么想要使用它。

轻微的偏差

许多Cognito使用示例表明,它的主要目标是允许用户直接从客户端应用程序登录。提供了服务器端(如PHP、NodeJS等)身份验证,但没有很好地记录。

就我个人而言,我经常发现AWS的文档很难浏览,如果没有看到其他社区的例子用其他语言实现了同样的事情,我想我会努力使它成功。谢谢开发社区,你们太棒了。

开始

我们要做的第一件事是创建一个新的Symfony 4项目,并让它在本地运行:

作曲家创建-项目symfony/网站骨架cognito-logincdcognito-login

接下来,让我们通过调用new来创建一个用户身份验证系统制造商包。我们会告诉你的制造商我们想要存储我们的安全用户对象,我们的应用程序是将检查密码本身(记住,我们将把所有这些头疼的事情都交给AWS):

php bin/console make:user安全用户类名如用户用户):>用户是否要存储用户数据数据库通过学说?是的/不是的>no输入唯一的属性名“显示”的名字用户例如,email,用户名,uuid电子邮件):>这个应用程序需要哈希/检查用户密码?选择不如果不需要密码,或者其他系统会检查/散列密码例如,单点登录服务器这个应用程序需要吗哈希/检查用户密码?是的/不是的>src/Security/User.php created: src/Security/UserProvider.php updated: config/packages/ Security .phpyaml成功!

现在,让我们使用制造商再次生成登录表单:

使用哪种类型的认证你想要什么?空的身份:0]空的身份1]登录表单验证器>要创建的验证方的类名例如AppCustomAuthenticator>CognitoAuthenticator选择名称控制器类例如SecurityControllerSecurityController]:>SecurityController输入需要认证的User类例如应用程序\ Entity\ U爵士应用程序\ S安全\ User):>应用程序\ S安全\ Usrc/Security/CognitoAuthenticator.php更新:config/packages/ Security .php创建:src/Controller/SecurityController.php创建:templates/security/login.html嫩枝成功!

如果你打开配置/包/ security.yaml你会注意到制造商Bundle已经配置了用户提供程序和防火墙来使用我们正在创建的类:

安全供应商app_user_provideridApp \安全\ UserProvider防火墙#……主要匿名真正的警卫身份验证器-App \安全\ CognitoAuthenticator

既然我们在这里,让我们加上注销防火墙配置security.yaml

安全#……防火墙#……主要#……注销路径app_logout

并添加/注销路径配置/ routes.yaml

app_logout路径/注销

接下来我们需要将AWS PHP SDK安装到我们的项目中:

作曲家需要aws / aws-sdk-php

现在唯一要做的就是启动本地web服务器,这样我们就可以边运行边测试了:

作曲家需要symfony / web-server-bundle——开发php bin /控制台服务器:运行

一旦web服务器开始运行,你就可以访问Symfony开发环境了。注意调试栏中用户图标旁边的' n/a ',这表示我们目前没有经过身份验证。

你好,世界"></a></p>
      <h3 id=创建用户池

我们想在Cognito中创建一个“用户池”,它将保存我们所有的用户及其密码。你可以通过AWS CLI完成所有这些操作,但当我第一次做一些事情时,我更喜欢能够点击并可视化我正在做的事情,所以我将在我的web浏览器中使用AWS控制台。

当你第一次访问Cognito页面时,你会被问到是想管理你的“用户池”还是“身份池”。身份与那些被允许直接使用您的AWS服务的人有关,这不是我们想要的。我们的目标是创建一个允许使用我们的应用程序的用户池。所以我们需要进入“用户池”管理。

现在创建一个新的用户池。给它起个名字,然后选择“review defaults”选项。

创建新的用户池"></a></p>
      <p>AWS现在将选择一系列默认选项,并向您显示摘要。我想在这里执行几个定制步骤,所以首先我要点击“Attributes”选项卡。</p>
      <p><a href=创建新的用户池"></a></p>
      <p>在我的应用程序中,我希望我的用户使用他们的电子邮件地址登录。我现在不介意用户名,所以我选择了<strong>电子邮件地址或电话号码</strong>对于登录方法。也许,如果我的域模型在以后需要它,我将创建一个<code class=配置文件实体,我可以存储在我的应用程序数据库,并添加用户名作为一个属性(看看我们是如何解耦的用户配置文件对象?)。

为用户选择登录方式"></a></p>
      <p>接下来我想添加一个“应用程序客户端”。重要的事情是<strong>检查</strong>'为基于服务器的认证启用登录<code class=ADMIN_NO_SRP_AUTH”,取消勾选“生成客户机的秘密”。在撰写本文时(2018年12月),PHP SDK与Cognito中的客户端机密不兼容。

创建应用程序客户端"></a></p>
      <p>现在我们可以返回到Review选项卡并创建池。记一下<strong>池Id</strong>在概览页中,和<strong>客户机ID</strong>在客户端设置中,因为我们稍后会需要这些来配置SDK。</p>
      <p>如果你的应用程序对管理用户和“普通”用户有不同的功能,进入“用户和组”选项卡,添加一个名为<code class=管理(保持其他设置不变)。我们可以稍后在防火墙设置中利用这一点。

创建管理组"></a></p>
      <p>现在转到Users选项卡,创建两个测试用户,然后将其中一个添加到刚刚创建的管理组中。</p>
      <h3 id=把它们连接在一起

创建了用户池并准备好PHP站点开始登录用户后,如何让Symfony向Cognito提交身份验证?

首先,我们将创建一个AWS Cognito适配器类,我们可以使用它来保存AWS配置设置,并将应用程序的请求代理到SDK。

创建src /桥/ AwsCognitoClient.php内容如下:

<?php名称空间App \桥使用Aws \ CognitoIdentityProvider \ CognitoIdentityProviderClientAwsCognitoClient私人美元的客户私人poolId美元私人clientId美元公共函数__construct字符串poolId美元字符串clientId美元字符串美元的地区“eu-west-2”字符串美元的版本“最新”这个美元->客户端CognitoIdentityProviderClient::工厂([“地区”= >美元的地区“版本”= >美元的版本]);这个美元->poolIdpoolId美元这个美元->clientIdclientId美元

如果您以前使用过AWS PHP SDK,那么您将知道配置它的最简单方法是使用环境变量,它将自动检测环境变量。添加你的账户的AWS_ACCESS_KEY_ID而且AWS_SECRET_ACCESS_KEY到你的.env文件。说到这里,让我们再加上COGNITO_POOL_ID而且COGNITO_CLIENT_ID我们在AWS web界面中记录的值:

#……aws / aws-sdk-php # # # # # # >AWS_ACCESS_KEY_IDAKIAJSKM……AWS_SECRET_ACCESS_KEYVRJ106jEn7D2Q7……COGNITO_POOL_IDeu-west-2_……COGNITO_CLIENT_ID7警察甲……# # # < aws / aws-sdk-php # # #

现在我们应该在中配置服务配置/ services.yaml

服务#……应用\ \ AwsCognitoClient桥梁参数poolId美元% env (COGNITO_POOL_ID) % 'clientId美元% env (COGNITO_CLIENT_ID) % '

如果我们去/登录试着用我们刚创建的一个用户登录,首先发生的是我们得到一个异常TODO:填写loadUserByUsername()

让我们打开src /安全/ UserProvider.php和修复。

首先,我们需要注入刚刚创建的桥接服务:

UserProvider实现了UserProviderInterface/** * @var AWSCognitoClient */私人cognitoClient美元公共函数__constructAWSCognitoClientcognitoClient美元这个美元->cognitoClientcognitoClient美元/ /……

然后我们想使用客户端在我们的用户池中查找用户,通过电子邮件地址进行搜索:

/ /……公共函数loadUserByUsername美元的用户名美元的结果这个美元->cognitoClient->findByUsername美元的用户名);如果美元的结果“用户”])= = =0UsernameNotFoundException();$ user用户();$ user->setEmail美元的用户名);返回$ user

现在我们应该实现findByUsername ()方法src /桥/ AwsCognitoClient.php

/ /……使用Aws \结果AwsCognitoClient/ /……公共函数findByUsername字符串美元的用户名):结果返回这个美元->客户端->listUsers([“UserPoolId”= >这个美元->poolId“过滤”= >“电子邮件=\”美元的用户名\”]);

现在,如果我们重新提交表单,就会得到异常检查…/Security/CognitoAuthenticator.php中的凭据.让我们更新src /安全/ CognitoAuthenticator.php要使用我们的适配器检查密码:

使用应用\ \ AwsCognitoClient桥梁使用Aws \ CognitoIdentityProvider \ \ CognitoIdentityProviderException异常/ /……私人cognitoClient美元公共函数__constructRouterInterface美元的路由器CsrfTokenManagerInterfacecsrfTokenManager美元AwsCognitoClientcognitoClient美元这个美元->路由器美元的路由器这个美元->csrfTokenManagercsrfTokenManager美元这个美元->cognitoClientcognitoClient美元/ /……公共函数checkCredentials美元的凭证用户界面$ user试一试这个美元->cognitoClient->checkCredentials美元的凭证“电子邮件”],美元的凭证“密码”);CognitoIdentityProviderException美元的例外返回返回真正的

再加上checkCredentials ()方法到适配器类(注意,前端SDK示例使用initiateAuth进行凭据检查,但是当使用带有预先配置了访问令牌和秘密的SDK的服务器端实现时,您应该使用adminInitiateAuth相反):

/ /……公共函数checkCredentials美元的用户名美元的密码):结果返回这个美元->客户端->adminInitiateAuth([“UserPoolId”= >这个美元->poolId“ClientId”= >这个美元->clientId“AuthFlow”= >“ADMIN_NO_SRP_AUTH”//匹配前面的'基于服务器的登录'复选框设置“AuthParameters”= >“用户名”= >美元的用户名“密码”= >美元的密码]);

当我们这次重新提交表单时,我们会得到异常TODO:在Security/CognitoAuthenticator.php中提供一个有效的重定向

为了演示的目的,我将重定向到登录表单:

/ /……公共函数onAuthenticationSuccess请求美元的请求TokenInterface美元的令牌providerKey美元如果定位路径美元这个美元->getTargetPath美元的请求->getSession(),providerKey美元返回RedirectResponse定位路径美元);返回RedirectResponse这个美元->路由器->生成“app_login”));

最后要解决的是TODO:在Security/UserProvider.php中填写refreshUser().我们已经在同一个类中有了通过用户名获取用户的方法,所以让我们重用它:

/ /……公共函数refreshUser用户界面$ user如果$ user运算符用户UnsupportedUserExceptionsprintf“无效的用户类“%s”。”get_class$ user);返回这个美元->loadUserByUsername$ user->getEmail());

这一次,当我刷新表单时,我被重定向到相同的登录表单,但是现在调试栏显示我正在使用我的电子邮件地址登录。我可以从AWS Cognito管理面板禁用用户(尝试一下,他们将无法登录!),而我自己的应用程序代码/实体完全不了解身份验证机制。事实上,在这个示例应用中,我们没有配置一个数据库连接或Doctrine实体!

使用Cognito PHP SDK进行身份验证"></a></p>
      <h3 id=添加角色支持

记得之前我们加了一个管理组在Cognito界面?让我们把它连接起来,这样我们也可以通过AWS管理我们的管理员。

有一个单独的SDK方法用于获取用户的角色,adminListGroupsForUser (),所以我们只需要在适配器类中利用它:

/ /……公共函数getRolesForUsername字符串美元的用户名):结果返回这个美元->客户端->adminListGroupsForUser([“UserPoolId”= >这个美元->poolId“用户名”= >美元的用户名]);

用在我们的UserProvider类将组转换为角色时,应进行更新loadUserByUsername

/ /……公共函数loadUserByUsername美元的用户名美元的结果这个美元->cognitoClient->findByUsername美元的用户名);如果美元的结果“用户”])= = =0UsernameNotFoundException();$ user用户();$ user->setEmail美元的用户名);美元集团这个美元->cognitoClient->getRolesForUsername美元的结果“用户”][0][“用户名”);如果美元集团“组织”])>0$ user->setRoles函数美元的项目返回“具备ROLE_”美元的项目“GroupName”];},美元集团“组织”);返回$ user

现在在分析器中,你可以看到我添加的用户管理集团早些时候已经给出了ROLE_ADMIN角色,但是其他用户只有默认的角色ROLE_USER附作用。

ADMIN组中的用户具有ADMIN角色"></a><a href=不属于ADMIN组的用户具有基本角色"></a></p>
      <h3 id=那么,这一切的意义是什么?

虽然我们还没有实现重置密码、删除用户等更高级的功能,但我们在这里所做的是提供一个概念验证,即我们可以轻松地使用AWS SDK来保护Symfony应用。

只需再多做一点工作,我们就可以创建一个功能齐全的用户管理系统,它与我们的领域问题完全分离。此外,前端团队可以允许相同的用户登录站点的React应用程序,而不需要我们在Symfony应用程序中添加身份验证端点,移动应用程序可以从相同的池中登录用户,所有这些都不会给我们的PHP后端或应用程序数据库增加任何额外的流量!

在MyBuilder和Instapro的乔布斯

我们需要有经验的软件工程师,他们热爱自己的手艺,并愿意分享他们来之不易的知识。

查看职位空缺
评论的Disqus
Baidu
map