Spring Security for Spring MVC 4 Application Simple Example using Spring Boot
Well, after quite a long time, nearly a year, I am all set to publish my next post here. This has been a post that is long overdue ...
https://www.programming-free.com/2015/08/spring-security-for-spring-mvc-4.html
Well, after quite a long time, nearly a year, I am all set to publish my next post here. This has been a post that is long overdue and highly requested for. I am gonna write about how to secure a Spring MVC 4 web application using Spring Security. I am going to use Spring Boot to build a quick and configuration-less application. I have written in detail on how to use Spring Boot in Spring Data Rest application here.
Spring Boot can be used with build tools such as Maven or Gradle. These build tools help you share jars between various applications, build your application and generate reports. I am going to use the same application that is provided in the spring security getting started guide but with JSP for views.
Set up project with Spring Boot
1. Go to New -> Maven Project in Eclipse,
2. Click Next -> Check Create a simple project -> Give workspace location
3. Click Next ->In Archetype selection, select maven-archetype-webapp
When we are done implementing the project at the end of this tutorial, the project structure is going to look like this,
pom.xml
<?xml version="1.0" encoding="UTF-8"?> <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/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.programmingfree</groupId> <artifactId>pf-securing-web</artifactId> <version>0.1.0</version> <packaging>war</packaging> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.2.5.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.apache.tomcat.embed</groupId> <artifactId>tomcat-embed-jasper</artifactId> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies> <properties> <java.version>1.8</java.version> </properties> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> <repositories> <repository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/libs-release</url> </repository> </repositories> <pluginRepositories> <pluginRepository> <id>spring-releases</id> <name>Spring Releases</name> <url>https://repo.spring.io/libs-release</url> </pluginRepository> </pluginRepositories> </project>
-- If you notice in the above pom.xml we are adding two dependencies given below since we are using JSP for the views.
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
-- Though we use "war" for packaging, we will still be able to execute it. This is taken care of by 'spring-boot-maven-plugin'.
-- We have 'spring-boot-starter-security' as one of the dependencies and this is going to bootstrap everything related to security for us.
5. Create views inside WEB-INF\jsp one by one.
src\main\webapp\WEB-INF\jsp\home.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <title>Spring Security Example - ProgrammingFree</title> </head> <body> <h1>Welcome!</h1> <p>Click <a href="<spring:url value='/hello' />">here</a> to see a greeting.</p> </body> </html>
This is a simple welcome page and it is not going to be secured. This page has a link to a greeting page (hello.jsp) which can be accessed only after being authenticated.
src\main\webapp\WEB-INF\jsp\hello.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <title>Hello World!</title> </head> <body> <h1>Hello <b><c:out value="${pageContext.request.remoteUser}"/></b> </h1> <form action="/logout" method="post"> <input type="submit" value="Sign Out"/> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> </form> </body> </html>
This page displays a greeting message with the name of logged in user and can only be accessed by autheticated users.
src\main\webapp\WEB-INF\jsp\login.jsp
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %> <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <!DOCTYPE html> <html> <head> <title>Spring Security Example </title> </head> <body> <c:if test="${param.error ne null}"> <div> Invalid username and password. </div> </c:if> <c:if test="${param.logout ne null}"> <div> You have been logged out. </div> </c:if> <form action="/login" method="post"> <div><label> User Name : <input type="text" name="username"/> </label></div> <div><label> Password: <input type="password" name="password"/> </label></div> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <div><input type="submit" value="Sign In"/></div> </form> </body> </html>
As the name states itself, this page contains the login form for the users to submit their credentials.
6. Create three java classes inside a package called 'hello' with the code given below.
src\main\java\hello\MvcConfig.java
package hello; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; @Configuration public class MvcConfig extends WebMvcConfigurerAdapter { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/home").setViewName("home"); registry.addViewController("/").setViewName("home"); registry.addViewController("/hello").setViewName("hello"); registry.addViewController("/login").setViewName("login"); } @Bean public InternalResourceViewResolver viewResolver() { InternalResourceViewResolver resolver = new InternalResourceViewResolver(); resolver.setPrefix("/WEB-INF/jsp/"); resolver.setSuffix(".jsp"); return resolver; } }
src\main\java\hello\WebSecurityConfig.java
package hello; 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("/", "/home").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll() .and() .logout() .permitAll(); } @Autowired public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { auth .inMemoryAuthentication() .withUser("user").password("password").roles("USER"); } }
src\main\java\hello\Application.java
package hello; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class Application { public static void main(String[] args) throws Throwable { SpringApplication.run(Application.class, args); } }
That is all! Now to run the application, right click project, run as Maven Build with the goal,
clean install spring-boot:run
This will install the project with all necessary dependencies, generate war file in target folder and start the embedded tomcat server provided by spring boot. Open a browser and hit http://localhost:8080/ to see the home page,
On clicking the link for greeting message, user will be redirected to login page as shown below,
As you can see, the login page provides a simple form that captures a username and password and posts them to "/login". As configured, Spring Security provides a filter that intercepts that request and authenticates the user. If the user fails to authenticate, the page is redirected to "/login?error" and our page displays the appropriate error message. Upon successfully signing out, our application is sent to "/login?logout" and our page displays the appropriate success message.
User is redirected to login page after signing out, with a logout message,
If the credentials are wrong, user is redirected to login page with error message,
To know how to directly run the downloaded project, watch this video.
clean install spring-boot:run
This will install the project with all necessary dependencies, generate war file in target folder and start the embedded tomcat server provided by spring boot. Open a browser and hit http://localhost:8080/ to see the home page,
On clicking the link for greeting message, user will be redirected to login page as shown below,
As you can see, the login page provides a simple form that captures a username and password and posts them to "/login". As configured, Spring Security provides a filter that intercepts that request and authenticates the user. If the user fails to authenticate, the page is redirected to "/login?error" and our page displays the appropriate error message. Upon successfully signing out, our application is sent to "/login?logout" and our page displays the appropriate success message.
User is redirected to login page after signing out, with a logout message,
If the credentials are wrong, user is redirected to login page with error message,
To know how to directly run the downloaded project, watch this video.
How it works
First let me start with application setup. Throughout the implementation, we did not write any xml configuration and even web.xml is eliminated with the use of Spring Boot. Let me go step by step on how Spring Boot set up the application for us,1. Once maven downloads all required libraries to the classpath (WEB-INF\lib), Spring Boot looks into the classpath and makes reasonable assumptions about what you’re missing, and adds it.
2. Spring Boot launches an application from a class which is annotated with @SpringBootApplication, so in our example it starts with 'Application.java'
@SpringBootApplication is a convenience annotation that adds all of the following:
-- @Configuration tags the class as a source of bean definitions for the application context.
-- @EnableAutoConfiguration tells Spring Boot to start adding beans based on classpath settings, other beans, and various property settings.
-- Normally you would add @EnableWebMvc for a Spring MVC app, but Spring Boot adds it automatically when it sees spring-webmvc on the classpath. This flags the application as a web application and activates key behaviors such as setting up a DispatcherServlet.
-- @ComponentScan tells Spring to look for other components, configurations, and services inside the same package as it is in. In this case it looks in to all classes inside the 'hello' package.
3. While 'hello' package is scanned, it will go through all classes with @Configuration and will register all configurations present. In our example, we have MvcConfig and WebSecurityConfig annotated with @Configuration
4. MvcConfig class registers the views with urls and hence exposes these url mappings to corresponding views.
Spring Security
When spring-security is present in the classpath, Spring automatically secures all HTTP end points with basic authentication. To further customize the security settings, such as authenticating users against details stored in a database or autenticate only specific http endpoints not all etc. you should setup security configuration. In our example, we provide a simple in-memory authentication for all the pages except for the home page(home.jsp) and this is configured in WebSecurityConfig class.
The WebSecurityConfig class is annotated with @EnableWebMvcSecurity to enable Spring Security’s web security support and provide the Spring MVC integration. It also extends WebSecurityConfigurerAdapter and overrides a couple of its methods to set some specifics of the web security configuration. The configure(HttpSecurity) method defines which URL paths should be secured and which should not. Specifically, the "/" and "/home" paths are configured to not require any authentication. All other paths must be authenticated.
When a user successfully logs in, they will be redirected to the previously requested page that requires authentication. There is a custom "/login" page specified by loginPage(), and everyone is allowed to view it. As for the configure(AuthenticationManagerBuilder) method, it sets up an in-memory user store with a single user. That user is given a username of "user", a password of "password", and a role of "USER".
Last we need to provide the user a way to display the current username and Sign Out. Update the hello.html to say hello to the current user and contain a "Sign Out" form as shown below
Last we need to provide the user a way to display the current username and Sign Out. Update the hello.html to say hello to the current user and contain a "Sign Out" form as shown below
CSRF Attack
You might have noticed myself placing hidden input type with name="${_csrf.parameterName}" and value="${_csrf.token}" in the login page and the page from where users log out. This is to protect the application against Cross Site Request Forgery (CSRF) attacks.
CSRF protection is enabled by default with Java Configuration. You can disable it as well. If you are using Spring MVC <form:form> tag, the CsrfToken is automatically included. Since we are not using Spring form tag, I have used hidden inputs to send csrf tokens to the server. A detailed and a very clear explanation of CSRF support in Spring MVC is provided in this article.
Keep yourself subscribed for getting programmingfree articles delivered directly to your inbox once in a month. Thanks for reading!
Thanks for sharing the useful tutorial..
ReplyDeleteSorry, but application doesn't work. After trying to access any page it writes:
ReplyDeleteWhitelabel Error Page
This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Nov 16 14:57:45 CET 2015
There was an unexpected error (type=Not Found, status=404).
No message available
I got the same error
DeleteThank you for this tutorial, everything works )
ReplyDeleteAwesome Tutorial !!
ReplyDeleteHi there, but why CSS doesn't get loaded, please refer to the article how to use resources in spring boot security.
ReplyDeleteIt works without @EnableWebMVC how to make it use static resources with that annotation for security
DeleteAivivu vé máy bay giá rẻ
ReplyDeleteve may bay tet 2021
vé máy bay đi Mỹ bao nhiêu
vé máy bay đi Pháp
vé máy bay từ hồ chí minh đi hàn quốc
vé máy bay đi nhật bản bao nhiêu
mua vé máy bay đi Anh
Aivivu chuyên vé máy bay, tham khảo
ReplyDeletevé máy bay đi Mỹ
đặt vé máy bay từ mỹ về việt nam
vé bay từ nhật về việt nam
ve may bay tư duc ve viet nam
gia ve may bay vietjet tu han quoc ve viet nam
This comment has been removed by the author.
ReplyDeleteA person who recognizes clinical terms and also cognizant of all the medicines, tablets, shot liquids that remain in the pharmacy technician resume skills in their clinical terms in addition to even more layperson names.
ReplyDeleteđại lý Japan Airlines
ReplyDeletephí đổi vé máy bay China Airlines
điều kiện hoàn vé Eva Air