06/12/2017 - SYMFONY
Bu örnekte API anahtarı ile istekleri doğrulama işlemini göreceğiz. Bu çok basit bir örnek olacak ama siz eğer isterseniz, API anahtarı haricinde ekstradan anahtar kullanabilirsiniz. API anahtarını kullanıp veritabanında kullanıcı arayacağız. Eğer kullanıcı mevcut ise, istek geçerli olacak ama eğer mevcut değil ise, istek geçersiz olacaktır.
GET /v1/customers
- Tüm müşterileri verir. Gerekli role: ROLE_API_USER
GET /v1/customers/{id}
- Müşteri'yi id'ye göre verir. Gerekli role: ROLE_API_USER
POST /v1/customers
- Yeni müşteri yaratır. Gerekli role: ROLE_ADMIN_USER
PATCH /v1/customers/{id}
- Müşteri'yi id'ye göre yeniler. Gerekli role: ROLE_ADMIN_USER
DELETE /v1/customers/{id}
- Müşteri'yi id'ye göre siler. Gerekli role: ROLE_ADMIN_USER
customer:
resource: "@CustomerBundle/Controller/"
type: annotation
prefix: /v1/
security:
role_hierarchy:
ROLE_API_USER: ROLE_USER
ROLE_ADMIN_USER: ROLE_USER
providers:
api_key_user_provider:
id: customer.security.api_key_user_provider
firewalls:
api:
pattern: ^/
stateless: true
simple_preauth:
authenticator: customer.security.api_key_authenticator
provider: api_key_user_provider
namespace CustomerBundle\Controller;
use CustomerBundle\Security\ApiUserHelper;
use CustomerBundle\Service\CustomerService;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
/**
* @Route("/customers", service="customer.controller.customer")
*/
class CustomerController
{
private $apiUserHelper;
private $customerService;
public function __construct(
ApiUserHelper $apiUserHelper,
CustomerService $customerService
) {
$this->apiUserHelper = $apiUserHelper;
$this->customerService = $customerService;
}
/**
* @Method({"GET"})
* @Route("")
* @Security("has_role('ROLE_API_USER')")
*
* @return Response
*/
public function getAllAction()
{
$result = $this->customerService->getAll();
return new Response(json_encode(['user' => $this->getUser(), 'result' => $result]));
}
/**
* @param int $id
*
* @Method({"GET"})
* @Route("/{id}", requirements={"id"="\d+"})
* @Security("has_role('ROLE_API_USER')")
*
* @return Response
*/
public function getOneAction($id)
{
$result = $this->customerService->getOne($id);
return new Response(json_encode($result));
}
/**
* @param Request $request
*
* @Method({"POST"})
* @Route("")
* @Security("has_role('ROLE_ADMIN_USER')")
*
* @return Response
*/
public function createOneAction(Request $request)
{
$payload = json_decode($request->getContent(), true);
$result = $this->customerService->createOne($payload);
return new Response($result, Response::HTTP_CREATED);
}
/**
* @param int $id
* @param Request $request
*
* @Method({"PATCH"})
* @Route("/{id}", requirements={"id"="\d+"})
* @Security("has_role('ROLE_ADMIN_USER')")
*
* @return Response
*/
public function updateOneAction(Request $request, $id)
{
$payload = json_decode($request->getContent(), true);
$this->customerService->updateOne($payload, $id);
return new Response();
}
/**
* @param int $id
*
* @Method({"DELETE"})
* @Route("/{id}", requirements={"id"="\d+"})
* @Security("has_role('ROLE_ADMIN_USER')")
*
* @return Response
*/
public function deleteOneAction($id)
{
$this->customerService->deleteOne($id);
return new Response();
}
private function getUser()
{
$user = $this->apiUserHelper->get();
$user = [
'api_key' => $user->getApiKey(),
'roles' => $user->getRoles(),
];
return $user;
}
}
services:
customer.controller.customer:
class: CustomerBundle\Controller\CustomerController
arguments:
- "@customer.security.api_user_helper"
- "@customer.service.customer"
namespace CustomerBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Security\Core\User\UserInterface;
/**
* @ORM\Entity(repositoryClass="CustomerBundle\Repository\ApiUserRepository")
* @ORM\Table(
* name="api_user",
* uniqueConstraints={
* @ORM\UniqueConstraint(name="unq_api_key", columns={"api_key"})
* }
* )
*/
class ApiUser implements UserInterface
{
/**
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
* @ORM\Column(name="id", type="integer")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="api_key", type="string", length=36)
*/
private $apiKey;
/**
* @var array
*
* @ORM\Column(name="roles", type="array")
*/
private $roles;
public function __construct($apiKey, array $roles)
{
$this->apiKey = $apiKey;
$this->roles = $roles;
}
public function getId()
{
return $this->id;
}
public function getApiKey()
{
return $this->apiKey;
}
public function getRoles()
{
return $this->roles;
}
public function getUsername()
{
}
public function getPassword()
{
}
public function getSalt()
{
}
public function eraseCredentials()
{
}
}
namespace CustomerBundle\Repository;
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\Query;
class ApiUserRepository extends EntityRepository
{
public function findOneByApiKey($apiKey)
{
return $this->createQueryBuilder('a')
->where('a.apiKey = :apiKey')
->setParameter('apiKey', $apiKey)
->getQuery()
->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true)
->getOneOrNullResult(Query::HYDRATE_SIMPLEOBJECT);
}
}
services:
customer.repository.api_user:
class: CustomerBundle\Repository\ApiUserRepository
factory: [ "@doctrine.orm.entity_manager", getRepository ]
arguments:
- CustomerBundle\Entity\ApiUser
namespace CustomerBundle\Security;
use CustomerBundle\Entity\ApiUser;
use CustomerBundle\Exception\ApiException;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\Security\Core\User\UserInterface;
class ApiUserHelper
{
private $tokenStorage;
public function __construct(
TokenStorageInterface $tokenStorage
) {
$this->tokenStorage = $tokenStorage;
}
/**
* @return UserInterface|ApiUser
*/
public function get()
{
$user = $this->tokenStorage->getToken()->getUser();
if (!$user instanceof UserInterface) {
throw new ApiException('API user not found.');
}
return $user;
}
}
namespace CustomerBundle\Exception;
use RuntimeException;
class ApiException extends RuntimeException
{
}
namespace CustomerBundle\Security;
use CustomerBundle\Entity\ApiUser;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\PreAuthenticatedToken;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\AuthenticationFailureHandlerInterface;
use Symfony\Component\Security\Http\Authentication\SimplePreAuthenticatorInterface;
class ApiKeyAuthenticator implements SimplePreAuthenticatorInterface, AuthenticationFailureHandlerInterface
{
public function createToken(Request $request, $providerKey)
{
$apiKey = $request->headers->get('x-auth-token');
if (!$apiKey) {
throw new BadCredentialsException();
}
return new PreAuthenticatedToken('anon.', $apiKey, $providerKey);
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof PreAuthenticatedToken && $token->getProviderKey() === $providerKey;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
if (!$userProvider instanceof ApiKeyUserProvider) {
throw new InvalidArgumentException(
sprintf(
'The user provider must be an instance of ApiKeyUserProvider. The "%s" was given.',
get_class($userProvider)
)
);
}
$user = $userProvider->loadUserByUsername($token->getCredentials());
if (!$user instanceof ApiUser) {
throw new CustomUserMessageAuthenticationException('Invalid API key.');
}
return new PreAuthenticatedToken($user, $user->getApiKey(), $providerKey, $user->getRoles());
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new Response($exception->getMessageKey(), Response::HTTP_UNAUTHORIZED);
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
{
return null;
}
}
namespace CustomerBundle\Security;
use CustomerBundle\Repository\ApiUserRepository;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
class ApiKeyUserProvider implements UserProviderInterface
{
private $apiUserRepository;
public function __construct(ApiUserRepository $apiUserRepository)
{
$this->apiUserRepository = $apiUserRepository;
}
public function loadUserByUsername($apiKey)
{
return $this->apiUserRepository->findOneByApiKey($apiKey);
}
public function refreshUser(UserInterface $user)
{
throw new UnsupportedUserException();
}
public function supportsClass($class)
{
return User::class === $class;
}
}
services:
customer.security.api_key_authenticator:
class: CustomerBundle\Security\ApiKeyAuthenticator
customer.security.api_key_user_provider:
class: CustomerBundle\Security\ApiKeyUserProvider
arguments:
- "@customer.repository.api_user"
customer.security.api_user_helper:
class: CustomerBundle\Security\ApiUserHelper
arguments:
- "@security.token_storage"
CREATE TABLE api_user (
id INTEGER NOT NULL,
api_key VARCHAR (36) NOT NULL,
roles CLOB NOT NULL,
PRIMARY KEY (
id
)
);
id api_key roles
1 api12345-449c-4e49-87ff-82db2da75715 ROLE_API_USER, ROLE_API_USER
2 admin123-449c-4e49-87ff-82db2da75715 ROLE_API_USER, ROLE_API_USER, ROLE_ADMIN_USER
Kayıp x-auth-token
header.
$ curl -I -X GET \
http://192.168.99.10:8081/app_test.php/v1/customers
HTTP/1.1 401 Unauthorized
Invalid credentials.
Geçersiz x-auth-token
API anahtarı.
$ curl -I -X GET \
http://192.168.99.10:8081/app_test.php/v1/customers \
-H 'x-auth-token: invalid'
HTTP/1.1 401 Unauthorized
Invalid API key.
Geçerli API anahtarı ama kayıp ROLE_ADMIN_USER
role.
$ curl -X POST \
http://192.168.99.10:8081/app_test.php/v1/customers \
-H 'content-type: application/json' \
-H 'x-auth-token: api12345-449c-4e49-87ff-82db2da75715' \
-d '{"name":"Customer 1","dob":"2017-01-02"}'
HTTP/1.1 403 Forbidden
Mevcut ROLE_API_USER
role ve API anahtarı.
$ curl -X GET \
http://192.168.99.10:8081/app_test.php/v1/customers \
-H 'x-auth-token: api12345-449c-4e49-87ff-82db2da75715'
HTTP/1.1 200 OK
{"user":{"api_key":"api12345-449c-4e49-87ff-82db2da75715","roles":["ROLE_USER","ROLE_API_USER"]},"result":[{"id":1,"name":"Customer 1","dob":{"date":"2017-01-02 00:00:00.000000","timezone_type":3,"timezone":"UTC"}}]}
Mevcut ROLE_ADMIN_USER
role ve API anahtarı.
$ curl -X POST \
http://192.168.99.10:8081/app_test.php/v1/customers \
-H 'content-type: application/json' \
-H 'x-auth-token: admin123-449c-4e49-87ff-82db2da75715' \
-d '{"name":"Customer 1","dob":"2017-01-02"}'
HTTP/1.1 201 Created