Spring Security

springmvc

spring-security-oauth

https://www.petrikainulainen.net/spring-social-tutorial/
https://docs.spring.io/spring-security/site/docs/4.0.4.RELEASE/reference/htmlsingle/
https://spring.io/guides/tutorials/spring-security-and-angular-js/
https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/
https://spring.io/guides/gs/securing-web/
http://www.mkyong.com/tutorials/spring-security-tutorials/
http://www.baeldung.com/security-spring
http://www.baeldung.com/securing-a-restful-web-service-with-spring-security
http://websystique.com/spring-security-tutorial/

What are the overall steps that we have to do in order to use Spring Security?

  1. Add the dependencies to the Maven pom.xml file.
  2. Add the SecurityConfiguration configuration class which extends WebSecurityConfigurerAdapter, and has the @Configuration annotation and the @EnableWebSecurity annotation.
  3. Execute Spring Security before every controller requests by using a filter.

What are the dependencies that we need to add to Maven pom.xml file in order to use Spring Security?

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-web</artifactId>
    <version>4.0.1.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-config</artifactId>
    <version>4.0.1.RELEASE</version>
</dependency>

What do we need to add to web.xml in order to execute Spring Security before every controller requests?

<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>

Complete example of using Spring Security:

/src/main/java/com/in28minutes/login/LogoutController.java:

package com.in28minutes.login;

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 LogoutController {

    @RequestMapping(value = "/logout", method = RequestMethod.GET)
    public String logout(HttpServletRequest request, HttpServletResponse response) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();

        if (auth != null) {
            new SecurityContextLogoutHandler().logout(request, response, auth);
        }
        return "redirect:/";
    }
}

/src/main/java/com/in28minutes/login/WelcomeController.java:

package com.in28minutes.login;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class WelcomeController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public String showWelcomePage(ModelMap model) {
        model.put("name", getLoggedInUserName());
        return "welcome";
    }

    private String getLoggedInUserName() {
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();

        if (principal instanceof UserDetails) {
            return ((UserDetails) principal).getUsername();
        }

        return principal.toString();
    }
}

/src/main/java/com/in28minutes/security/SecurityConfiguration.java:

package com.in28minutes.security;

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 SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication().withUser("in28Minutes").password("dummy").roles("USER", "ADMIN");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/login").permitAll()
                .antMatchers("/", "/*todo*/**").access("hasRole('USER')").and()
                .formLogin();
    }
}

/src/main/webapp/WEB-INF/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">

    <display-name>To do List</display-name>

    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>/WEB-INF/todo-servlet.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

   <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> 
</web-app>

What is the purpose of the Concurrency Support feature part of Spring Security?

It supports transferring user credentials between threads.

What does Spring Security cover?

  1. Authentication
  2. Authorization
  3. Protection against attacks like session fixation, clickjacking, cross site request forgery, etc
  4. Servlet API integration
  5. Optional integration with Spring MVC
  6. Portability

What are the overall steps to implement Spring Security using the JavaConfig approach?

  1. Add the necessary dependencies
  2. Create the Spring Security Configuration class
  3. Register Spring Security with the war

What packages do we need to implement Spring Security?

<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-web</artifactId>
  <version>4.1.3.RELEASE</version>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-config</artifactId>
  <version>4.1.3.RELEASE</version>
</dependency>

We also have to tell Maven to update the project:

  1. Right click on the spring-security-samples-xml-insecure application
  2. Select Maven→Update project…​
  3. Ensure the project is selected, and click OK

What should our Spring Security Configuration class look like?

  1. Right click the spring-security-samples-xml-insecure project in the Package Explorer view
  2. Select New→Class
  3. Enter org.springframework.security.samples.config for the Package
  4. Enter SecurityConfig for the Name
  5. Click Finish
  6. Replace the file with the following contents:
package org.springframework.security.samples.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.*;

@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}

The name of the configureGlobal method is not important. However, it is important to only configure AuthenticationManagerBuilder in a class annotated with either @EnableWebSecurity, @EnableGlobalMethodSecurity, or @EnableGlobalAuthentication. Doing otherwise has unpredictable results.

How can we register Spring Security with the war?

We have created the Spring Security configuration, but we still need to register it with the war. This can be done using the following steps:

  1. Navigate to the Package Explorer view
  2. Right click the org.springframework.security.samples.config package within the spring-security-samples-xml-insecure project
  3. Select New→Class
  4. Enter SecurityWebApplicationInitializer for the Name
  5. Click Finish
  6. Replace the file with the following contents:
package org.springframework.security.samples.config;

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

public class SecurityWebApplicationInitializer
      extends AbstractSecurityWebApplicationInitializer {

    public SecurityWebApplicationInitializer() {
        super(SecurityConfig.class);
    }
}

The SecurityWebApplicationInitializer will do the following things:

  1. Automatically register the springSecurityFilterChain Filter for every URL in your application
  2. Add a ContextLoaderListener that loads the SecurityConfig.

Since we were not already using Spring, this is a simple way to add our SecurityConfig. If we were already using Spring, then we should add our SecurityConfig with the reset of our Spring configuration (i.e. a subclass of AbstractContextLoaderInitializer or AbstractDispatcherServletInitializer) and use the default constructor instead.

How can we output the name of the authenticated user?

<c:out value="${pageContext.request.remoteUser}"/>

The <c:out /> tag ensures the username is escaped to avoid XSS vulnerabilities Regardless of how an application renders user inputed values, it should ensure that the values are properly escaped.

How can we implement the logout functionality using Spring Security?

<c:url var="logoutUrl" value="/logout"/>
<form class="form-inline" action="${logoutUrl}" method="post">
  <input type="submit" value="Log out" />
  <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>

In order to help protect against CSRF attacks, by default, Spring Security Java Configuration log out requires:

  1. the HTTP method must be a POST
  2. the CSRF token must be added to the request. You can access it on the ServletRequest using the attribute _csrf as illustrated above.

If you were using Spring MVC’s tag library or Thymeleaf, the CSRF token is automatically added as a hidden input for you.

What are the overall steps to implement Spring Security using the XML approach?

  1. Add the dependencies
  2. Creating your Spring Security configuration XML file
  3. Registering Spring Security with the war

How can we create the Spring Security configuration XML file?

  1. In the Package Explorer view, right click on the folder src/main/webapp
  2. Select New→Folder
  3. Enter WEB-INF/spring for the Folder name
  4. Then right click on the new folder WEB-INF/spring
  5. Select New→File
  6. Enter security.xml for the File name
  7. Click Finish
  8. Replace the contents of the file with the following:
<b:beans xmlns="http://www.springframework.org/schema/security"
         xmlns:b="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
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <http />
    <user-service>
        <user name="user" password="password" authorities="ROLE_USER" />
    </user-service>
</b:beans>

How can we register Spring Security with the war using the XML approach?

  1. In the Package Explorer view, right click on the folder src/main/webapp/WEB-INF
  2. Select New→File
  3. Enter web.xml for the File name
  4. Click Finish
  5. Replace the contents of the file with the following:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" 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">

    <!--
      - Location of the XML file that defines the root application context
      - Applied by ContextLoaderListener.
      -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>
            /WEB-INF/spring/*.xml
        </param-value>
    </context-param>

    <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>

    <!--
      - Loads the root application context of this web app at startup.
      - The application context is then available via
      - WebApplicationContextUtils.getWebApplicationContext(servletContext).
    -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

</web-app>

The web.xml will do the following things:

  1. Registers the springSecurityFilterChain Filter for every URL in your application
  2. Adds a ContextLoaderListener that loads the security-config-xml.

What are the overall steps to implement Spring Security together with Spring MVC?

  1. Add the dependencies
  2. Create our Spring Security configuration class (this look the same as for the JavaConfig approach)
  3. Register Spring Security with the war
  4. Verify SecurityConfig is loaded

How can we create our Spring Security configuration class for the Spring MVC approach?

  1. Right click the spring-security-samples-xml-insecuremvc project in the Package Explorer view
  2. Select New→Class
  3. Enter org.springframework.security.samples.config for the Package
  4. Enter SecurityConfig for the Name
  5. Click Finish
  6. Replace the file with the following contents:
package org.springframework.security.samples.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.*;

@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }
}

How can we register Spring Security with the war if we are using Spring Security together with Spring MVC?

  1. Right click the spring-security-samples-xml-insecuremvc project the Package Explorer view
  2. Select New→Class
  3. Enter org.springframework.security.samples.config for the Package
  4. Enter MessageSecurityWebApplicationInitializer for the Name
  5. Click Finish
  6. Replace the file with the following contents:
package org.springframework.security.samples.config;

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

public class MessageSecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer {
}

The MessageSecurityWebApplicationInitializer will automatically register the springSecurityFilterChain Filter for every URL in your application. If Filters are added within other WebApplicationInitializer instances we can use @Order to control the ordering of the Filter instances.

How can we verify SecurityConfig is loaded for Spring MVC?

Just because SecurityConfig exists, does not mean that our Spring application knows about it. In this instance, our Spring root application context is initialized using MessageWebApplicationInitializer which is included with our spring-security-samples-javaconfig-messages project. You can find a snippet of it below:

public class MessageWebApplicationInitializer extends
        AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[] { RootConfiguration.class };
    }

    // ... other overrides ...
}

You will notice it is loading the RootConfiguration class which is also included in our spring-security-samples-javaconfig-messages project. RootConfiguration.java:

@Configuration
@ComponentScan
public class RootConfiguration {
}

The @ComponentScan is loading all configuration within the same package (and child packages) as RootConfiguration. Since SecurityConfig is in this package, it will be loaded with our existing setup and there is nothing more to do. Had SecurityConfig not been loaded, we could have used an @Import(SecurityConfig.class) above the class definition of RootConfiguration or added SecurityConfig as one of the results for getRootConfigClasses().

How can we customize the default login form?

As we saw in Hello Spring MVC Security Java Config, Spring Security’s WebSecurityConfigurerAdapter provides some convenient defaults to get our application up and running quickly. However, our login form does not look like the rest of our application. Let’s see how we can update our configuration to use a custom form. The default configuration for the configure(HttpSecurity) method can be seen below:

protected void configure(HttpSecurity http) throws Exception {
    http
        .authorizeRequests()
            .anyRequest().authenticated() 
            .and()
        .formLogin()                      
            .and()
        .httpBasic();                     
}

The configuration ensures that:

  1. every request requires the user to be authenticated
  2. form based authentication is supported
  3. HTTP Basic Authentication is supported

We will want to ensure we compensate for overriding these defaults in our updates. Open up the SecurityConfig and insert the configure method as shown below:

import org.springframework.security.config.annotation.web.builders.HttpSecurity;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login");
    }

    // ...
}

The line loginPage("/login") instructs Spring Security:

  1. when authentication is required, redirect the browser to /login
  2. we are in charge of rendering the login page when /login is requested
  3. when authentication attempt fails, redirect the browser to /login?error (since we have not specified otherwise)
  4. we are in charge of rendering a failure page when /login?error is requested
  5. when we successfully logout, redirect the browser to /login?logout (since we have not specified otherwise)
  6. we are in charge of rendering a logout confirmation page when /login?logout is requested

Go ahead and start up the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. In many browsers you will see an error similar to This webpage has a redirect loop. What is happening?

The issue is that Spring Security is protecting access to our custom login page. In particular the following is happening:

  1. We make a request to our web application
  2. Spring Security sees that we are not authenticated
  3. We are redirected to /login
  4. The browser requests /login
  5. Spring Security sees that we are not authenticated
  6. We are redirected to /login …​

To fix this we need to instruct Spring Security to allow anyone to access the /login URL. We can easily do this with the following updates (src/main/java/org/springframework/security/samples/config/SecurityConfig.java):

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll();
    }

    // ...
}

The method formLogin().permitAll() statement instructs Spring Security to allow any access to any URL (i.e. /login and /login?error) associated to formLogin().

Granting access to the formLogin() URLs is not done by default since Spring Security needs to make certain assumptions about what is allowed and what is not. To be secure, it is best to ensure granting access to resources is explicit.

Start up the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. You should now get a 500 error stating Error resolving template "login" because we have not created the /login page yet. Within Spring Web MVC there are two steps to creating our login page:

  1. Creating a controller
  2. Creating a view

Within Spring Web MVC, the first step is to ensure that we have a controller that can point to our view. Since our project adds the javaconfig/messages project as a dependency and it contains a view controller for /login we do not need to create a controller within our application. For reference, you can see the configuration below:

@EnableWebMvc
@ComponentScan("org.springframework.security.samples.mvc")
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {

    // ...

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/login").setViewName("login");
        registry.setOrder(Ordered.HIGHEST_PRECEDENCE);
    }
}

Our existing configuration means that all we need to do is create a login.html file with the following contents:

<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
  <head>
    <title tiles:fragment="title">Messages : Create</title>
  </head>
  <body>
    <div tiles:fragment="content">
        <form name="f" th:action="@{/login}" method="post">               
            <fieldset>
                <legend>Please Login</legend>
                <div th:if="${param.error}" class="alert alert-error">    
                    Invalid username and password.
                </div>
                <div th:if="${param.logout}" class="alert alert-success"> 
                    You have been logged out.
                </div>
                <label for="username">Username</label>
                <input type="text" id="username" name="username"/>        
                <label for="password">Password</label>
                <input type="password" id="password" name="password"/>    
                <div class="form-actions">
                    <button type="submit" class="btn">Log in</button>
                </div>
            </fieldset>
        </form>
    </div>
  </body>
</html>
  1. The URL we submit our username and password to is the same URL as our login form (i.e. /login), but a POST instead of a GET.
  2. When authentication fails, the browser is redirected to /login?error so we can display an error message by detecting if the parameter error is non-null.
  3. When we are successfully logged out, the browser is redirected to /login?logout so we can display an logout success message by detecting if the parameter logout is non-null.
  4. The username should be present on the HTTP parameter username
  5. The password should be present on the HTTP parameter password

Do not display details about why authentication failed. For example, we do not want to display that the user does not exist as this will tell an attacker that they should try a different username.

We use Thymeleaf to automatically add the CSRF token to our form. If we were not using Thymleaf or Spring MVCs taglib we could also manually add the CSRF token using <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>.

Start up the server and try visiting http://localhost:8080/sample/ to see the updates to our configuration. We now see our login page, but it does not look very pretty. The issue is that we have not granted access to the css files.

We need to update our configuration to allow anyone to access our resources and our logout pages. Update the configuration as shown below (src/main/java/org/springframework/security/samples/config/SecurityConfig.java):

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/resources/**").permitAll() 
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()                                    
                .permitAll();
    }

    // ...
}

This allows anyone to access a URL that begins with /resources/. Since this is where our css, javascript, and images are stored all our static resources are viewable by anyone.

As you might expect, logout().permitAll() allows any user to request logout and view logout success URL.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License