Spring Security framework provides authentication and authorization (which user can access what) to Java Web applications.

Example

Let us create a simple web application and then add spring security to it.

  1. Start Spring Source Toolsuite
  2. File -> New -> Other -> Maven -> Maven Project
  3. Choose maven-archetype-webapp as the Archetype
  4. GroupId      : springweb
    Artifact Id  : security
  5. If you do not see the folder src/main/java, create it.
  6. Add servlet and spring dependencies to the pom.xml file:

    pom.xml

    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>springweb</groupId>
        <artifactId>security</artifactId>
        <packaging>war</packaging>
        <version>0.0.1-SNAPSHOT</version>
        <name>spring security webapp</name>
        <url>http://maven.apache.org</url>
    
        <properties>
          <spring.version>4.2.0.RELEASE</spring.version>
          <log4j.version>1.2.14</log4j.version>
        </properties>
    
        <dependencies>
          <!-- servlet dependencies -->
    
          <dependency>
            <groupId>jstl</groupId>
            <artifactId>jstl</artifactId>
            <version>1.2</version>
          </dependency>
    
          <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
          </dependency>
    
          <dependency>
            <groupId>javax.servlet.jsp</groupId>
            <artifactId>javax.servlet.jsp-api</artifactId>
            <version>2.3.1</version>
            <scope>provided</scope>
          </dependency>
    
          <dependency>
             <groupId>commons-dbcp</groupId>
             <artifactId>commons-dbcp</artifactId>
             <version>1.4</version>
             <type>jar</type>
             <scope>runtime</scope>
          </dependency>
    
          <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
          </dependency>
    
          <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.2.1</version>
          </dependency>
    
          <!-- spring dependencies -->
    
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
          </dependency>
    
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>${spring.version}</version>
            <type>jar</type>
            <scope>compile</scope>
          </dependency>
    
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
            <type>jar</type>
            <scope>compile</scope>
          </dependency>
    
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context-support</artifactId>
            <version>${spring.version}</version>
            <type>jar</type>
            <scope>compile</scope>
          </dependency>
    
          <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
            <type>jar</type>
            <scope>compile</scope>
          </dependency>
    
          <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
          </dependency>
    
        </dependencies>
        <build>
           <finalName>security</finalName>
        </build>
    </project>
  7. Now, we will create controllers and views for two different pages. Create a new package com.security.controller. Add a new controller TestController to it:

    TestController.java

    package com.security.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class TestController {
    
      @RequestMapping("/")
      public String index(){
         return "index";
      }
     
      @RequestMapping("/admin/info")
      public String info(){
         return "info";
      }
    }
  8. Now create a new folder named views under WEB-INF. Create following two files in it:index.jsp
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <html>
     
       <head>
         <title>Welcome to Spring Security</title>
       </head>
     
       <body>
     
          <h3>Welcome to Spring Security Test Home Page</h3>
    
          <br/>
    
          <a href="<c:url value='admin/info'/>">Country sizes</a>
     
       </body>
    </html>

    info.jsp

    <html>
     
       <head>
          <title>Welcome to Spring Security</title>
       </head>
     
       <body>
     
          <h3>World's largest countries area wise</h3>
     
          <ol>
             <li>Russia</li>
             <li>Canada</li>
             <li>USA</li>
             <li>China</li>
             <li>Brazil</li>
             <li>Australia</li>
             <li>India</li>
          </ol>
     
       </body>
    </html>
  9. Create a new package named com.security.config in src/main/java folder for configuration classes. Create following classes in this package to load Spring and our web application configurations:

    AppConfig.java

    package com.security.config;
    
    import javax.servlet.ServletContext;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.view.InternalResourceViewResolver;
    import org.springframework.web.servlet.view.JstlView;
    
    @EnableWebMvc
    @Configuration
    @ComponentScan({ "com.security.*" })
    @Import({ WebConfig.class })
    public class AppConfig {
    
       @Autowired
       ServletContext servletContext;
     
       @Bean
       public InternalResourceViewResolver viewResolver() {
          InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
     
          viewResolver.setViewClass(JstlView.class);
          viewResolver.setPrefix("/WEB-INF/views/");
          viewResolver.setSuffix(".jsp");
     
          return viewResolver;
      }
    }

    SpringMvcInitializer.java

    package com.security.config;
    
    import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
    
    public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    
       @Override
       protected Class<?>[] getRootConfigClasses() {
           return new Class[] { AppConfig.class };
       }
    
       @Override
       protected Class<?>[] getServletConfigClasses() {
           return null;
       }
    
       @Override
       protected String[] getServletMappings() {
           return new String[] { "/" };
       }
    }

    WebConfig.java

    package com.security.config;
    
    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
    import org.springframework.web.servlet.config.annotation.EnableWebMvc;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
    
    @Configuration
    @EnableWebMvc
    public class WebConfig extends WebMvcConfigurerAdapter {
    
      @Override
      public void configureDefaultServletHandling( DefaultServletHandlerConfigurer configurer) {
          configurer.enable();
      }
    }
  10. Now at this moment, if you run your application, you can call following two URLs:

    http://localhost:8080/security
    http://localhost:8080/security/info

Enabling Spring Security

  1. To enable spring security in a web application, first we need to add following dependencies in our pom.xml file:
    <!-- spring security dependencies -->
    <dependency>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-web</artifactId>
       <version>4.1.1.RELEASE</version>
    </dependency>
    <dependency>
       <groupId>org.springframework.security</groupId>
       <artifactId>spring-security-config</artifactId>
       <version>4.1.1.RELEASE</version>
    </dependency>
  2. Now create the files  SecurityConfig.java in the package com.security.config to enable spring security and its configuration:

    SecurityWebApplicationInitializer.java

    package com.security.config;
    
    import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
    
    public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { 
    }

    SecurityConfig.java

    package com.security.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 configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
          auth.inMemoryAuthentication()
             .withUser("admin").password("test").roles("ADMIN");
    
          auth.inMemoryAuthentication()
             .withUser("test").password("test").roles("USER");
     }
     
     @Override
     protected void configure(HttpSecurity http) throws Exception {
    
          http.authorizeRequests()
            .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
            .and()
            .formLogin()
            .and().logout();
      }
    }

    This file will make following changes to the web application:

    a. Add authentication to every URL of the application
    b. Generate a login form
    c. Allow the user with username admin and password test
    d. Allow the user to logout
    e. CSRF (Cross Site Request Forgery) protection
    f. Session fixation protection

    Note: The above class uses in-memory authentication i.e. we have specified the username and password in the class only. Later we will specify the table from which login should be checked.

  3. Update your Import in AppConfig.java to this:  @Import({ WebConfig.class, SecurityConfig.class })
  4. Add following controller to your com.security.controller package:

    AuthController.java

    package com.security.controller;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.context.SecurityContextHolder;
    import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    @Controller
    public class AuthController {
    
       @RequestMapping(value = "/logout", method = RequestMethod.GET)
       public String logoutPage(HttpServletRequest request, HttpServletResponse response) {
           Authentication auth = SecurityContextHolder.getContext().getAuthentication();
           if (auth != null) {
               new SecurityContextLogoutHandler().logout(request, response, auth);
           }
           return "redirect:login";
       }
    }
  5. Lastly, edit the info.jsp file to show currently logged username and the logout link:

    info.jsp

    <%@taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    <%@page session="true" isELIgnored="false"%>
    <html>
     
     <head>
     <title>Welcome to Spring Security</title>
     </head>
     
     <body>
     
     Hello: ${pageContext.request.userPrincipal.name} 
     
     <h3>World's largest countries area wise</h3>
     
     <ol>
         <li>Russia</li>
         <li>Canada</li>
         <li>USA</li>
         <li>China</li>
         <li>Brazil</li>
         <li>Australia</li>
         <li>India</li>
     </ol>
     
     <a href='<c:url value="/logout" />'>Logout</a>
     
     </body>
    
    </html>
  6. Try to access the same URLs. You will be redirected to login page if you try to access admin/info. Login with username = admin, password = test and now you can see the admin pages.
  7. We don’t want the user test/test to view the admin/info page. So, we will make following modifications to our configuration.
    a. Update the configure() method in SecurityConfig class to this:

       @Override
       protected void configure(HttpSecurity http) throws Exception {
    
           http.authorizeRequests()
          .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
          .and()
          .formLogin()
          .and().logout()
          .and().exceptionHandling().accessDeniedPage("/403");
       }

    b. Add following method to AuthController class:

       @RequestMapping(value = "/403", method = RequestMethod.GET)
       public String accessDenied(HttpServletRequest request, HttpServletResponse response) {
          return "access-denied";
       }

    c. Create the view access-denied.jsp in the views folder:

       <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
    
       <h3>You are not allowed to view this page</h3>
    
       <br/>
    
       <a href="<c:url value='/'/>">Back</a>
  8. So, if you try to login with the user test/test and visit the info page, you will see the access denied page.

    Download the source code

Spring Security Login From Database

In the previous example, we had two fixed users for authentication. In a real world situation, we have users stored in a database table and we need to check login from there only.

Let us modify the above example to include database table for login check.

  1. Create a new database named spring_security in MySQL. Create following tables in database:
    CREATE TABLE users (
     username VARCHAR(45) NOT NULL ,
     password VARCHAR(45) NOT NULL ,
     enabled TINYINT NOT NULL DEFAULT 1 ,
     PRIMARY KEY (username)
    );
    CREATE TABLE user_roles (
     id int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
     username varchar(50) NOT NULL,
     role varchar(50) NOT NULL,
     UNIQUE KEY unique_username_role (role,username),
     CONSTRAINT fk_username FOREIGN KEY (username) REFERENCES users (username)
    );
  2. Add some data to both the tables:
    insert into users(username,password) values ('ashutosh','pandey');
    insert into users(username,password) values ('test','test');
    
    insert into user_roles(username,role) values ('ashutosh','ROLE_ADMIN');
    insert into user_roles(username,role) values ('test','ROLE_USER');
  3. Add data source information in the AppConfig.java configuration file:
    @Bean(name = "dataSource")
    public DriverManagerDataSource dataSource() {
    
       DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
    
       driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
       driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/spring_security");
    
       driverManagerDataSource.setUsername("root");
       driverManagerDataSource.setPassword("password");     // change value of password according to you
    
       return driverManagerDataSource;
    }
  4. Add MySQL and Spring JDBC dependencies in your pom.xml file:
    <dependency>
       <groupId>org.springframework</groupId>
       <artifactId>spring-jdbc</artifactId>
       <version>${spring.version}</version>
    </dependency>
    <dependency>
       <groupId>mysql</groupId>
       <artifactId>mysql-connector-java</artifactId>
       <version>5.1.30</version>
    </dependency>
  5. Lastly, update the SecurityConfig.java class to specify query for login:
    package com.security.config;
    
    import javax.sql.DataSource;
    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
       private DataSource dataSource;
     
       @Autowired
       public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
          /*
             auth.inMemoryAuthentication()
            .withUser("admin").password("test").roles("ADMIN");
             auth.inMemoryAuthentication()
            .withUser("test").password("test").roles("USER");
         */
     
         auth.jdbcAuthentication()
            .dataSource(dataSource)
            .usersByUsernameQuery("select username,password, enabled from users where username=?")
            .authoritiesByUsernameQuery("select username, role from user_roles where username=?");
        }
     
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
           .antMatchers("/admin/**").access("hasRole('ROLE_ADMIN')")
           .and()
           .formLogin()
           .and().logout()
           .and().exceptionHandling().accessDeniedPage("/403");
        }
    }

That’s it, your authentication will now work with database.