有趣的是,在这里登录和注册,在你没有任何配置的情况下,也已经做好了重定向。但是,如果我们的需求有所变化,怎么办呢?一切都很简单。
默认情况下:
- 如果一个用户去访问一个页面,当这个用户还没有登录时,这个页面要求用户必须登录,一旦用户登录成功,就会返回这个页面。
- 如果一个用户去访问一个页面,当这个用户还没有注册时,这个页面要求用户必须注册,注册完成后,会有一个点击返回的连接,点击链接你可以返回之前访问的页面。
当然,我们可以定制和调整这个流程,来满足我们的任何需求。
大多数情况下,我们的修改不只是针对fosuserbundle。这个配置可以应用于任何的form_login。唯一不同的就是FOSUserBundle的注册流程。
如果你不知道到底有哪些配置选项你可以看看:Symfony Security Bundle Coufiguration Reference
我们感兴趣的就是form_login部分:
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 |
# app/config/security.yml security: firewalls: # Required somename: form_login: # submit the login form here check_path: /login_check # the user is redirected here when they need to log in login_path: /login # if true, forward the user to the login form instead of redirecting use_forward: false # login success redirecting options (read further below) always_use_default_target_path: false default_target_path: / target_path_parameter: _target_path use_referer: false # login failure redirecting options (read further below) failure_path: /foo failure_forward: false failure_path_parameter: _failure_path failure_handler: some.service.id success_handler: some.service.id # field names for the username and password fields username_parameter: _username password_parameter: _password # csrf token options csrf_parameter: _csrf_token intention: authenticate csrf_provider: my.csrf_provider.id # by default, the login form *must* be a POST, not a GET post_only: true remember_me: false # by default, a session must exist before submitting an authentication request # if false, then Request::hasPreviousSession is not called during authentication # new in Symfony 2.3 require_previous_session: true |
你提供的地址可以是一个URI,一个绝对路径,或者是一个Symfony路由名称。
一个例子说明了这一切:
1 2 3 4 5 6 7 8 9 |
# app/config/security.yml security: firewalls: somename: form_login: # One of the following: default_target_path: /some-page/on/our-website # default_target_path: http://www.bbc.co.uk # default_target_path: a_symfony_route_name |
这就说明这些配置都是有效的,但你只能使用一个,其他两个就只能注释掉了。
默认的Target Path
也许这个default_target_path配置是众多设置中比较容易混乱的一个,你可能会认为登录成功后总会跳转到这个链接上,因为他是默认的。
如果你设置always_use_default_target_path为true,的确是这样的,但是默认情况下,always_use_default_target_path为false。
那么问题来了,为什么两个配置在做相似的事情?
答案就在这里,我们找到了下面的一些代码。default_target_path主要是在用户不知道自己要发送到哪里时使用:
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 |
// /vendor/symfony/symfony/src/Symfony/Component/Security/Http/Authentication/DefaultAuthenticationSuccessHandler.php /** * Builds the target URL according to the defined options. * * @param Request $request * * @return string */ protected function determineTargetUrl(Request $request) { if ($this->options['always_use_default_target_path']) { return $this->options['default_target_path']; } if ($targetUrl = $request->get($this->options['target_path_parameter'], null, true)) { return $targetUrl; } if (null !== $this->providerKey && $targetUrl = $request->getSession()->get('_security.'.$this->providerKey.'.target_path')) { $request->getSession()->remove('_security.'.$this->providerKey.'.target_path'); return $targetUrl; } if ($this->options['use_referer'] && ($targetUrl = $request->headers->get('Referer')) && $targetUrl !== $this->httpUtils->generateUri($request, $this->options['login_path'])) { return $targetUrl; } return $this->options['default_target_path']; } |
注意,这个片段解释了如果use_referer设置为true的处理:
1 2 3 4 5 6 |
# app/config/security.yml security: firewalls: somename: form_login: use_referer: true |
默认情况下,use_referer是false,如果设置为true,Referer头就会被检查:
‘HTTP_REFERER’跳转到当前页面的连接,也就是上一页的连接。但是他可以被篡改,所以不是那么可靠。
你会发现:
1 |
$this->httpUtils->generateUri($request, $this->options['login_path']) |
如果你的登录页设置的login_path是/login,你会发现这句话的返回值也是/login,$request->headers->get(‘Referer’)也是/login,所以不能返回上一页。那么他会用在什么情况下呢?例如:你的登录表单嵌入在文章中,比如回复需要登录,那么这种条件就成立了,也就是说他不能与你安全配置的login_path相同。
default_target_path 一般用在用户直接访问登录页面,例如用户从浏览器直接输入登录页面网址,这种情况下我们的REFERER并没有任何的上一页或者其他页面的信息,这种情况下登录成功,会回到default_target_path指定的页面。例如:首页。
注册流程
你可能很少关注,用户第一次注册页面的路径问题。
我们要让注册成功后,来到一个页面,这个页面有一个返回”以前页面“的连接。
这个就要依赖FOSUserBundle了,需要处理twig模板的逻辑:
1 2 3 4 5 6 7 8 9 10 11 |
<!-- /app/Resources/FOSUserBundle/views/Registration/confirmed.html.twig --> {% extends "FOSUserBundle::layout.html.twig" %} {% trans_default_domain 'FOSUserBundle' %} {% block fos_user_content %} <p>{{ 'registration.confirmed'|trans({'%username%': user.username}) }}</p> {% if targetUrl %} <p><a href="{{ targetUrl }}">{{ 'registration.back'|trans }}</a></p> {% endif %} {% endblock fos_user_content %} |
注意在这里,我们检查了, targetUrl是否存在。它来自于FOSUserBundle Registration controller内部:
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 |
// /vendor/friendsofsymfony/user-bundle/Controller/RegistrationController.php /** * Tell the user his account is now confirmed */ public function confirmedAction() { $user = $this->getUser(); if (!is_object($user) || !$user instanceof UserInterface) { throw new AccessDeniedException('This user does not have access to this section.'); } return $this->render('FOSUserBundle:Registration:confirmed.html.twig', array( 'user' => $user, 'targetUrl' => $this->getTargetUrlFromSession(), )); } private function getTargetUrlFromSession() { // Set the SecurityContext for Symfony <2.6 if (interface_exists('Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface')) { $tokenStorage = $this->get('security.token_storage'); } else { $tokenStorage = $this->get('security.context'); } $key = sprintf('_security.%s.target_path', $tokenStorage->getToken()->getProviderKey()); if ($this->get('session')->has($key)) { return $this->get('session')->get($key); } } |
target_path关键在于,这个最后请求的连接,被存储在你的session中。
默认情况下,最后请求的连接(上一页的连接)会被默认存储在session的_security.main.target_path中(main是防火墙的名称,在security.yml中)。成功登录后,用户被重定向到这个路径上,帮助用户从这个页面继续开始
在FOSUSERBUNDLE登录成功后,重定向到上一页
在安全配置中,form_login:会有use_referer: true的配置,这个配置的默认行为是登录后跳转到上次访问的页面。可是use_referer配置只在session没有存储以前的URL时生效。如果你的登录页是/login,你会发现这句话的返回值也是/login,$request->headers->get(‘Referer’)也是/login,所以不能返回上一页。那么他会用在什么情况下呢?例如:你的登录表单嵌入在文章中,比如回复需要登录,那么这种条件就成立了,也就是说他不会与你安全配置的login_path相同。所有use_referer不会生效,即使生效了也不对。
触发你的登录页面,你可以看看debugger中session是否存在_security.yourfirewall.target_path?如果有,登录后,会首先选择此链接跳转。
有些情况下是没有的,那你怎么办。你可以使用在表单中添加,_target_path隐藏字段的方法,来指定登录后的跳转页面:
1 2 3 |
{% if app.session.get('_security.main.target_path') == null and app.request.headers.get('referer') != null or app.session.get('_security.main.target_path') == null and app.request.headers.get('referer') != '' %} <input type="hidden" name="_target_path" value="{{ app.request.headers.get('referer') }}" /> {% endif %} |