Chapter 7. Incorporating Spring Security

Spring Security is a powerful and highly customizable authentication and access-control framework for enterprise Java applications. The Spring Security framework is mainly used to ensure web application security such as authentication and authorization on application-level operations.

For web-layer security, Spring Security heavily leverages the existing Servlet filter architecture; it does not depend on any particular web technology. Spring Security mainly concerns HttpRequest and HttpResponse objects; it doesn't care about the source of the request and the response target. A request may originate from a web browser, web service, HTTP client, or JavaScript-based Ajax request. The only critical requirement for Spring Security is that it must be an HttpRequest, so that it can apply standard Servlet filters.

Spring Security provides various pre-built Servlet filters as part of the framework; the only requirement for us is to configure the appropriate filters in our web application in order to intercept the request and perform security checks. Spring Security is a vast topic, so we are not going to see all the capabilities of Spring Security; instead we are only going to see how to add basic authentication to our web pages.

Using Spring Security

In Chapter 4, Working with Spring Tag Libraries, we saw how to serve and process web forms; in that exercise we created a web page to add products. Anyone with access to the add products page could add new products to our web store. But in a typical web store, only administrators can add products. So how can we prevent other users from accessing the add products page? Spring Security comes to the rescue.

Time for action - authenticating users based on roles

We are going to restrict access to all our web pages using Spring Security. Only an authorised user or administrator with a valid username and password can access our web pages from a browser:

  1. Open pom.xml; you can find pom.xml under the project root folder itself.
  2. You should see some tabs at the bottom of pom.xml; select the Dependencies tab and click the add button in the Dependencies section.
  3. A Select Dependency window will appear; enter Group Id as org.springframework.security, Artifact Id as spring-security-config, Version as 4.1.1.RELEASE, select Scope as compile, and click the OK button.
  4. Similarly, add one more dependency Group Id as org.springframework.security, Artifact Id as spring-security-web, Version as 4.1.1.RELEASE, select Scope as compile, and click the OK button. And most importantly, save pom.xml.
  5. Now create one more controller class called LoginController under the com.packt.webstore.controller package in the src/main/java source folder. Add the following code to it:
          package com.packt.webstore.controller; 
     
         import org.springframework.stereotype.Controller; 
         import org.springframework.web.bind.annotation.RequestMapping; 
         import org.springframework.web.bind.annotation.RequestMethod; 
     
          @Controller 
          public class LoginController { 
     
             @RequestMapping(value = "/login", method = 
             RequestMethod.GET) 
             public String login() { 
                return "login"; 
             } 
          } 
    
  6. Add one more JSP view file called login.jsp under the src/main/webapp/WEB-INF/views/ directory, add the following code snippets into it, and save it:
          <%@ taglib prefix="c"
           uri="http://java.sun.com/jsp/jstl/core"%> 
          <%@ taglib prefix="form"
           uri="http://www.springframework.org/tags/form"%> 
          <%@ taglib prefix="spring"
           uri="http://www.springframework.org/tags"%> 
          <!DOCTYPE html> 
          <html> 
          <head> 
          <meta http-equiv="Content-Type" content="text/html; 
          charset=ISO-8859-1"> 
          <link rel="stylesheet" 
           href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css
          /bootstrap.min.css"> 
          <title>Products</title> 
          </head> 
          <body> 
             <section> 
                <div class="jumbotron"> 
                   <div class="container"> 
                      <h1>Welcome to Web Store!</h1> 
                      <p>The one and only amazing web store</p> 
                   </div> 
                </div> 
             </section> 
             <div class="container"> 
                <div class="row"> 
                   <div class="col-md-4 col-md-offset-4"> 
                      <div class="panel panel-default"> 
                         <div class="panel-heading"> 
                            <h3 class="panel-title">Please sign in</h3> 
                         </div> 
                         <div class="panel-body"> 
           <c:url var="loginUrl" value="/login" /> 
                            <form action="${loginUrl}" method="post" 
                            class="form-horizontal"> 
     
                               <c:if test="${param.error != null}"> 
                                  <div class="alert alert-danger"> 
                                     <p>Invalid username and password.
                                     </p> 
                                  </div> 
                               </c:if> 
     
                               <c:if test="${param.logout != null}"> 
                                  <div class="alert alert-success"> 
                                     <p>You have been logged out                                        successfully.</p> 
                                  </div> 
                               </c:if> 
     
                               <c:if test="${param.accessDenied != 
                                null}"> 
                                  <div class="alert alert-danger"> 
                                     <p>Access Denied: You are not                                        authorised! </p> 
                                  </div> 
                               </c:if> 
     
                               <div class="input-group input-sm"> 
                                  <label class="input-group-addon"                                   for="username"><i 
                                   class="fa fa-user"></i></label>                                <input type="text" class="form-control" 
                                     id="userId" name="userId"                                           placeholder="Enter Username" 
                                     required> 
                               </div> 
                               <div class="input-group input-sm"> 
                                  <label class="input-group-addon"                                   for="password"><i 
                                     class="fa fa-lock"></i></label>                                     <input type="password" 
                                     class="form-control" id="password"                                  name="password" placeholder="Enter                                  Password" required> 
                               </div> 
     
                               <div class="form-actions"> 
                                  <input type="submit" 
                                     class="btn btn-block btn-primary                                  btn-default" value="Log in"> 
                               </div> 
                            </form> 
                         </div> 
                      </div> 
                   </div> 
                </div> 
             </div> 
          </body> 
    
  7. Now create one more configuration file called SecurityConfig.java under the com.packt.webstore.config package in the src/main/java source folder, add the following content into it, and save it:
          package com.packt.webstore.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.EnableWebSecurity; 
          import org.springframework.security.config
          .annotation.web.configuration.WebSecurityConfigurerAdapter; 
     
          @Configuration 
          @EnableWebSecurity 
          public class SecurityConfig extends 
          WebSecurityConfigurerAdapter { 
     
              @Autowired 
              public void       configureGlobalSecurity(AuthenticationManagerBuilder auth)
       throws Exception { 
                  auth.inMemoryAuthentication().withUser("john").password("pa55word      ").roles("USER"); 
                  auth.inMemoryAuthentication().withUser("admin").password("root123      ").roles("USER","ADMIN"); 
              } 
           
              @Override 
              protected void configure(HttpSecurity httpSecurity)
              throws Exception { 
     
                 httpSecurity.formLogin().loginPage("/login") 
                             .usernameParameter("userId") 
                             .passwordParameter("password"); 
           
                httpSecurity.formLogin().defaultSuccessUrl
                ("/market/products/add")
                            .failureUrl("/login?error"); 
     
                 httpSecurity.logout().logoutSuccessUrl("/login?      
                 logout"); 
     
                 httpSecurity.exceptionHandling().accessDeniedPage
                 ("/login?accessDenied"); 
     
                 httpSecurity.authorizeRequests() 
                    .antMatchers("/").permitAll() 
                    .antMatchers("/**/add").access("hasRole('ADMIN')") 
                    .antMatchers("/**/market/**").access
                    ("hasRole('USER')"); 
     
                 httpSecurity.csrf().disable(); 
              } 
          } 
    
  8. Create one more initializer class called SecurityWebApplicationInitializer under the com.packt.webstore.config package in the src/main/java source folder, add the following content into it, and save it:
          package com.packt.webstore.config; 
     
          import org.springframework.security.web.context
          .AbstractSecurityWebApplicationInitializer; 
     
          public class SecurityWebApplicationInitializer extends
          AbstractSecurityWebApplicationInitializer { 
     
          } 
    
  9. Now open your addProduct.jsp file and add the following code after the <a href="?language=en" >English</a>|<a href="?language=nl" >Dutch</a> anchor tag:
          <a href="<c:url value="/logout" />">Logout</a> 
    
  10. Now run your application and enter the URL, http://localhost:8080/webstore/; you will see the welcome screen.
  11. Now try to access the products page by entering the URL, http://localhost:8080/webstore/market/products; a login page will be shown. Enter an arbitrary username and password; you will see an Invalid username and password error:
    Time for action - authenticating users based on roles

    Login page showing error messages for invalid credentials

  12. Now enter the username john and the password pa55word, and press the Log in button; you should be able to see the regular products page.
  13. Now try to access the add products page by entering the URL, http://localhost:8080/webstore/market/products/add; again the login page will be shown with the error message Access Denied: You are not authorised!
    Time for action - authenticating users based on roles

    Login page showing error messages for unauthorised users

  14. Now enter the username admin and the password root123, and press the Log in button; you should be able to see the regular add products page with a logout button in the top-right corner.

What just happened?

As usual, in order to use Spring Security in our project, we need some Spring Security-related JARs; from steps 1 to 4 we just added those JARs as Maven dependencies.

In step 5, we created one more controller called LoginController to handle all login-related web requests. It simply contains a single request mapping method to handle login-login failure and log out requests. Since the request mapping method returns a view named login, we need to create a view file called login.jsp, which is what we did in step 6.

login.jsp contains many tags with the bootstrap style class applied to enhance the look and feel of the login form; we don't need to concentrate on those tags. But some important tags are used to understand the flow; the first one is the <c:if> tag:

<c:if test="${param.error != null}"> 
   <div class="alert alert-danger"> 
      <p>Invalid username and password.</p> 
   </div> 
</c:if> 

<c:if > is a special JSTL tag to check a condition; it is more like an if...else condition that we use in our programming language. Using this <c:if> tag we are simply checking whether the page request parameter contains a variable called error; if the request parameter contains a variable called error we simply show an error message, Invalid username and password, within the <p> tag using the <spring:message> tag.

Similarly, we are also checking whether the request parameter contains variables called logout and accessDenied; if so we show the corresponding message, also within the <P> tag:

<c:if test="${param.logout != null}"> 
   <div class="alert alert-success"> 
      <p>You have been logged out successfully.</p> 
   </div> 
</c:if> 
 
<c:if test="${param.accessDenied != null}"> 
   <div class="alert alert-danger"> 
      <p>Access Denied: You are not authorised! </p> 
   </div> 
</c:if> 

So now we facilitated all possible login-related messages being shown in the login page. The other important tag in login.jsp is the form tag, which represents the login form. Notice the action attribute of the form tag:

<c:url var="loginUrl" value="/login" /> 
<form action="${loginUrl}" method="post" class="form-horizontal"> 

We are simply posting our login form values, such as username and password, to the Spring Security authentication handler URL, which is stored in the variable called ${loginUrl}. Here the special JSTL tag <c:url> is used to encode the URL.

Okay, now we have created a controller (LoginController) to dispatch the login page. But we need to tell Spring to present this login page to users if they try to access a page without logging in. How can we enforce that? This is where the WebSecurityConfigurerAdapter class comes in; by extending WebSecurityConfigurerAdapter, we can configure the HttpSecurity object for various security-related settings in our web application. So in step 7, we are simply creating a class called SecurityConfig to configure the security-related aspects of our web application.

One of the important methods in the SecurityConfig class is configureGlobalSecurity; under this method we are simply configuring AuthenticationManagerBuilder to create two users, john and admin, with a specified password and roles:

@Autowired 
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { 
   auth.inMemoryAuthentication().withUser("john") 
                                .password("pa55word") 
                                .roles("USER"); 
 
   auth.inMemoryAuthentication().withUser("admin") 
                                .password("root123") 
                                .roles("USER","ADMIN"); 
} 

The next important method is configure; within this method we are doing some authentication-and authorization-related configuration and we will see these one by one. The first configuration tells Spring MVC that it should redirect the users to the login page if authentication is required; here the loginpage attribute denotes to which URL it should forward the request to get the login form.

Remember this request path should be the same as the request mapping of the login () method of LoginController. We are also setting the user name parameter and password parameter name in this configuration:

httpSecurity.formLogin().loginPage("/login") 
            .usernameParameter("userId") 
            .passwordParameter("password"); 

With this configuration, while posting the username and password to the Spring Security authentication handler through the login page, Spring expects those values to be bound under the variable name userId and password respectively; that's why, if you notice, the input tags for username and password carry the name attributes userId and password in step 6:

<input type="text" class="form-control" id="userId" name="userId" placeholder="Enter Username" required> 
<input type="password" class="form-control" id="password" name="password" placeholder="Enter Password" required> 

Similarly, Spring handles the log out operation under the /logout URL; that's why in step 9 we formed the logout link on the add products page, as follows:

<a href="<c:url value="/logout" />">Logout</a> 

Next, we are just configuring the default success URL, which denotes the default landing page after a successful login; similarly the authentication failure URL indicates to which URL the request needs to be forwarded in the case of login failure:

httpSecurity.formLogin().defaultSuccessUrl("/market/products/add") 
                    .failureUrl("/login?error"); 

Notice we are setting the request parameter to error in the failure URL; thus when the login page is rendered, it will show the error message Invalid username and password in the case of login failure. Similarly, we can also configure the logout success URL, which denotes where the request needs to be forwarded to after a logout:

   httpSecurity.logout().logoutSuccessUrl("/login?logout"); 

You can also see that we are setting the request parameter as logout for the logout success URL to match the condition checking that we performed in login.jsp:

<c:if test="${param.logout != null}"> 
   <div class="alert alert-success"> 
      <p>You have been logged out successfully.</p> 
   </div> 
</c:if> 

And similarly, we also configured the redirection URL for the access denied page in the case of authorization failure as follows:

   httpSecurity.exceptionHandling().accessDeniedPage("/login?accessDenied"); 

The request parameter we are setting here for the access denied page URL should match the parameter name that we are checking in the login.jsp page:

<c:if test="${param.accessDenied != null}"> 
   <div class="alert alert-danger"> 
      <p>Access Denied: You are not authorised! </p> 
   </div> 
</c:if> 

So now we have configured almost all redirection URLs that specify to which page the user should get redirected in the case of login success, login failure, logout success, and access denial. The next configuration is the more important as it defines which user should get access to which page:

httpSecurity.authorizeRequests() 
   .antMatchers("/").permitAll() 
   .antMatchers("/**/add").access("hasRole('ADMIN')") 
   .antMatchers("/**/market/**").access("hasRole('USER')"); 

The preceding configuration defines three important authorization rules for our web application, in terms of Ant pattern matchers. The first one allows the request URLs that end with /, even if the request doesn't carry any roles. The next rule allows all request URLs that end with /add, if the request has the role ADMIN. The third rule allows all request URLs that have the path /market/ if they have the role USER.

Okay we defined all security-related configurations in the security context file, but Spring should know about this configuration file and will have to read this configuration file before booting the application. Only then can it create and manage those security-related configurations. How can we instruct Spring to pick up this file? The answer is AbstractSecurityWebApplicationInitializer; by extending the AbstractSecurityWebApplicationInitializer class, we can instruct Spring MVC to pick up our SecurityConfig class during bootup. So in step 8, that's what we are doing.

After finishing all the steps, if you run your application and enter the http://localhost:8080/webstore/ URL, you are able to see the welcome screen. Now try to access the products page by entering the http://localhost:8080/webstore/market/products URL; a login page will be shown. Enter an arbitrary username and password; you will see an Invalid username and password error.

Now enter the username john and the password pa55word, and press the Log in button; you should be able to see the regular products page. Now try to access the add products page by entering the http://localhost:8080/webstore/market/products/add URL; again a login page will be shown with an error message, Access Denied: You are not authorised! Now enter the username admin and the password root123, and press the Log in button; you should be able to see the regular add products page with a logout button in the top-right corner.

Pop quiz - Spring Security

Which URL is the Spring Security default authentication handler listening on for the username and password?

  1. /login
  2. /login?userName&password
  3. /handler/login

What is the default logout handler URL for Spring Security?

  1. /logout
  2. /login?logout
  3. /login?logout=true

Have a go hero - play with Spring Security

Just for demonstration purposes, I added the logout link only on the add products page; why don't you add the logout link to every page? Also, try adding new users and assigning new roles to them.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset