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


How to protect the resources of a user in a REST API with FOSRestBundle and FOSOauthServerBundle?

我正在开发一个RESTful web服务在Symfony2与FOSRest和FOSOauthServer包(…以及其他许多人)。我的问题是,使用其他用户的访问令牌,api给出响应而不是403状态码。例如:

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

userA with tokenAuserB with tokenB

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

电流响应

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

但是我用用户b的访问令牌请求用户A的产品,我如何检查提供的访问令牌是否是产品的所有者??

我的安全。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] }

我的路由。

# 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);
    }
}

非常感谢你的帮助!

我找到解决办法了。这很容易,只是我在我的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();
        }
...

~/app/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

您也可以在Symfony>= 2.4中使用@Security注释使其更简单。在你的例子中,它看起来像

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

和动作头:

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