如何使用Guard创建自定义认证系统

编辑本页

警告:您正在浏览的文档欧宝官网下载appob娱乐下载Symfony 2.8,现已不再维护。

本页的更新版本用于Syob娱乐下载mfony 6.2(当前稳定版本)。

如何使用Guard创建自定义认证系统

无论您是需要构建传统的登录表单、API令牌身份验证系统,还是需要与一些专有的单点登录系统集成,Guard组件都可以让您轻松完成…和乐趣!

在本例中,您将构建一个API令牌身份验证系统,并学习如何使用Guard。

创建用户和用户提供者

无论您如何进行身份验证,您都需要创建一个实现的User类用户界面并配置用户提供者.在本例中,用户通过Doctrine存储在数据库中,每个用户都有一个apiKey他们用来通过API访问他们帐户的属性:

12 34 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
/ / src / AppBundle /实体/ User.php名称空间AppBundle实体使用ob娱乐下载组件安全核心用户用户界面使用学说ORM映射作为ORM/ * * *@ORM* \实体@ORM\表(name = "用户”)* /用户实现了用户界面/ * * *@ORM\ Id *@ORM\ GeneratedValue(策略=“汽车”)*@ORM\列(类型=“整数”)* /私人id/ * * *@ORM\Column(type="string", unique=true) */私人用户名/ * * *@ORM\Column(type="string", unique=true) */私人apiKey公共函数getUsername()返回->用户名;}公共函数将getRoles()返回数组“ROLE_USER”);}公共函数getPassword(){}公共函数getSalt(){}公共函数eraseCredentials(){}//更多的getter /setter

谨慎

在上面的例子中,表名是用户.这是保留的SQL关键字和必须用反引号引用以避免错误。你也可以改变表名(例如用app_user)来解决这个问题。

提示

此用户没有密码,但您可以添加密码属性,如果您还想允许该用户使用密码登录(例如通过登录表单)。

你的用户class不需要存储在Doctrine中:做任何你需要的事情。接下来,确保你已经为用户配置了一个“用户提供者”:

  • YAML
  • XML
  • PHP
1 2 3 4 5 6 7 8 9 10 11
# app / config / security.yml安全:#……提供者:your_db_provider:实体:类:AppBundle:用户属性:apiKey#……

就是这样!有关此步骤的更多信息,请参见:

步骤1)创建Authenticator类

假设您有一个API,您的客户端将发送一个X-AUTH-TOKEN头和它们的API令牌。您的工作是读取该文件并找到相关的用户(如果有的话)。

要创建自定义身份验证系统,只需创建一个类并使其实现GuardAuthenticatorInterface.或者,扩展更简单的AbstractGuardAuthenticator.这需要你实现七个方法:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17日18 19 20 21日22日23日24日25日26日27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
/ / src / AppBundle /安全/ TokenAuthenticator.php名称空间AppBundle安全使用ob娱乐下载组件HttpFoundation请求使用ob娱乐下载组件HttpFoundationJsonResponse使用ob娱乐下载组件HttpFoundation响应使用ob娱乐下载组件安全核心用户用户界面使用ob娱乐下载组件安全警卫AbstractGuardAuthenticator使用ob娱乐下载组件安全核心身份验证令牌TokenInterface使用ob娱乐下载组件安全核心异常AuthenticationException使用ob娱乐下载组件安全核心用户UserProviderInterfaceTokenAuthenticator扩展AbstractGuardAuthenticator/** *在每个请求时调用。返回您希望传递给getUser()的任何凭据。返回null将导致这个验证器*被跳过。* /公共函数getCredentials(请求请求如果(!令牌请求->->get (“X-AUTH-TOKEN”)) {//没有token?令牌;}//这里返回的内容将作为$credentials传递给getUser()返回数组“令牌”= >令牌,);}公共函数getUser凭证, UserProviderInterfaceuserProviderapiKey凭证“令牌”];如果===apiKey){返回;}//如果是User对象,调用checkCredentials()返回userProvider->loadUserByUsername (apiKey);}公共函数checkCredentials凭证,用户界面用户//检查凭证-例如,确保密码有效//在这种情况下不需要凭据检查//返回true导致认证成功返回真正的;}公共函数onAuthenticationSuccess(请求请求, TokenInterface令牌providerKey//如果成功,让请求继续返回;}公共函数onAuthenticationFailure(请求请求, AuthenticationException异常数据数组“消息”= > strtr (异常->getMessageKey (),异常->getMessageData ())//或翻译此消息// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData()));返回JsonResponse (数据、响应::HTTP_FORBIDDEN);}/** *需要身份验证时调用,但不发送*/公共函数开始(请求请求, AuthenticationExceptionauthException= null)数据数组//你可以翻译这条信息“消息”= >身份验证所需的);返回JsonResponse (数据、响应::HTTP_UNAUTHORIZED);}公共函数supportsRememberMe()返回;}}

不错的工作!每种方法解释如下:守卫验证器方法

步骤2)配置验证方

要完成这个操作,将类注册为服务:

  • YAML
  • XML
  • PHP
1 2 3 4
# app / config / services.yml服务:app.token_authenticator:类:AppBundle \安全\ TokenAuthenticator

最后,配置防火墙关键在security.yml使用此验证器:

  • YAML
  • XML
  • PHP
12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20
# app / config / security.yml安全:#……防火墙:#……主要:匿名:注销:警卫:身份验证器:-app.token_authenticator#如果你愿意,禁用在会话中存储用户#无状态:true#也许还有其他东西,比如form_login, remember_me等等#……

你做到了!现在您已经有了一个正常工作的API令牌身份验证系统。如果需要你的主页ROLE_USER,然后可以在不同的条件下进行测试:

1 2 3 4 5 6 7 8 9 10 11
#测试没有标记curl http://localhost: 8000 /# {"message":"Authentication Required"}#测试一个坏的标记curl - h“X-AUTH-TOKEN:假”http://localhost:8000/# {"message":"无法找到用户名。"}使用一个工作令牌进行测试curl - h“X-AUTH-TOKEN:真正的”http://localhost:8000/#主页控制器执行:页面正常加载

现在,进一步了解每种方法的功能。

守卫验证器方法

每个验证方需要使用以下方法:

美元getCredentials(请求请求)
这将被调用每一个请求,您的任务是从请求中读取令牌(或任何“身份验证”信息)并返回它。如果你回来,则跳过其余的身份验证过程。否则,getUser ()将被调用,返回值将作为第一个参数传递。
getUser($credentials, UserProviderInterface $userProvider)
如果getCredentials ()返回一个非空值,然后调用此方法并将其返回值作为美元的凭证论点。您的任务是返回一个实现的对象用户界面.如果你愿意,那么checkCredentials ()将被调用。如果你回来(或者扔一个AuthenticationException)验证将失败。
checkCredentials($credentials, UserInterface $user)
如果getUser ()返回一个User对象,此方法被调用。你的工作是验证凭证是否正确。对于登录表单,这是检查用户密码是否正确的地方。若要通过认证,返回真正的.如果你回来任何东西Else(或throw anAuthenticationException),验证将失败。
onAuthenticationSuccess(请求$ Request, TokenInterface $token, $providerKey)
这是在成功验证后调用的,您的工作是返回响应对象,该对象将被发送到客户机或继续请求(例如,允许像正常一样调用路由/控制器)。由于这是一个每个请求都要对自己进行身份验证的API,所以您希望返回
onAuthenticationFailure(请求$ Request, AuthenticationException $exception)
如果身份验证失败,则调用此方法。你的任务是归还响应应该发送给客户端的对象。的美元的例外会告诉你什么身份验证时出错。
开始(请求$请求,AuthenticationException $authException = null)
如果客户端访问一个需要身份验证的URI/资源,但没有发送身份验证详细信息(即您返回),则调用此方法getCredentials ()).你的任务是返回一个响应对象,帮助用户进行身份验证(例如,表示“令牌缺失!”的401响应)。
supportsRememberMe ()
如果你想支持“remember me”功能,从这个方法返回true。你仍然需要激活remember_me在你的防火墙下才能生效。由于这是一个无状态API,因此在本例中不希望支持“remember me”功能。
createAuthenticatedToken(UserInterface $user,字符串$providerKey)
如果您正在实现GuardAuthenticatorInterface而不是扩展AbstractGuardAuthenticator类时,您必须实现此方法。在成功进行身份验证之后,将调用它为用户创建并返回令牌,该用户是作为第一个参数提供的。

下图显示了Symfony如何调用Guard Authenob娱乐下载ticator方法:

自定义错误消息

onAuthenticationFailure ()是调用,它是传递一个AuthenticationException描述如何通过其异常- > getMessageKey ()(和异常- > getMessageData ())方法。信息会因不同而不同在哪里身份验证失败(即getUser ()checkCredentials ()).

方法可以轻松返回自定义消息CustomUserMessageAuthenticationException.你可以把这个扔出去getCredentials ()getUser ()checkCredentials ()导致失败:

12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
/ / src / AppBundle /安全/ TokenAuthenticator.php/ /……使用ob娱乐下载组件安全核心异常CustomUserMessageAuthenticationExceptionTokenAuthenticator扩展AbstractGuardAuthenticator/ /……公共函数getCredentials(请求请求/ /……如果令牌= =“ILuvAPIs”){CustomUserMessageAuthenticationException (“ILuvAPIs不是真正的API密钥:它只是一个愚蠢的短语”);}/ /……/ /……

在这种情况下,由于"ILuvAPIs"是一个可笑的API键,如果有人尝试这样做,你可以包括一个彩蛋来返回一个自定义消息:

1 2
curl - h“X-AUTH-TOKEN: ILuvAPIs”http://localhost:8000/# {"message":"ILuvAPIs不是一个真正的API键:它只是一个愚蠢的短语"}

避免对每个请求都进行浏览器认证

如果您创建了一个浏览器使用的Guard登录系统,而您的会话或CSRF令牌遇到了问题,则原因可能是验证器的不良行为。当Guard身份验证器要由浏览器使用时,您应该这样做验证用户的身份每一个请求。换句话说,你需要确保getCredentials ()方法只有返回一个非空值需要验证用户身份。为什么?因为,当getCredentials ()返回一个非空值,出于安全考虑,用户的会话被“迁移”到一个新的会话id。

这是一个边缘情况,除非您遇到会话或CSRF令牌问题,否则可以忽略它。下面是一个好行为和坏行为的例子:

12 3 4 5 6 7 8 9 10 11 12
公共函数getCredentials(请求请求//良好行为:只在特定路由上验证如果请求->属性->get (“_route”) = = !“login_route”| | !请求->isMethod (“职位”)) {返回;}例如,你的登录系统通过用户的IP地址进行认证//错误行为:认证将在每个请求上执行//即使用户已经通过身份验证(由于会话)返回数组“知识产权”= >请求->getClientIp ());}

当基于浏览器的身份验证程序试图对用户进行身份验证时,就会出现问题每一个类似于上面基于IP地址的例子中的request。有两种可能的解决方法:

  1. 如果有的话需要身份验证存储在会话中,设置无状态:真在你的防火墙下。
  2. 如果用户已经通过身份验证,更新身份验证器以避免身份验证:
12 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
// src/Security/ myipauthentication .php //…+使用Symob娱乐下载fony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;MyIpAuthenticator类+ private $tokenStorage;+公共函数__construct(TokenStorageInterface $tokenStorage)+ {+ $this->tokenStorage = $tokenStorage;+}公共函数getCredentials(请求$请求){+ //如果已经有一个身份验证的用户(可能是由于会话)+ //然后返回null并跳过认证:不需要。+ $user = $this->tokenStorage->getToken() ?$this->tokenStorage->getToken()->getUser(): null;+ if (is_object($user)) {+返回null;+}返回数组('ip' => $request->getClientIp());}}

你还需要更新你的服务配置来传递令牌存储:

  • YAML
  • XML
  • PHP
1 2 3 4 5
# app / config / services.yml服务:app.token_authenticator:类:AppBundle \安全\ TokenAuthenticator参数:(“@security.token_storage”)

常见问题

我可以拥有多个身份验证器吗?

是的!但当你这么做的时候,你需要选择一个验证器作为您的“entry_point”。这意味着你需要做出选择哪一个身份验证的start ()方法在匿名用户试图访问受保护资源时调用。例如,假设你有一个app.form_login_authenticator它处理传统的表单登录。当用户匿名访问受保护的页面时,需要使用start ()方法,并重定向到登录页面(而不是返回JSON响应):

  • YAML
  • XML
  • PHP
12 3 4 5 6 7 8 9 10 11 12 13 14 16 17 18 19 20 21 22
# app / config / security.yml安全:#……防火墙:#……主要:匿名:注销:警卫:entry_point:app.form_login_authenticator身份验证器:-app.token_authenticator-app.form_login_authenticator#如果你愿意,禁用在会话中存储用户#无状态:true#也许还有其他东西,比如form_login, remember_me等等#……
我可以使用这个form_login吗?
是的!form_login一个验证用户身份的方法,这样您就可以使用它而且然后添加一个或多个身份验证器。使用保护身份验证器不会与其他身份验证方法冲突。
我可以用FOSUserBundle吗?
是的!实际上,FOSUserBundle并不处理安全性:它只是给您一个用户对象和一些路由和控制器来帮助登录、注册、忘记密码等。当您使用FOSUserBundle时,您通常使用form_login实际验证用户。您可以继续这样做(参见前一个问题)或使用用户从FOSUserBundle中创建一个或多个验证器(就像本文中一样)。
此工作,包括代码示例,是根据创作共用BY-SA 3.0许可证。