API şeklinde dizayn edilen bu uygulamada, Basic Auth authentication kullanıyoruz ayrıca, login veya logout fonksiyonlarıda mevcut değil. Kullanıcılar sadece izinli oldukları adreslere girebilirler, aksi takdirde "401 Unauthorised" hatası alırlar. Kullanıcılar, roller, username, password ve URIler "security.yml" içinde belirtilmişlerdir. Daha fazla bilgi için buraya tıklayın.


Adresler, kullanıcılar ve ulaşım hakları



Security.yml


Bir kullanıcıya birden fazla rol atamak için roles: [ROLE_STUDENT, ROLE_LECTURER, ROLE_ADMIN] kullanabilirsiniz.


# app/config/security.yml
security:
# http://symfony.com/doc/current/book/security.html#encoding-the-user-s-password
encoders:
Symfony\Component\Security\Core\User\User:
algorithm: bcrypt
cost: 12

# http://symfony.com/doc/current/book/security.html#hierarchical-roles
role_hierarchy:
ROLE_STUDENT: ROLE_USER
ROLE_LECTURER: ROLE_STUDENT
ROLE_ADMIN: [ROLE_LECTURER, ROLE_ALLOWED_TO_SWITCH]

# http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
providers:
in_memory:
memory:
users:
student:
password: $2a$10$GaEjFjAlt4R5Sc3.rGUAXu087b/hN/mQqe0oDLGAzBRIxinYuCVq2 # student
roles: ROLE_STUDENT
lecturer:
password: $2a$10$GEOb1t0g5QusbzaJwtRfqOuVJhQAzH5jcJt1m487UO.DsnUmd6ul2 # lecturer
roles: ROLE_LECTURER
admin:
password: $2a$10$GogNFjqLFM8vlopZTdl3te2EoVsgX9EGDOilodUige/syc5vKFFwO # admin
roles: ROLE_ADMIN

# the main part of the security, where you can set up firewalls
# for specific sections of your app
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
# the default controller has to be accessible for everybody
unsecured:
pattern: ^/$
security: false
# secures part of the application
api_secured:
pattern: ^/api/
stateless: true
http_basic:
realm: "Inanzzz Webservice API"
provider: in_memory

# with these settings you can restrict or allow access for different parts
# of your application based on roles, ip, host or methods
# http://symfony.com/doc/current/cookbook/security/access_control.html
access_control:
- { path: ^/, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/api/, role: ROLE_STUDENT }

Konfigürasyon


Composer ile "jms/serializer-bundle" : "1.1.0" kütüphanesini kurun ve new JMS\SerializerBundle\JMSSerializerBundle(), satırını AppKernel dosyasına ekleyin.


Routing.yml


# app/config/routing.yml
application_api:
resource: "@ApplicationApiBundle/Controller"
prefix: /
type: annotation

Services.yml


# app/config/services.yml
services:
doctrine_common_inflector:
class: Doctrine\Common\Inflector\Inflector

Controllers.yml


# src/Application/ApiBundle/Resources/config/controllers.yml
services:
application_api.controller.abstract:
class: Application\ApiBundle\Controller\AbstractController
abstract: true
arguments:
- @serializer
- @validator
- @doctrine_common_inflector

application_api.controller.default:
class: Application\ApiBundle\Controller\DefaultController
parent: application_api.controller.abstract

application_api.controller.api:
class: Application\ApiBundle\Controller\ApiController
parent: application_api.controller.abstract

AbstractController


namespace Application\ApiBundle\Controller;

use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializationContext;
use JMS\Serializer\SerializerInterface;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\ConstraintViolationListInterface;
use Symfony\Component\Validator\Validator\ValidatorInterface;

abstract class AbstractController
{
private $validContentTypes = ['json' => 'application/json', 'xml' => 'application/xml'];

protected $serializer;
protected $validator;
protected $inflector;

public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
$this->serializer = $serializer;
$this->validator = $validator;
$this->inflector = $inflector;
}

/**
* @param string $contentType
*
* @return string|Response
*/
protected function validateContentType($contentType)
{
if (!in_array($contentType, $this->validContentTypes)) {
return $this->createFailureResponse(
['content_type' => sprintf('Invalid content type [%s].', $contentType)],
'json',
Response::HTTP_UNSUPPORTED_MEDIA_TYPE
);
}

return array_search($contentType, $this->validContentTypes);
}

/**
* @param string $payload
* @param string $model
* @param string $format
*
* @return object|Response
*/
protected function validatePayload($payload, $model, $format)
{
$payload = $this->serializer->deserialize($payload, $model, $format);

$errors = $this->validator->validate($payload);
if (count($errors)) {
return $this->createFailureResponse($errors, $format);
}

return $payload;
}

/**
* @param array|object $content
* @param string $format
*
* @return Response
*/
protected function createSuccessResponse($content, $format = 'json')
{
return $this->getResponse($content, $format, Response::HTTP_OK);
}

/**
* @param array|ConstraintViolationListInterface $content
* @param string $format
*
* @return Response
*/
protected function createFailureResponse($content, $format = 'json')
{
$errorList = null;

if ($content instanceof ConstraintViolationList) {
foreach ($content as $error) {
$error = $this->getErrorFromValidation($error);
$errorList[$error['key']] = $error['value'];
}
} else {
$errorList = $content;
}

return $this->getResponse(['errors' => $errorList], $format, Response::HTTP_BAD_REQUEST);
}

/**
* @param array|object $content
* @param string $format
* @param int $status
*
* @return Response
*/
private function getResponse($content, $format, $status)
{
$context = new SerializationContext();
$context->setSerializeNull(false);

$response = $this->serializer->serialize($content, $format, $context);

return new Response($response, $status, ['Content-Type' => $this->validContentTypes[$format]]);
}

/**
* @param ConstraintViolationInterface $error
*
* @return array
*/
private function getErrorFromValidation($error)
{
$properties = $this->inflector->tableize($error->getPropertyPath());

return ['key' => $properties, 'value' => $error->getMessage()];
}
}

DefaultController


namespace Application\ApiBundle\Controller;

use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
* @Route("", service="application_api.controller.default")
*/
class DefaultController extends AbstractController
{
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
parent::__construct($serializer, $validator, $inflector);
}

/**
* @Method({"GET"})
* @Route("")
*
* @return Response
*/
public function indexAction()
{
return $this->createSuccessResponse('Welcome to Webservice API!');
}
}

ApiController


Aşağıda göründüğü gibi diğer iki methoddan farklı olarak, studentAction methodu için @Security ekini tanımlamadık çünkü, security.yml içinde tanımlanan ulaşım hakları, login olan tüm kullanıcıların (ROLE_STUDENT, ROLE_LECTURER, ROLE_ADMIN) /api/... adreslerine varsayılan olarak ulaşım hakkı veriyor. Bu varsayımı uygun bir biçimde çiğnemek için @Security ekini kullanırız.


namespace Application\ApiBundle\Controller;

use Doctrine\Common\Inflector\Inflector;
use JMS\Serializer\SerializerInterface;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Method;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
* @Route("api", service="application_api.controller.api")
*/
class ApiController extends AbstractController
{
public function __construct(
SerializerInterface $serializer,
ValidatorInterface $validator,
Inflector $inflector
) {
parent::__construct($serializer, $validator, $inflector);
}

/**
* @Method({"GET"})
* @Route("/student")
*
* @return Response
*/
public function studentAction()
{
return $this->createSuccessResponse('Hello ROLE_STUDENT user');
}

/**
* @Method({"GET"})
* @Route("/lecturer")
* @Security("has_role('ROLE_LECTURER')")
*
* @return Response
*/
public function lecturerAction()
{
return $this->createSuccessResponse('Hello ROLE_LECTURER user');
}

/**
* @Method({"GET"})
* @Route("/admin")
* @Security("has_role('ROLE_ADMIN')")
*
* @return Response
*/
public function adminAction()
{
return $this->createSuccessResponse('Hello ROLE_ADMIN user');
}
}

Testler


Tüm kullanıcılar / adresine ulaşabilirler o nedenle bunun görselini aşağıda kullanmaya gerek yok. Ayrıca tüm adreslerin tüm kullanıcı ulaşım testleriyle ilgili kanıt olabilecek olan görsellerini kullanmaya da gerek yok ama, bana inanın ki uygulamamız doğru bir şekilde çalışıyor.


IS_AUTHENTICATED_ANONYMOUSLY


Hiçbir /api/... adreslerine ulaşamaz.



ROLE_STUDENT


Sadece /api/student adresine ulaşabilir ama /api/lecturer ve /api/admin adreslerine ulaşamaz.




ROLE_LECTURER


Sadece /api/student ve /api/lecturer adreslerine ulaşabilir ama /api/admin adresine ulaşamaz.




ROLE_ADMIN


Tüm /api/... adreslerine ulaşabilir.