如何使用FOSRestBundle和FOSOauthServerBundle保护REST API中的用户资源?

时间:2014-05-29 10:22:46

标签: php rest symfony fosrestbundle fosoauthserverbundle

我正在使用FOSRest和FOSOauthServer捆绑包(......和许多其他捆绑包)在Symfony2中开发RESTful Web服务。我的问题是,使用其他用户的访问令牌,api给出响应而不是403状态代码。例如:

我有两个用户存储在数据库

userA with tokenA userB with tokenB

示例请求 http://example.com/api/v1/userA/products?access_token=tokenB

当前回应

{
   products: {
    0: { ... } 
    1: { ... }
  }
}

但我要求用户A的产品使用用户B的访问令牌。我如何检查提供的访问令牌是否属于产品所有者?

我的security.yml文件:

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512

    role_hierarchy:
        MY_ROLE:
            # ...
        ROLE_ADMIN:       ROLE_USER
        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
        SONATA:
            - ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT
    providers:
        fos_userbundle:
            id: fos_user.user_provider.username_email

    firewalls:            
        admin:
            pattern:            /admin(.*)
            context:            user
            form_login:
                provider:       fos_userbundle
                csrf_provider:  form.csrf_provider
                login_path:     /admin/login
                use_forward:    false
                check_path:     /admin/login_check
                failure_path:   null
            logout: 
                path:           /admin/logout
            anonymous:          true                

        # FOSOAuthBundle and FOSRestBundle    
        oauth_token:
            pattern:    ^/oauth/v2/token
            security:   false

#        oauth_authorize: commented because there are not oauth login form on this app
#            pattern:    ^/oauth/v2/auth
            # Add your favorite authentication process here

        api:
            pattern:    ^/api
            fos_oauth:  true
            stateless:  true
            anonymous:  false

        # This firewall is used to handle the public login area
        # This part is handled by the FOS User Bundle
        main:
            # ...

    access_control:
        # ...

        # API (FOSRestBundle and FOSOAuthBundle)
        - { path: ^/api, roles: [IS_AUTHENTICATED_FULLY] }

我在ApiBundle上的routing.yml

# API Endpoints
app_api_user_get_products:
    pattern: /{username}/products
    defaults: { _controller: ApiBundle:User:getProducts, _format: json }
    methods: GET

我的UserController.php

<?php

namespace App\ApiBundle\Controller;

Use App\MainBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
// ... more use statments

    class UserController extends ApiController {

    /**
     * List user's products.
     *
     * @ApiDoc(
     *  resource = true,
     *  description="This method must have the access_token parameter. The parameters limit and offset are optional.",
     *  filters={
     *      {"name"="access_token", "dataType"="string", "required"="true"},
     *      {"name"="offset", "dataType"="integer", "default"="0", "required"="false"},
     *      {"name"="limit", "dataType"="integer", "default"="250", "required"="false"}
     *  },
     * )
     *
     * @Annotations\QueryParam(name="offset", requirements="\d+", nullable=true, description="Offset from which to start listing products.")
     * @Annotations\QueryParam(name="limit", requirements="\d+", default="500", description="How many products to return.")
     *
     * @Annotations\View()
     *
     * @param User               $user      the request object
     * @param ParamFetcherInterface $paramFetcher param fetcher service
     *
     * @return array
     */
    public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) { 


//        $offset = $paramFetcher->get('offset');
//        $offset = null == $offset ? 0 : $offset;
//        $limit = $paramFetcher->get('limit');

        try {
            // estructure and exclude fields strategy http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies
            $data = array('products' => array());
            foreach ($user->getCatalog() as $p) {
                if ($p->getAvailable() == true) {
                    $product = $p->getProduct();
                    $data['products'][] = array(
                        'id' => $product->getId(),
                        'reference' => $product->getReference(),
                        'brand' => $product->getBrand(),
                        'description' => $product->getDescription(),
                        // ...
                    );
                }
            }
        } catch (\Exception $e) {
            throw new HttpException(Codes::HTTP_INTERNAL_SERVER_ERROR, $e->getTraceAsString());
        }

        // New view
        $view = new View($data);
        $view->setFormat('json');

        return $this->handleView($view);
    }
}

非常感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

我找到了解决方案。这很容易,只是我在我的rest控制器中添加了以下代码,并在app / config.yml上添加了配置参数

UserController.php

...
public function getProductsAction(User $user, ParamFetcherInterface $paramFetcher, Request $request) {

        // Check if the access_token belongs to the user making the request
        $requestingUser = $this->get('security.context')->getToken()->getUser();
        if (!$requestingUser || $requestingUser !== $user) {
                throw new AccessDeniedHttpException();
        }
...

〜/应用程序/ config.yml

# FOSRestBundle
fos_rest:
    routing_loader:
        default_format: json
    param_fetcher_listener: true
    view:
        view_response_listener: force
    access_denied_listener: # I've added this
        # all requests using the 'json' format will return a 403 on an access denied violation
        json: true

答案 1 :(得分:0)

您还可以使用Symfony&gt; = 2.4中的@Security注释使其更简单。在你的情况下,它看起来像

/**
 * @Security("user.getId() == userWithProducts.getId()")
 */

和动作标题:

...
public function getProductsAction(User $userWithProducts, ParamFetcherInterface $paramFetcher, Request $request) {
...