CustomMenu

Thursday, February 27, 2014

Spring MVC 4 & Spring Security 3.2 - Part 1

In this series of posts, will add Spring Security to our application.  We will start by adding very simple authentication functionality driven by usernames and passwords contained in configuration files.  Once that functionality is operating correctly, we will move this authentication information into MySQL.  The last step will be to add role authorization to our application.

In part 1, we will follow along with the Spring Security presentation by Rob Winch/Spring.io:

1. We will first create two new configuration files, and modify our web application config file.
package com.dtr.oas.config;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

public class SecurityInitializer extends  AbstractSecurityWebApplicationInitializer {
}
package com.dtr.oas.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.servlet.configuration.EnableWebMvcSecurity;

@Configuration
@EnableWebMvcSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**", "/signup").permitAll()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()
                .permitAll();
        //.and().httpBasic();
    }

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().
        withUser("user").password("password").roles("USER").and().
        withUser("trader").password("password").roles("USER","TRADER").and().
        withUser("admin").password("password").roles("USER", "TRADER", "ADMIN");
    }
}
package com.dtr.oas.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.Ordered;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.JstlView;
import org.springframework.web.servlet.view.UrlBasedViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan("com.dtr.oas")
public class WebAppConfig extends WebMvcConfigurerAdapter {
    
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }

    // Maps resources path to webapp/resources
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
    }

    @Bean
    public UrlBasedViewResolver setupViewResolver() {
        UrlBasedViewResolver resolver = new UrlBasedViewResolver();
        resolver.setPrefix("/WEB-INF/pages/");
        resolver.setSuffix(".jsp");
        resolver.setViewClass(JstlView.class);
        return resolver;
    }

    // Provides internationalization of messages
    @Bean
    public ResourceBundleMessageSource messageSource() {
        ResourceBundleMessageSource source = new ResourceBundleMessageSource();
        source.setBasename("messages");
        return source;
    }

}
When you are finished, your configuration directory structure should look like the image below.


2. With these three files in place (containing sample users and roles), our simple security is almost complete.  You will notice in the WebAppConfig.java file we added configuration for a controller to direct requests for "/login" to a custom login page.  This login page is called login.html and will need to be in the root of our WEB-INF/views directory.  This is the same directory containing our other strategy HTML pages.  The login page is shown below.
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:th="http://www.thymeleaf.org">

<head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />

    <!-- Core CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/bootstrap-3.1.1.min.css" 
        data-th-href="@{/resources/css/bootstrap-3.1.1.min.css}" />
        
    <link type="text/css" rel="stylesheet" href="../../resources/font-awesome/css/font-awesome.css" 
        data-th-href="@{/resources/font-awesome/css/font-awesome.css}" />

    <!-- SB Admin CSS - Include with every page -->
    <link type="text/css" rel="stylesheet" href="../../resources/css/sb-admin.css" 
        data-th-href="@{/resources/css/sb-admin.css}" />
    
    <style>
        .no-border-on-me>thead>tr>th,
        .no-border-on-me>tbody>tr>th,
        .no-border-on-me>tfoot>tr>th,
        .no-border-on-me>thead>tr>td,
        .no-border-on-me>tbody>tr>td,
        .no-border-on-me>tfoot>tr>td
        {
            border-top-style: none;
            border-bottom-style: none;
        }
    </style>
    <!-- HTML5 Shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
    <!--[if lt IE 9]>
          <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
          <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->
    
    <title data-th-text="#{login.page.title}">Login Page</title>
</head>

<body>

    <div class="container">
        <div class="row">
            <div class="col-md-4 col-md-offset-4">
                <div class="login-panel panel panel-default">
                    <div class="panel-body">
                        <form name="f" data-th-action="@{/login}" method="post">
                            <fieldset>
                    <legend>Please Login</legend>
                    <div data-th-if="${param.error}" class="alert alert-danger">
                        Invalid username and password.
                    </div>
                    <div data-th-if="${param.logout}" class="alert alert-success">
                        You have been logged out.
                    </div>
                                <div class="form-group">
                                    <label for="username">Username</label>
                                    <input class="form-control" placeholder="User ID" type="text" id="username" name="username"></input>
                                </div>
                                <div class="form-group">
                                    <label for="password">Password</label>
                                    <input class="form-control" placeholder="Password" type="password" id="password" name="password"></input>
                                </div>
                                <div class="form-actions">
                                    <button type="submit" class="btn btn-lg btn-success btn-block">Login</button>
                                </div>
                            </fieldset>
                        </form>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
    <script src="../../resources/js/jquery-1.11.0.min.js" 
        data-th-href="@{/resources/js/jquery-1.11.0.min.js}"></script>
        
    <!-- Include all compiled plugins (below), or include individual files as needed -->
    <script src="../../resources/js/bootstrap-3.1.1.min.js" 
        data-th-href="@{/resources/js/bootstrap-3.1.1.min.js}"></script>

    <!-- Core Scripts - Include with every page -->
    <script src="../../resources/js/plugins/metisMenu/jquery.metisMenu.js" 
        data-th-href="@{/resources/js/plugins/metisMenu/jquery.metisMenu.js}"></script>
        
    <!-- SB Admin Scripts - Include with every page -->
    <script src="../../resources/js/sb-admin.js" 
        data-th-href="@{/resources/js/sb-admin.js}"></script>

</body>
</html>
The head section of this file looks the same as our other Thymeleaf files, and the same goes for the script inclusion in the footer.  In between these sections is our login form using the HTTP-POST method, and Thymeleaf error reporting tags.


3. Our last step is to update our Thymeleaf horizontal navbar fragment to display the username and provide logout functionality.  The updated navbar fragment is shown below.
<nav data-th-fragment="top-nav" class="navbar navbar-default navbar-static-top" style="margin-bottom: 0">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".sidebar-collapse">
                    <span class="sr-only">Toggle navigation</span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                <a class="navbar-brand" href="#">Option Algo System</a>
            </div>
            <!-- /.navbar-header -->

            <ul class="nav navbar-top-links navbar-right" data-th-with="currentUser=${#httpServletRequest.userPrincipal?.name}">
                    <li><a href="/"            >Home</a></li>
                    <li><a href="/trading"     >Trading</a></li>
                    <li><a href="/backtesting" >Backtesting</a></li>
                    <li><a href="/admin"       >Admin</a></li>
                    <li class="dropdown" data-th-if="${currentUser != null}">
                      <a class="dropdown-toggle" data-toggle="dropdown" href="#">
                          <i class="fa fa-user fa-fw"></i>
                          <font color="#049cbd" th:text="'&nbsp;' + ${currentUser} + '&nbsp;&nbsp;'">&nbsp;Dave&nbsp;&nbsp;</font>
                          <i class="fa fa-caret-down"></i>
                      </a>
                            <ul class="dropdown-menu dropdown-user">
                       <li><a href="#"><i class="fa fa-user fa-fw"></i>User Profile</a></li>
                       <li><a href="#"><i class="fa fa-gear fa-fw"></i>Settings</a></li>
                       <li class="divider"></li>
                       <li>
                                 <form class="navbar-form" data-th-action="@{/logout}" method="post">
                                 <label for="mySubmit" class="btn"><i class="fa fa-sign-out fa-fw"></i>Log Out</label>
                                 <input id="mySubmit" type="submit" value="Go" class="hidden" />
                                 </form>
                             </li>
                            </ul>
                      <!-- /.dropdown-user -->
              </li>
            </ul>  <!-- /.navbar-top-links -->
  </nav>     <!-- /.navbar-static-top -->

4. With these files in place, we can now attempt to login.  The first image below shows our login page as it is initially served up by Tomcat.  The second image shows the result of an incorrect password.



5. After successful login and navigation from the home page to the list page, you should see the screen in the image below.
The update to the navbar fragement, has given us a dropdown with the title containing the username of our logged in user.  The profile and settings links are stubs, but the logout button is functional.


6. After pressing the logout button, you should be taken back to the login screen, but with a logout message.


If you want more information on this topic, the following pages on the Spring Security site go over in a lot of detail, the steps above.

In the next post, we will move the user information from configuration files to a database.

Code at GitHub: https://github.com/dtr-trading/spring-ex08-security

No comments:

Post a Comment