Introduction
Recently I had to figure out how to validate a persons details in the DMZ using some standard reference; the main concern was that the machine in the DMZ could not “send” any sensitive (useful) information. This is “typically” what is done, that is to say “hide” information. There was a dilemma of how to communicate this information from external (DMZ) to internal (secure data). RESTful web services seemed like the best way to go.
One of the most downloaded Symfony bundles is FOSRestBundle, and this is understandable, since REST is becoming more prevalent nowadays. So I decided to use FOSRestBundle for my application. This blog describes how to install the bundle and get it working.
Installation
If you haven’t installed Symfony before, you’ll need to install that and all its requirements first. My post Apache & Symfony on RHEL describes details on installing Apache (httpd), PHP 7.0, MariaDB, Composer, and the Symfony binary. So I would suggest you look at that blog first and then proceed with these steps.
I’ll presume you’ve created a Symfony project called “restTest”, and then you’ll need to issue the following commands to install FOSRestBundle:
composer require friendsofsymfony/rest-bundle
If for some reason on your machine you can’t write to the html folder (example /var/www/html), and you need to use sudo to create the permissions, then you’ll need to run composer differently. Use the command “whereis composer” to find out the path to composer, then issue the following command:
sudo /usr/local/bin/composer require friendsofsymfony/rest-bundle
Then, enable the bundle by editing “restTest/app/AppKernel.php”:
// app/AppKernel.php class AppKernel extends Kernel { public function registerBundles() { $bundles = [ ... new FOS\RestBundle\FOSRestBundle(), ... ];
After that, then you’ll need to enable a JSON serializer and the configuration for the FOSRestBundle. To do this, edit the file “restTest/app/config/config.yml” as follows:
# app/config/config.yml ... framework: ... serializer: { enable_annotations: true } ... ... fos_rest: body_listener: true format_listener: rules: - { path: '^/', priorities: ['json'], fallback_format: json, prefer_extension: false } param_fetcher_listener: true view: view_response_listener: 'force' formats: json: true ...
That’s it for installation!
Create a Default Controller
Next let’s edit the default controller and create a simple controller that returns a very simple JSON response. In the controller example below, I’ll use @Rest annotations that simplify routing. Open the PHP file “restTest/src/AppBundle/Controller/DefaultController.php” and make the following changes:
<?php namespace AppBundle\Controller; use FOS\RestBundle\Controller\Annotations as Rest; use FOS\RestBundle\Controller\FOSRestController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; class DefaultController extends FOSRestController { /** * @Rest\Get("/api/{testID}") */ public function apiAction(Request $request) { $data = [ 'test1' => 'someData1', 'test2' => false, ]; $view = $this->view($data, Response::HTTP_OK); return $view; }
That’s it! Now open a browser and enter the URL:
http://192.168.1.15/api/1234
In the above example, the value “192.168.1.15” is the IP address of the host running the Apache/Symfony server. You could also setup the “ServerName” directive and configure your hosts file (as described in my Apache httpd VirtualHost Config post), and then use a hostname in the URL. If everything is working and setup correctly, this will return the following JSON response:
{ "test1":"someData","test2":false }
This demonstrates a simple REST controller, you can modify the controller to return the desired data that you need.
Returning Useful Information
The above default controller works great, except for it only returns static data and nothing really useful. What if we wanted the passed in parameter “testID” to represent an ID of a user table in a database? Then we can pass in the parameter and then return via the controller information about the user.
Most Symfony users use the default (local)database which is MySQL. However, you can use other databases such as PostgreSQL, MSSQL, Oracle, etc…, and the database can also be external to the Symfony host. In my example I’ll use an external Oracle database connection using a Doctrine Database Abstraction Layer connection. Here is an example code getting user information:
/** * @Rest\Get("/api/{testID}") */ public function apiAction(Request $request) { // Get an Oracle database connection. $orc = $this->get('doctrine.dbal.oracle_connection'); // Query for user ID, and get first & last names. $query = "SELECT U.FIRST_NAME,U.LAST_NAME FROM USERS U WHERE U.ID = '".$request->get('testID')."'"; $result = $orc->fetchAll( $query ); if ($result == null){ $data = []; } else{ $data = [ 'fname' => $result[0]['FIRST_NAME'], 'lname' => $result[0]['LAST_NAME'], ]; } $view = $this->view($data, Response::HTTP_OK); return $view; }
In the above code, we retrieve the “testID” from the Request and then run a query on the database for the passed in “testID” and return the First & Last names for the particular user in the JSON response.
Using the above controller, presuming the database contained the user with a “testID” of “3333” has a USERS tables values of “Tony Baloney” for First & Last names; then using the REST api URL of “http://192.168.1.15/api/333” would return:
{"fname":"Tony","lname":"Baloney"}
This is much more useful, since passing in the ID returns the users name as a response.
Using WSSE Authentication
At the begining of this article, I mentioned the concern of sending information between servers (especially in the DMZ) and protecting that information. One way of protecting that information would be to authenticate. Using a REST system, this would mean to provide a username / password combination and only return a response to clients attempting to consume the web service that have authenticated by passing in the same username / password combination.
WSSE is an ideal protocol to use to achieve the security we need. WSSE operation can be summarized as setting custom http headers; specifically “X-WSSE” headers. A sample X-WSSE header might look like the following:
X-WSSE: UsernameToken Username="wsse", PasswordDigest="gBgj6GM5MsOQkn+j3VkBiSmSt7g=", Nonce="NTMxNTcyODI4ZGZkZmQ4Zg==", Created="2016-11-23T22:20:17Z"
There is one popular WSSE bundle for Symfony available:
djoos/EscapeWSSEAuthenticationBundle
You can open the above link and install using composer; then to enable it and add a sample user / password, we can edit the “app/config/security.yml” file like so:
# To get started with security, check out the documentation: # http://symfony.com/doc/current/book/security.html security: # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers providers: in_memory: memory: users: - { name: 'wsse_user', password: 'myPass' } wsse_users: memory: users: - { name: 'wsse_user', password: 'myPass' } firewalls: wsse_secured: provider: users wsse: provider: wsse_users
That’s it! WSSE authentication is enabled for every request. We should probably add a simpler Route to use to test out the authentication. Open the DefultController and ADD (add below your other code) the following route:
/** * @Rest\Get("/testApi/{testNumber}") */ public function testApiAction(Request $request) { $data = [ 'result' => $request->get('testNumber') ]; return $this->view($data, Response::HTTP_OK); }
Once you make this change, make sure you issue this command from your Symfony REST project directory to clear your production cache, which is a common mistake for developers new to Symfony:
php bin/console cache:clear --env=prod
If you issue the following command:
php bin/console debug:router
You should now see the above route “/testApi/{testNumber}” appear in the “Path” column. Now, to test this out, we can open a browser and enter in the following URL which should call the above route:
http://192.168.1.15/testApi/9933
You will get a blank page (no response), which is good, since it is working as expected.So, the question you might have is: how do we test this, and how do we know it’s working? In Chrome, we can add the REST Console extension, and we can use the REST Console together with this JavaScript WSSE Header Generator tool. In the JavaScript WSSE Header Generator tool, we simply enter in our username and password, check the “auto” boxes for nonce and created, and then click “Generate”. Below is a sample screenshot:
The X-WSSE headers are shown below the Generate button. Now we can use the REST Console as a test client to consume the REST service. In Chrome open the REST Console – it appears under Apps. Enter the following:
Target Request URI: http://192.168.1.15/testApi/9933 Custom Headers -> Click "+" Request Parameters header: X-WSSE value: paste the value from JavaScript WSSE Header Generator above. Click GET button
Note, the header value does not include “X-WSSE:”, but just the trailing part. If everything worked correctly and you autheticated, the Response section shows the JSON result. It should look like the sample below:
Now you simply need to create a client that you will normally using in production to consume the REST web service.
Hi, You’ve done a great job. I’ll certainly digg it and
recommend to my twitter followers personally. I’m confident they
will be benefited
from this website.