12

Edit: See below for my own solution, which is, at the time of writing, functioning but imperfect. Would love some criticism and feedback, if I get something put together that I feel is really solid then I'll make a howto blog post for other people facing the same challenge.

I've been struggling with this for days, and I'm hoping someone can let me know if I'm on the right path.

I have a system with a FOSRestBundle webservice in which I'm currently using FOSUserBundle and HWIOAuthBundle to authenticate users.

I would like to set up stateless api key authentication for the webservice.

I've read through http://symfony.com/doc/current/cookbook/security/api_key_authentication.html and this seems simple enough to implement, I've also installed UecodeApiKeyBundle which seems to be mostly just an implementation of this book page.

My question is a n00b one...what now? The book page and bundle both cover authenticating a user by API key, but don't touch on the flow of logging users in, generating API keys, allowing users to register, etc. What I would really like is simple API endpoints for login, register, and logout that my app developers can use. Something like /api/v1/login, etc.

I think I can handle registration....login is confusing me though. Based upon some additional reading, it seems to me like what I need to do for login is this:

  • Create a controller at api/v1/login that accepts POST requests. The request will either look like { _username: foo, _password: bar } or something like { facebook_access_token: foo. Alternately, the facebook login could require a different action, like /user/login/facebook, and just redirect to the HWIOAuthBundle path }.

  • If the request contains _username and _password parameters, then I need to forward the request to login-check (I'm not sure about this one. Can I just process this form myself? Or, should I manually check the username and password against the database?)

  • Add a login event listener, if user authenticated successfully, generate an api key for the user (This is only necessary if I'm not checking it myself, of course)

  • Return the API Key in the response of the POST request (This breaks the post-redirect-get strategy, but otherwise I don't see any issues with it) I think this eliminates the redirect to login-check option I listed above.

As you can probably see I'm confused. This is my first Symfony2 project, and the book pages on Security sound simple...but seem to gloss over some of the details and it's left me quite unsure of what way to proceed.

Thanks in advance!

=============================================================

Edit:

I've installed a API Key Authentication pretty much identically to the relevant cookbook article: http://symfony.com/doc/current/cookbook/security/api_key_authentication.html

To handle user's logging in, I've created a custom controller method. I doubt that this is perfect, I would love to hear some feedback on how it can be improved, but I do believe that I'm on the right path as my flow is now working. Here's the code (Please note, still early in development...I haven't looked at Facebook login yet, only simple username/password login):

class SecurityController extends FOSRestController
{

    /**
     * Create a security token for the user
     */

    public function tokenCreateAction()
    {
        $request = $this->getRequest();

        $username = $request->get('username',NULL);
        $password = $request->get('password',NULL);

        if (!isset($username) || !isset($password)){
            throw new BadRequestHttpException("You must pass username and password fields");
        }

        $um = $this->get('fos_user.user_manager');
        $user = $um->findUserByUsernameOrEmail($username);

        if (!$user instanceof \Acme\UserBundle\Entity\User) {
            throw new AccessDeniedHttpException("No matching user account found");
        }

        $encoder_service = $this->get('security.encoder_factory');
        $encoder = $encoder_service->getEncoder($user);
        $encoded_pass = $encoder->encodePassword($password, $user->getSalt());

        if ($encoded_pass != $user->getPassword()) {
            throw new AccessDeniedHttpException("Password does not match password on record");
        }


        //User checks out, generate an api key
        $user->generateApiKey();
        $em = $this->getDoctrine()->getEntityManager();
        $em->persist($user);
        $em->flush();

        return array("apiKey" => $user->getApiKey());
    }

}

This seems to work pretty well, and user registration will be handled similarly.

Interestingly to me, the api key authentication method I implemented from the cookbook appears to ignore the access_control settings in my security.yml file, in the cookbook they outline how to only generate the token for a specific path, but I didn't like that solution, so I've implemented my own (also somewhat poor) solution to not check the path I'm using to authenticate users

api_login:
    pattern: ^/api/v1/user/authenticate$
    security: false

api:
    pattern: ^/api/*
    stateless: true
    anonymous: true
    simple_preauth:
        authenticator: apikey_authenticator

I'm sure there's a better way to do this too, but again...not sure what it is.

4

3 回答 3

3

您正在尝试使用用户名和登录名实现无状态身份验证。这几乎就是 Oauth2 身份验证密码授予的作用。这是非常标准的,因此我建议您为此使用 Bundle,而不是尝试自己实现它,例如FOSOauthServerBundle。它可以使用 FOSUserBundle 作为其用户提供程序,并且比自制解决方案更清洁、更安全且更易于使用。

要注册用户,您可以在 API 中创建注册操作(例如,在 REST API 中,我将使用 POST - api/v1/users),并在控制器方法中复制并粘贴 FOSUserBundle:RegistrationController 中的代码(当然适应你的需要)。

我在 REST API 中做到了这一点,它就像一个魅力。

于 2015-07-16T08:04:08.427 回答
2

我认为您实际上并不需要 /login 端点。

在 symfony 文档中,api 客户端需要将其密钥(通过 apiKey http 参数)传递给对 API 的每个请求。

我不确定这是最佳实践,但你可以这样做。

"The book page and bundle both cover authenticating a user by API key, but don't touch on the flow of logging users in, generating API keys, allowing users to register"

最好的办法是允许您的用户通过 Web 表单进行注册(例如使用 fos_user_register 路由)。用户实体可以有一个 apikey 字段,预先填充了一个生成的密钥,例如 sha1("secret".time()) ,以及他们的配置文件中的一个按钮来重新生成密钥。

于 2014-09-25T07:22:06.917 回答
-1
Class GenearteToken extends FOSRestController
{

     public getTokenAction(Request $request){

          $apiKey = $request->query->get('apikey');
          return $apiKey;
      }


}
于 2017-01-23T07:21:24.510 回答