I have a situation where I need to run some code if the user tries accessing a protected URL and is not authenticated / authorized.
By default, Symfony handles unauthenticated users by redirecting or forwarding the user to a login form. I'd like to prevent this from happening if the request method is POST, and instead echo a JSON object.
The best way I can think of to handle this is to create a custom listener that listens to the kernel.request
event and checks two things:
- checks if the request method is POST
- checks if the user is fully authenticated
If it is a POST request, and the user is not fully authenticated, I would echo a JSON object.
But my listener is (expectedly) firing for all requests - I'd like to limit it to only check if the request is for a URL protected by the firewall. Is it possible to che开发者_开发百科ck this programatically?
I also have a nagging suspicion there's a simpler way to go about this, but can't figure it out, so if anyone has any tips, I'd love to hear them :)
Edit
@Problematic - The reason for checking only firewalled requests is because I have some requests that aren't firewalled, and if my code is fired, I'll receive the aforementioned JSON object instead of the request's real response.Right now if I'm not logged in and make a POST request to api/get/something
(which is behind the firewall), Symfony returns the HTML describing the login page. Instead, I want to just echo something like {error: 'User is not authorized'}
. But I only want this to happen for POST requests.
I think I was going about this the wrong way. I wanted to know if a URL was behind the firewall, but I think I should've been trying to find out if the current user was authorized for the request. In essense, knowing that a user is denied access to a URL means that the URL must be behind a firewall, otherwise access couldn't have been denied.
Keeping this in mind, I was able to get the end result I wanted. It's pretty simple once you realize how the security mechanism works...
Symfony\Component\Security\Http\Firewall
listens for thekernel.request
event- Firewall then calls a number of event listeners that are registered in
security.yml
- If any security violation (i.e. a user trying to access something without being logged in) is detected, an
AccessDeniedException
is thrown, and thekernel.exception
event is dispatched. Symfony/Component/Security/Http/Firewall/ExceptionListener
listens for the event and fires itsonKernelException
method which decides what the next step is. In my case, it would start the Authentication process
Since starting the Authentication process is what I wanted to avoid, I wrote my own event listener that intercepts kernel.exception
before Symfony's ExceptionListener
does. I gave my event listener a priority of 1.
This is the method I wrote:
public function handleException(GetResponseForExceptionEvent $event) {
$exception = $event->getException();
$request = $event->getRequest();
if ($request->getMethod() == 'POST') {
if ($exception instanceof AccessDeniedException) {
$response = new Response({err: 'not logged in'});
$event->setResponse($response);
}
}
}
As long as the user isn't authorized, and the request method is POST, a JSON object is returned (which also stops the event propogation) instead of the HTML for the login page. Otherwise, other listeners of kernel.exception
will react and Symfony can go about its business.
So, the original question remains unanswered, but I think it could be accomplished by checking if a user has access to an action. Symfony\Component\Security\Core\Authorization\AccessDecisionManager
looks like it'd be helpful for this.
Edit
I don't know if this method only handles users that aren't logged in. I haven't tested it yet, but I think it will also fire if a (logged in) user tries accessing an action that requires a role they haven't been granted. If this causes a problem, I would try modifying it to use Symfony\Component\Security\Core\Authentication\AuthenticationTrustResolver
's isFullFledged($token)
method to only care about users who aren't logged in.
精彩评论