tutorial example symfony oauth-2.0 fosoauthserverbundle

tutorial - symfony 4 rest api example



¿Cómo implementar FosOAuthServerBundle para asegurar una API REST? (1)

También encontré que la documentación puede ser un poco confusa. Pero después de muchas horas de intentarlo, lo descubrí con la ayuda de este blog (actualización: el blog ya no existe, cambiado a Internet Archive). En su caso, no necesita una entrada de firewall para ^/oauth/v2/auth porque esto es para la página de autorización. Debe recordar lo que OAuth puede hacer ... se utiliza para algo más que una APLICACIÓN REST. Pero si una APO REST es lo que desea proteger, no la necesita. Aquí hay una configuración de firewall de ejemplo de mi aplicación:

firewalls: oauth_token: pattern: ^/oauth/v2/token security: false api_firewall: pattern: ^/api/.* fos_oauth: true stateless: true anonymous: false secure_area: pattern: ^/ fos_oauth: true form_login: provider: user_provider check_path: /oauth/v2/auth_login_check login_path: /oauth/v2/auth_login logout: path: /logout target: / anonymous: ~ access_control: - { path: ^/oauth/v2/auth_login$, role: IS_AUTHENTICATED_ANONYMOUSLY } - { path: ^/, roles: IS_AUTHENTICATED_FULLY }

Tenga en cuenta que necesita definir un proveedor de usuario. Si usa FOSUserBundle, ya hay un proveedor de usuario creado para usted. En mi caso, hice la mía y creé un servicio a partir de ella.

Y en mi config.yml:

fos_oauth_server: db_driver: orm client_class: BB/AuthBundle/Entity/Client access_token_class: BB/AuthBundle/Entity/AccessToken refresh_token_class: BB/AuthBundle/Entity/RefreshToken auth_code_class: BB/AuthBundle/Entity/AuthCode service: user_provider: platform.user.provider options: supported_scopes: user

También debo mencionar que las tablas que crea en la base de datos (access_token, client, auth_code, refresh_token) deben tener más campos de los que se muestran en los documentos ...

Tabla de tokens de acceso: id (int), client_id (int), user_id (int), token (cadena), scope (cadena), expires_at (int)

Tabla de clientes: id (int), random_id (cadena), secret (cadena), redirect_urls (cadena), allowed_grant_types (cadena)

Tabla de códigos de autenticación: id (int), client_id (int), user_id (int)

Actualizar tabla de tokens : id (int), client_id (int), user_id (int), token (cadena), expires_at (int), scope (cadena)

Estas tablas almacenarán la información necesaria para OAuth, por lo tanto, actualice sus entidades Doctrine para que coincidan con las tablas db como se muestra arriba.

Y luego necesita una manera de generar realmente el secreto y client_id, por lo que es donde entra la sección "Creating a Client" de los documentos, aunque no es muy útil ...

Cree un archivo en /src/My/AuthBundle/Command/CreateClientCommand.php (deberá crear la carpeta Command ) Este código pertenece al artículo al que he vinculado anteriormente y muestra un ejemplo de lo que puede poner en este archivo:

<?php # src/Acme/DemoBundle/Command/CreateClientCommand.php namespace Acme/DemoBundle/Command; use Symfony/Bundle/FrameworkBundle/Command/ContainerAwareCommand; use Symfony/Component/Console/Input/InputArgument; use Symfony/Component/Console/Input/InputOption; use Symfony/Component/Console/Input/InputInterface; use Symfony/Component/Console/Output/OutputInterface; class CreateClientCommand extends ContainerAwareCommand { protected function configure() { $this ->setName(''acme:oauth-server:client:create'') ->setDescription(''Creates a new client'') ->addOption( ''redirect-uri'', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, ''Sets redirect uri for client. Use this option multiple times to set multiple redirect URIs.'', null ) ->addOption( ''grant-type'', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, ''Sets allowed grant type for client. Use this option multiple times to set multiple grant types..'', null ) ->setHelp( <<<EOT The <info>%command.name%</info>command creates a new client. <info>php %command.full_name% [--redirect-uri=...] [--grant-type=...] name</info> EOT ); } protected function execute(InputInterface $input, OutputInterface $output) { $clientManager = $this->getContainer()->get(''fos_oauth_server.client_manager.default''); $client = $clientManager->createClient(); $client->setRedirectUris($input->getOption(''redirect-uri'')); $client->setAllowedGrantTypes($input->getOption(''grant-type'')); $clientManager->updateClient($client); $output->writeln( sprintf( ''Added a new client with public id <info>%s</info>, secret <info>%s</info>'', $client->getPublicId(), $client->getSecret() ) ); } }

Luego, para crear realmente el client_id y el secreto, ejecuta este comando desde la línea de comando (esto insertará en la base de datos los ids necesarios y demás):

php app/console acme:oauth-server:client:create --redirect-uri="http://clinet.local/" --grant-type="password" --grant-type="refresh_token" --grant-type="client_credentials"

observe que el acme:oauth-server:client:create puede ser lo que sea que realmente nombre su comando en el archivo CreateClientCommand.php con $this->setName(''acme:oauth-server:client:create'') .

Una vez que tenga el client_id y el secreto, estará listo para autenticarse. Haga una solicitud en su navegador que sea algo como esto:

http://example.com/oauth/v2/token?client_id=[CLIENT_ID_YOU GENERATED]&client_secret=[SECRET_YOU_GENERATED]&grant_type=password&username=[USERNAME]&password=[PASSWORD]

Esperemos que funcione para usted. Definitivamente hay mucho para configurar, solo trata de ir paso a paso.

También escribí una clase PHP simple para llamar a mi apéndice REST de Symfony usando oAuth, si crees que sería útil, házmelo saber y puedo transmitirlo.

ACTUALIZAR

En respuesta a sus preguntas adicionales:

El "cliente" se describe en el mismo blog, solo un artículo diferente. Lea la sección Clientes y alcances aquí, debe aclararle qué es un cliente. Como se menciona en el artículo, no necesita un cliente para cada usuario. Puede tener un solo cliente para todos sus usuarios si lo desea.

De hecho, también estoy usando la autenticación Symfony clásica para mi sitio frontend, pero eso puede cambiar en el futuro. Así que siempre es bueno tener estas cosas en mente, pero no diría que es extraño combinar los dos métodos.

Refresh_token se utiliza cuando access_token ha caducado y desea solicitar un nuevo access_token sin volver a enviar las credenciales del usuario. en su lugar, envía el token de actualización y obtiene un nuevo access_token. Esto no es realmente necesario para una API REST porque una única solicitud probablemente no tomará el tiempo suficiente para que access_token expire.

oAuth1 y oAuth2 son muy diferentes, así que supongo que el método que usas no funcionaría, pero nunca lo intenté. Pero solo para probar, puede hacer una solicitud GET o POST normal, siempre y cuando pase el access_token=[ACCESS_TOKEN] en la cadena de consulta GET (para todos los tipos de solicitudes, en realidad).

Pero de todos modos, aquí está mi clase. Usé un archivo de configuración para almacenar algunas variables, y no implementé la capacidad de ELIMINAR, pero eso no es demasiado difícil.

class RestRequest{ private $token_url; private $access_token; private $refresh_token; private $client_id; private $client_secret; public function __construct(){ include ''config.php''; $this->client_id = $conf[''client_id'']; $this->client_secret = $conf[''client_secret'']; $this->token_url = $conf[''token_url'']; $params = array( ''client_id''=>$this->client_id, ''client_secret''=>$this->client_secret, ''username''=>$conf[''rest_user''], ''password''=>$conf[''rest_pass''], ''grant_type''=>''password'' ); $result = $this->call($this->token_url, ''GET'', $params); $this->access_token = $result->access_token; $this->refresh_token = $result->refresh_token; } public function getToken(){ return $this->access_token; } public function refreshToken(){ $params = array( ''client_id''=>$this->client_id, ''client_secret''=>$this->client_secret, ''refresh_token''=>$this->refresh_token, ''grant_type''=>''refresh_token'' ); $result = $this->call($this->token_url, "GET", $params); $this->access_token = $result->access_token; $this->refresh_token = $result->refresh_token; return $this->access_token; } public function call($url, $method, $getParams = array(), $postParams = array()){ ob_start(); $curl_request = curl_init(); curl_setopt($curl_request, CURLOPT_HEADER, 0); // don''t include the header info in the output curl_setopt($curl_request, CURLOPT_RETURNTRANSFER, 1); // don''t display the output on the screen $url = $url."?".http_build_query($getParams); switch(strtoupper($method)){ case "POST": // Set the request options for POST requests (create) curl_setopt($curl_request, CURLOPT_URL, $url); // request URL curl_setopt($curl_request, CURLOPT_POST, 1); // set request type to POST curl_setopt($curl_request, CURLOPT_POSTFIELDS, http_build_query($postParams)); // set request params break; case "GET": // Set the request options for GET requests (read) curl_setopt($curl_request, CURLOPT_URL, $url); // request URL and params break; case "PUT": // Set the request options for PUT requests (update) curl_setopt($curl_request, CURLOPT_URL, $url); // request URL curl_setopt($curl_request, CURLOPT_CUSTOMREQUEST, "PUT"); // set request type curl_setopt($curl_request, CURLOPT_POSTFIELDS, http_build_query($postParams)); // set request params break; case "DELETE": break; default: curl_setopt($curl_request, CURLOPT_URL, $url); break; } $result = curl_exec($curl_request); // execute the request if($result === false){ $result = curl_error($curl_request); } curl_close($curl_request); ob_end_flush(); return json_decode($result); } }

Y luego para usar la clase, simplemente:

$request = new RestRequest(); $insertUrl = "http://example.com/api/users"; $postParams = array( "username"=>"test", "is_active"=>''false'', "other"=>"3g12g53g5gg4g246542g542g4" ); $getParams = array("access_token"=>$request->getToken()); $response = $request->call($insertUrl, "POST", $getParams, $postParams);

Me gustaría proporcionar una API RESTful asegurada con OAuth2 utilizando FOSOAuthServerBundle y no estoy realmente seguro de lo que tengo que hacer.

Seguí los pasos básicos de la documentación, pero faltan algunas cosas y no puedo encontrar un ejemplo completo de lo que necesito.

Entonces, traté de entender lo mejor que pude este ejemplo de implementación (el único que encontré) pero todavía hay cosas que no entiendo.

Primero, ¿por qué necesitamos una página de inicio de sesión en una API? Supongamos que mi cliente es una aplicación para iPhone o Android, veo el interés de la página de inicio de sesión en la aplicación, pero creo que el cliente solo tiene que llamar a un servicio web desde la API para obtener su token, ¿me equivoco? Entonces, ¿cómo implementar la autorización y la entrega de tokens vía REST endpoint?

Entonces, la documentación dice que escriba este firewall:

oauth_authorize: pattern: ^/oauth/v2/auth # Add your favorite authentication process here

Y no sé cómo agregar un proceso de autenticación. ¿Debo escribir el mío, por ejemplo, siguiendo este tutorial o estoy completamente equivocado?

A nivel mundial, ¿alguien puede tomarse el tiempo de explicar el proceso necesario, después de los cinco pasos en los documentos, para proporcionar una API RESTful segura de OAuth2? Sería muy agradable ...

EDITAR después de la respuesta de @Sehael:

Todavía tengo algunas preguntas antes de que sea perfecto ...

¿Qué representa el "Cliente" aquí? Por ejemplo, ¿debería crear un cliente para una aplicación de iPhone y otro para una aplicación de Android? y ¿tengo que crear un nuevo cliente para cada instancia que quiera usar la API? ¿Cuál es la mejor práctica en este caso?

A diferencia de ti, no utilizo el proceso OAuth para el sitio web frontal, sino el modo symfony "clásico". ¿Te parece extraño o es normal?

¿Cuál es la utilidad de refresh_token? ¿Cómo usarlo?

Traté de probar mis nuevos servicios protegidos de OAuth. Utilicé la extensión de cromo POSTman, que es compatible con OAuth 1.0, ¿OAuth2 se parece a OAuth1 lo suficiente para probarse con POSTman? Hay un campo de "token secreto" que no sé cómo llenar. Si no puedo, me gustaría ver tu clase PHP (@Sehael), como propones. / Editar: OK, creo que encontré la respuesta para este. Acabo de agregar access_token como parámetro GET con el token como valor. Parece estar funcionando. Es lamentable que tenga que hacer un engenering inverso en el código del paquete para encontrarlo en lugar de leerlo en la documentación.

De todos modos, muchas gracias!