Spring Security + Thymeleaf (CSRF) (#695)

* Expression-Based Access Control

PermitAll, hasRole, hasAnyRole etc.
I modified classes regards to Security

* Added test cases for Spring Security Expressions

* Handler Interceptor - logging example

* Test for logger interceptor

* Removed conflicted part

* UserInterceptor (adding user information to model)

* Spring Handler Interceptor - session timers

* Spring Security CSRF attack protection with Thymeleaf
This commit is contained in:
maibin 2016-09-23 14:37:53 +02:00 committed by Grzegorz Piwowarek
parent 043a6bddc8
commit b0a1959cf4
8 changed files with 224 additions and 49 deletions

View File

@ -14,6 +14,7 @@ import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.WebApplicationContext;
import com.fasterxml.jackson.core.JsonProcessingException;
@ -21,6 +22,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@Transactional
public class CsrfAbstractIntegrationTest {
@Autowired

View File

@ -8,24 +8,24 @@
<properties>
<java-version>1.7</java-version>
<!-- spring -->
<org.springframework-version>4.1.8.RELEASE</org.springframework-version>
<org.springframework-version>4.3.3.RELEASE</org.springframework-version>
<javax.servlet-version>3.0.1</javax.servlet-version>
<!-- logging -->
<org.slf4j.version>1.7.12</org.slf4j.version>
<logback.version>1.1.3</logback.version>
<org.slf4j.version>1.7.12</org.slf4j.version>
<logback.version>1.1.3</logback.version>
<!-- thymeleaf -->
<org.thymeleaf-version>2.1.4.RELEASE</org.thymeleaf-version>
<!-- validation -->
<javax.validation-version>1.1.0.Final</javax.validation-version>
<org.hibernate-version>5.1.2.Final</org.hibernate-version>
<!-- Maven plugins -->
<maven-compiler-plugin.version>3.5.1</maven-compiler-plugin.version>
<maven-war-plugin.version>2.6</maven-war-plugin.version>
<maven-surefire-plugin.version>2.19.1</maven-surefire-plugin.version>
<cargo-maven2-plugin.version>1.4.18</cargo-maven2-plugin.version>
</properties>
<dependencies>
<!-- Spring -->
<dependency>
@ -45,6 +45,17 @@
<artifactId>spring-webmvc</artifactId>
<version>${org.springframework-version}</version>
</dependency>
<!-- 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>
<!-- Thymeleaf -->
<dependency>
<groupId>org.thymeleaf</groupId>
@ -57,29 +68,29 @@
<version>${org.thymeleaf-version}</version>
</dependency>
<!-- Logging -->
<!-- logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<!-- <scope>runtime</scope> -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
<!-- <scope>runtime</scope> --> <!-- some spring dependencies need to compile against jcl -->
</dependency>
<dependency> <!-- needed to bridge to slf4j for projects that use the log4j APIs directly -->
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<!-- logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
<!-- <scope>runtime</scope> -->
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
<!-- <scope>runtime</scope> --> <!-- some spring dependencies need to compile against jcl -->
</dependency>
<dependency> <!-- needed to bridge to slf4j for projects that use the log4j APIs directly -->
<groupId>org.slf4j</groupId>
<artifactId>log4j-over-slf4j</artifactId>
<version>${org.slf4j.version}</version>
</dependency>
<!-- Servlet -->
<dependency>
<groupId>javax.servlet</groupId>
@ -98,6 +109,31 @@
<artifactId>hibernate-validator</artifactId>
<version>${org.hibernate-version}</version>
</dependency>
<!-- test scoped -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.3.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security/spring-security-test -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<version>4.1.3.RELEASE</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
@ -129,25 +165,25 @@
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>${cargo-maven2-plugin.version}</version>
<configuration>
<wait>true</wait>
<container>
<containerId>jetty8x</containerId>
<type>embedded</type>
<systemProperties>
</systemProperties>
</container>
<configuration>
<properties>
<cargo.servlet.port>8082</cargo.servlet.port>
</properties>
</configuration>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.cargo</groupId>
<artifactId>cargo-maven2-plugin</artifactId>
<version>${cargo-maven2-plugin.version}</version>
<configuration>
<wait>true</wait>
<container>
<containerId>jetty8x</containerId>
<type>embedded</type>
<systemProperties>
</systemProperties>
</container>
<configuration>
<properties>
<cargo.servlet.port>8082</cargo.servlet.port>
</properties>
</configuration>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,11 @@
package com.baeldung.thymeleaf.config;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
public class InitSecurity extends AbstractSecurityWebApplicationInitializer {
public InitSecurity() {
super(WebMVCSecurity.class);
}
}

View File

@ -20,7 +20,7 @@ public class WebApp extends AbstractAnnotationConfigDispatcherServletInitializer
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { WebMVCConfig.class };
return new Class<?>[] { WebMVCConfig.class, WebMVCSecurity.class, InitSecurity.class };
}
@Override

View File

@ -1,6 +1,5 @@
package com.baeldung.thymeleaf.config;
import com.baeldung.thymeleaf.formatter.NameFormatter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@ -14,6 +13,8 @@ import org.thymeleaf.spring4.SpringTemplateEngine;
import org.thymeleaf.spring4.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ServletContextTemplateResolver;
import com.baeldung.thymeleaf.formatter.NameFormatter;
@Configuration
@EnableWebMvc
@ComponentScan({ "com.baeldung.thymeleaf" })

View File

@ -0,0 +1,50 @@
package com.baeldung.thymeleaf.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true, prePostEnabled = true)
public class WebMVCSecurity extends WebSecurityConfigurerAdapter {
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
public WebMVCSecurity() {
super();
}
@Override
protected void configure(final AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user1").password("user1Pass").authorities("ROLE_USER");
}
@Override
public void configure(final WebSecurity web) throws Exception {
web.ignoring().antMatchers("/resources/**");
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.httpBasic()
.and()
;
}
}

View File

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
</head>
<body>
<form action="http://localhost:8080/spring-thymeleaf/saveStudent" method="post">
<input type="hidden" name="payload" value="CSRF attack!"/>
<input type="submit" />
</form>
</body>
</html>

View File

@ -0,0 +1,63 @@
package org.baeldung.security.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import javax.servlet.Filter;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.RequestPostProcessor;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import com.baeldung.thymeleaf.config.InitSecurity;
import com.baeldung.thymeleaf.config.WebApp;
import com.baeldung.thymeleaf.config.WebMVCConfig;
import com.baeldung.thymeleaf.config.WebMVCSecurity;
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = { WebApp.class, WebMVCConfig.class, WebMVCSecurity.class, InitSecurity.class })
public class CsrfEnabledIntegrationTest {
@Autowired
WebApplicationContext wac;
@Autowired
MockHttpSession session;
private MockMvc mockMvc;
@Autowired
private Filter springSecurityFilterChain;
protected RequestPostProcessor testUser() {
return user("user1").password("user1Pass").roles("USER");
}
@Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).addFilters(springSecurityFilterChain).build();
}
@Test
public void addStudentWithoutCSRF() throws Exception {
mockMvc.perform(post("/saveStudent").contentType(MediaType.APPLICATION_JSON).param("id", "1234567").param("name", "Joe").param("gender", "M").with(testUser())).andExpect(status().isForbidden());
}
@Test
public void addStudentWithCSRF() throws Exception {
mockMvc.perform(post("/saveStudent").contentType(MediaType.APPLICATION_JSON).param("id", "1234567").param("name", "Joe").param("gender", "M").with(testUser()).with(csrf())).andExpect(status().isOk());
}
}