Friday, September 14, 2012

Part 3 - Introducing Spring Security, Thymeleaf and Spring MVC

Security is one of the key aspect of an application, especially web application. In our application we intend to integrate Spring Security as we are already on Spring framework. Alternative was Apache Shiro. But we prefer Spring Security. Here is how we can integrate Spring Security with Spring MVC and Thymeleaf.
The first step to this integration is to include the Spring Security and Thymeleaf Spring Security jars into our application. This can be done by adding Maven dependencies. Here is the snippet from the parent/pom.xml file.


Listing 1 - parent/pom.xml - snippet
<!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <!-- SPRING SECURITY          -->
        <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring-security.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring-security.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
            <version>${spring-security.version}</version>
        </dependency>
    <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity3</artifactId>
            <version>1.0.0-beta1</version>
        </dependency>
Now the next step is to include the Spring Security filter in the web.xml. Note some subtle changes in the Spring config file changes. Spring contexts have parent child relationship. The context loaded by ContextLoaderListener is the parent and that by dispatcher servlet is child. The child context now has the beans related to view and contoller. The service and repository beans will be loaded by the parent context. The contoller accesses the service beans. Since child context has full access to parent context the controllers can easily use the service beans and not the other way round. The modified web.xml is shown in Listing 2.
Listing 2 - web.xml
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
    version="3.0">
    <!-- Log4j -->
    <context-param>
        <param-name>log4jConfigLocation</param-name>
        <param-value>file:${catalina.home}/temp/effectivcrm/conf/log4j.xml</param-value>
    </context-param>

    <context-param>
        <param-name>log4jExposeWebAppRoot</param-name>
        <param-value>false</param-value>
    </context-param>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            classpath*:conf/spring-*-config.xml
        </param-value>
    </context-param>

    <!-- Log4j listerner -->
    <listener>
        <listener-class>org.springframework.web.util.Log4jConfigListener</listener-class>
    </listener>

    <!-- Spring Listeners -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <filter>
        <filter-name>springSecurityFilterChain</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>springSecurityFilterChain</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <servlet>
        <servlet-name>Spring MVC 3 Servlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>
                classpath*:conf/spring-view.xml
                classpath*:conf/spring-controller.xml
            </param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Spring MVC 3 Servlet</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>
The spring-web-config.xml is renamed as spring-view.xml and we add the Thymeleaf Spring Security dialect here. This exposes Spring security variables to Thymeleaf. Refer to the Spring Security 3 Thymeleaf integration project for details.
Listing 3 - spring-view.xml
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd">

    <mvc:annotation-driven />

    <mvc:resources location="classpath:/META-INF/assets/img/" mapping="/assets/img/**" />
    <mvc:resources location="classpath:/META-INF/assets/css/" mapping="/assets/css/**" />
    <mvc:resources location="classpath:/META-INF/assets/js/" mapping="/assets/js/**" />

    <bean
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping">

    </bean>

    <bean
        class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
        <property name="cacheSeconds" value="0" /> <!-- NO CACHE -->
    </bean>

    <bean id="contentNegotiatingResolver"
        class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
        <property name="mediaTypes">
            <map>
                <entry key="html" value="text/html" />
                <entry key="pdf" value="application/pdf" />
                <entry key="xsl" value="application/vnd.ms-excel" />
                <entry key="xml" value="application/xml" />
                <entry key="json" value="application/json" />
            </map>
        </property>
    </bean>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!-- Tiles                                -->
    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->

    <bean id="tilesConfigurer" class="org.thymeleaf.extras.tiles2.spring.web.configurer.ThymeleafTilesConfigurer">
      <property name="definitions">
        <list>
          <value>classpath:/META-INF/tiles/tiles-*.xml</value>
        </list>
      </property>
    </bean>

    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->
    <!-- Themeleaf View Config -->
    <!-- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -->

    <bean id="templateResolver"
        class="org.thymeleaf.templateresolver.ClassLoaderTemplateResolver">

        <property name="suffix" value=".html" />
        <property name="templateMode" value="HTML5" />
        <property name="cacheable" value="false" />
    </bean>

    <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine">
        <property name="templateResolver" ref="templateResolver" />

        <property name="additionalDialects">
            <set>
                <bean class="com.effectivcrm.view.form.ExtraSpringDialect" />
                <bean class="org.thymeleaf.extras.tiles2.dialect.TilesDialect"/>
                <bean class="org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect"/>
            </set>
        </property>
    </bean>

    <bean id="tilesViewResolver" class="org.thymeleaf.spring3.view.ThymeleafViewResolver">
      <property name="viewClass" value="org.thymeleaf.extras.tiles2.spring.web.view.ThymeleafTilesView"/>
      <property name="templateEngine" ref="templateEngine" />
      <property name="characterEncoding" value="UTF-8" />
    </bean>

</beans>
 Now we introduce the Spring security configuration as shown in Listing 4.
Listing 4 - spring-security-config.xml
<?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:security="http://www.springframework.org/schema/security"
    xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">

    <security:http use-expressions="true">
        <security:form-login login-page="/signin"
            default-target-url="/listlead"
            authentication-failure-url="/signinfailure" />
        <security:intercept-url pattern="/signin*" access="permitAll" />
        <security:intercept-url pattern="/**/*.js" access="permitAll" />
        <security:intercept-url pattern="/**/*.css" access="permitAll" />
        <security:intercept-url pattern="/**/*.gif" access="permitAll" />

        <security:intercept-url pattern="/list*" access="fullyAuthenticated" />
        <security:intercept-url pattern="/create*" access="fullyAuthenticated" />

    </security:http>

    <security:authentication-manager>
      <security:authentication-provider>
        <security:user-service>
        <security:user name="dhrubo" password="123456" authorities="ROLE_USER" />
        </security:user-service>
      </security:authentication-provider>
    </security:authentication-manager>

</beans>

I will also modify the signinform.html to allow it to submit to Spring Security filter to initiate the authentication and subsequent redirection on successful authentication.
Listing 5 - signinform.html
<h3>Sign In</h3>
<form xmlns:th="http://www.thymeleaf.org"
    action="authentication" th:action="@{/j_spring_security_check}"
    method="post" class="well">
    <fieldset>
        <label>Username :</label> <input id="j_username" name="j_username"
            type="text" required="required" autofocus="autofocus"
            class="input span5" placeholder="Username" /> <label>Password
            :</label> <input id="j_password" name="j_password" type="password"
            required="required" class="input span5"
            placeholder="Password" />
    </fieldset>
    <button type="submit" class="btn btn-success">Sign In</button>
    <button type="submit" class="btn btn-info">Recover password</button>
</form>

The header.html is also modified to check if authentication is working fine and also check Thymeleaf Spring Security 3 integration by printing the name of the logged in user.
Listing 6 - header.html
<div xmlns:th="http://www.thymeleaf.org"
    class="navbar navbar-inverse navbar-fixed-top">
    <div class="navbar-inner">

        <div class="container-fluid">
            <a class="btn btn-navbar" data-toggle="collapse"
                data-target=".nav-collapse"> <span class="icon-bar"></span> <span
                class="icon-bar"></span> <span class="icon-bar"></span>
            </a> <a class="brand" href="#">Project name</a>
            <div class="nav-collapse collapse">

                <p class="navbar-text pull-right">
                    Logged in as <a href="#" class="navbar-link" th:text="${#authentication.name}">Username</a>
                </p>
                <ul class="nav">
                    <li class="active"><a href="#">Home</a></li>
                    <li><a href="#about">About</a></li>
                    <li><a href="#contact">Contact</a></li>

                </ul>
            </div>
            <!--/.nav-collapse -->
        </div>
    </div>
</div>
We will now add 1 controller mapping - signin in the GuestController.
Listing 7 - GuestController.java
/**
 *
 */
package com.effectivcrm.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @author Dhrubo
 *
 */
@Controller
public class GuestController {

    @RequestMapping(value="/signin")
    public String gotoSignIn(){
        return "signin";
    }

    @RequestMapping(value="/")
    public String index(){
        return "signin";
    }

}

No comments:

Post a Comment