Search

Dark theme | Light theme
Showing posts with label RESTful webservices. Show all posts
Showing posts with label RESTful webservices. Show all posts

July 9, 2014

Grails Goodness: Custom Controller Class with Resource Annotation

In Grails we can apply the @Resource AST (Abstract Syntax Tree) annotation to a domain class. Grails will generate a complete new controller which by default extends grails.rest.RestfulController. We can use our own controller class that will be extended by the @Resource transformation. For example we might want to disable the delete action, but still want to use the @Resource transformation. We simply write a new RestfulController implementation and use the superClass attribute for the annotation to assign our custom controller as the value.

First we write a new RestfulController and we override the delete action. We return a HTTP status code 405 Method not allowed:

// File: grails-app/controllers/com/mrhaki/grails/NonDeleteRestfulController.groovy
package com.mrhaki.grails

import grails.rest.*

import static org.springframework.http.HttpStatus.METHOD_NOT_ALLOWED

/**
 * Custom RestfulController where we disable the delete action.
 */
class NonDeleteRestfulController<T> extends RestfulController<T> {

    // We need to provide the constructors, so the 
    // Resource transformation works.
    NonDeleteRestfulController(Class<T> domainClass) {
        this(domainClass, false)
    }

    NonDeleteRestfulController(Class<T> domainClass, boolean readOnly) {
        super(domainClass, readOnly)
    }

    @Override
    def delete() {
        // Return Method not allowed HTTP status code.
        render status: METHOD_NOT_ALLOWED
    }
    
}

Next we indicate in the @Resource annotation with the superClass attribute that we want to use the NonDeleteRestfulController:

// File: grails-app/domain/com/mrhaki/grails/User.groovy
package com.mrhaki.grails

import grails.rest.*

@Resource(uri = '/users', superClass = NonDeleteRestfulController)
class User {

    String username
    String lastname
    String firstname
    String email

    static constraints = {
        email email: true
        lastname nullable: true
        firstname nullable: true
    }

}

When we access the resource /users/{id} with the HTTP DELETE method we get a 405 Method Not Allowed response status code.

Written with Grails 2.4.2.

Grails Goodness: Change Response Formats in RestfulController

We can write a RESTful application with Grails and define our API in different ways. One of them is to subclass the grails.rest.RestfulController. The RestfulController already contains a lot of useful methods to work with resources. For example all CRUD methods (save/show/update/delete) are available and are mapped to the correct HTTP verbs using a URL mapping with the resource(s) attribute.

We can define which content types are supported with the static variable responseFormats in our controller. The variable should be a list of String values with the supported formats. The list of supported formats applies to all methods in our controller. The names of the formats are defined in the configuration property grails.mime.types. We can also use a Map notation with the supportedFormats variable. The key of the map is the method name and the value is a list of formats.

// File: grails-app/controllers/com/mrhaki/grails/UserApiController.groovy
package com.mrhaki.grails

import grails.rest.*

class UserApiController extends RestfulController {

    // Use Map notation to set supported formats
    // per action.
    static responseFormats = [
        index: ['xml', 'json'],  // Support both XML, JSON
        show: ['json']           // Only support JSON
    ]

    // We make the resource read-only in
    // the constructor.
    UserApiController() {
        super(User, true /* read-only */)
    }

}

We can also specify supported formats per action using the respond method in our controller. We can define the named argument formats followed by a list of formats when we invoke the respond method. In the following controller we override the index and show methods and use the formats attribute when we use the respond method:

// File: grails-app/controllers/com/mrhaki/grails/UserApiController.groovy
package com.mrhaki.grails

import grails.rest.*

class UserApiController extends RestfulController {

    // We make the resource read-only in
    // the constructor.
    UserApiController() {
        super(User, true /* read-only */)
    }

    @Override
    def index(Integer max) {
        params.max = Math.min(max ?: 10, 100)
        respond listAllResources(params), formats: ['xml', 'json']
    }

    @Override
    def show() {
        respond queryForResource(params.id), formats: ['json']
    }

}

Code written with Grails 2.4.2.

February 5, 2009

Use Spring or Acegi security to protect RESTful webservices

Simple protection of RESTful webservices is accomplished by using HTTP Basic authentication. We can use Acegi 1.x or Spring 2.x security to define an URL pattern to protect with a username and password. For example we have developed a RESTful webservice with the URI /resources/articles and we want to protect it with a username and password. Let's look at the necessary Spring definition to achieve this with Acegi 1.x security:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
        <property name="filterInvocationDefinitionSource">
            <value>
              <![CDATA[
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /resources/**=httpSessionContextIntegrationFilterWithASCFalse,basicProcessingFilter,exceptionTranslationWithASCFilter,filterInvocationInterceptor
              ]]>
            </value>
        </property>
    </bean>

    <bean id="httpSessionContextIntegrationFilterWithASCFalse"
        class="org.acegisecurity.context.HttpSessionContextIntegrationFilter">
        <property name="allowSessionCreation" value="false"/>
    </bean>

    <bean id="exceptionTranslationWithASCFilter"
        class="org.acegisecurity.ui.ExceptionTranslationFilter">
        <property name="createSessionAllowed" value="false"/>
        <property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
        <property name="accessDeniedHandler">
            <bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl"/>
        </property>
    </bean>

    <bean id="basicProcessingFilter" class="org.acegisecurity.ui.basicauth.BasicProcessingFilter">
        <property name="authenticationManager">
            <ref bean="authenticationManager"/>
        </property>
        <property name="authenticationEntryPoint">
            <ref bean="authenticationEntryPoint"/>
        </property>
    </bean>

    <bean id="filterInvocationInterceptor"
        class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="accessDecisionManager" ref="httpRequestAccessDecisionManager"/>
        <property name="objectDefinitionSource">
            <value>
            <![CDATA[
                CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
                PATTERN_TYPE_APACHE_ANT
                /resources/**=ROLE_ADMIN
            ]]>
            </value>
        </property>
    </bean>

    <bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
        <property name="providers">
            <list>
                <ref local="daoAuthenticationProvider" />
            </list>
        </property>
    </bean>

    <bean id="daoAuthenticationProvider"
        class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
        <property name="userDetailsService" ref="inMemoryDaoImpl" />
        <property name="forcePrincipalAsString">
            <value>true</value>
        </property>
    </bean>

    <bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
        <property name="userMap">
            <value>
                <![CDATA[
                    admin=crypticpassw,ROLE_ADMIN
                ]]>
            </value>
        </property>
    </bean>

    <bean id="authenticationEntryPoint" class="org.acegisecurity.ui.basicauth.BasicProcessingFilterEntryPoint">
        <property name="realmName">
            <value>Restricted resources</value>
        </property>
    </bean>

    <bean id="httpRequestAccessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
        <property name="allowIfAllAbstainDecisions" value="false"/>
        <property name="decisionVoters">
            <list>
                <ref bean="roleVoter" />
            </list>
        </property>
    </bean>

    <bean id="roleVoter" class="org.acegisecurity.vote.RoleVoter"/>

</beans>

This is just a basic configuration. To learn more about the different components see the official Acegi 1.x reference documentation. For now the most important thing to remember is that we don't need HTTP sessions to be created when invoking a RESTful webservice. We don't want this to happen, because it will take up a lot of resources on the server and eventually our application can become very slow. The nice thing about a RESTful webservice is that it doesn't need an HTTP session to work correctly. But to accomplish this we must tell Acegi explicitly we don't want HTTP sessions to be created. If we don't, Acegi will create an HTTP session to save authentication information.

When we look at the configuration we notice at line 11 we reference httpSessionContextIntegrationFilterWithASCFalse and exceptionTranslationWithASCFilter. The definition can be found at lines 17 and 22. Here we assign the property allowSessionCreation the value false. Now Acegi will no longer create an HTTP session to save authentication information and that is exactly we wanted to achieve!

Spring 2.0 security is the new Acegi security, but we can use namespaces to configure the security which results in a much easier to read configuration file:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:util="http://www.springframework.org/schema/util"
       xmlns:security="http://www.springframework.org/schema/security"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
                           http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util-2.0.xsd
                           http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-2.0.4.xsd">

    <security:http create-session="never" auto-config="false" realm="Restricted resources">
        <security:intercept-url pattern="/resources/**" access="ROLE_ADMIN"/>
        <security:http-basic />
        <security:logout />
    </security:http>
    
    <security:authentication-provider>
        <security:user-service>
            <security:user name="admin" password="crypticpassw" authorities="ROLE_ADMIN"/>
        </security:user-service>
    </security:authentication-provider>

</beans>

Wow that is much easier to read (and maintain) than the previous configuration. To read more about the configuration options see the official Spring 2.0 security reference documentation. At line 9 we assign the attribute create-session the value never to make sure no HTTP session is created when using the RESTful webservice.