Spring 启动 + 安全 + Thymeleaf 和 CSRF 令牌未自动注入
Spring Boot + Security + Thymeleaf and CSRF token not injected automatically
免责声明:我知道如何使用 thymeleaf 手动将令牌注入表单:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />`
这个post的目标是提高对平台的了解,更好地了解内部发生的事情Spring Boot
我还没有尝试过 Spring Boot,但最近我决定试一试,不得不承认它很棒,但是在 Spring MVC 上使用 Thymeleaf 和 Security,我没有不需要在表单上注入 CSRF 令牌 (POST),因为 Thymeleaf 会自动处理它,但现在由于某些原因在 Spring 启动时它不会。
从 Spring Boot Reference 中,我找到了 application.properties 文件中使用的常用属性列表,其中与 thymeleaf 和安全性相关的是:
Thymeleaf 特性
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.excluded-view-names= # comma-separated list of view names that should be excluded from resolution
spring.thymeleaf.view-names= # comma-separated list of view names that can be resolved
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html # ;charset=<encoding> is added
spring.thymeleaf.cache=true # set to false for hot refresh
安全属性
security.user.name=user # login username
security.user.password= # login password
security.user.role=USER # role assigned to the user
security.require-ssl=false # advanced settings ...
security.enable-csrf=false
security.basic.enabled=true
security.basic.realm=Spring
security.basic.path= # /**
security.basic.authorize-mode= # ROLE, AUTHENTICATED, NONE
security.filter-order=0
security.headers.xss=false
security.headers.cache=false
security.headers.frame=false
security.headers.content-type=false
security.headers.hsts=all # none / domain / all
security.sessions=stateless # always / never / if_required / stateless
security.ignored= # Comma-separated list of paths to exclude from the default secured paths
但是如果有让 Thymeleaf 再次注入令牌的解决方案,我看不到它。
编辑:添加我的配置
该项目是使用上一个 STS 版本(在我看来很棒)中附带的初始化程序创建的,具有 Web、Thymeleaf、Security、JPA、MySQL、H2、Mail、Facebook、检查了 Twitter、LinkedIn 和 Actuator 项目,并在后面添加了一些额外内容
使用 Java 7 和 Tomcat 7 因为我打算在不久的将来在 Openshift 上部署该项目,接下来是我的配置文件:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.adrisasws.springmvc.WebApplication</start-class>
<java.version>1.7</java.version>
<tomcat.version>7.0.59</tomcat.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>1.1.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-facebook</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-linkedin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-twitter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-google</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>openshift</id>
<build>
<finalName>webapp</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<outputDirectory>webapps</outputDirectory>
<warName>ROOT</warName>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
安全配置(与我在非引导项目中使用的安全文件完全相同,其中 CSRF 令牌实际上是自动注入的)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//////////////////////////////////////////////////////////////////////////
// DEPENDENCIES //
//////////////////////////////////////////////////////////////////////////
@Autowired private DataSource dataSource;
@Autowired private UserRepository userRepository;
//////////////////////////////////////////////////////////////////////////
// PROPERTIES //
//////////////////////////////////////////////////////////////////////////
@Value("${custom.security.rememberme-secret}") private String secret;
@Value("${custom.security.rememberme-create-tables}") private String createTables;
private final static String[] adminRequests = new String[] { ... some matchers here... };
private final static String[] userRequests = new String[] { ... some matchers here... };
private final static String[] publicRequests = new String[] { ...some matchers here... };
//////////////////////////////////////////////////////////////////////////
// AUTHORIZATION //
//////////////////////////////////////////////////////////////////////////
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/error**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(adminRequests).access("hasRole('"+Role.ADMIN.toString()+"')")
.antMatchers(userRequests).access("hasRole('"+Role.USER.toString()+"')")
.antMatchers(publicRequests).permitAll()
.anyRequest().authenticated()
.and()
.requiresChannel()
.anyRequest().requiresSecure()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/", false)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.rememberMe()
.rememberMeServices(rememberMeService())
.and()
.apply(new SpringSocialConfigurer());
}
//////////////////////////////////////////////////////////////////////////
// AUTHENTICATION //
//////////////////////////////////////////////////////////////////////////
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(bCryptPasswordEncoder());
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(11);
}
@Bean
public UserDetailsService userDetailsService() {
return new UserRepositoryUserDetailsService(userRepository);
}
@Bean
public SocialUserDetailsService socialUserDetailsService() {
return new UserRepositorySocialUserDetailsService(userDetailsService());
}
//////////////////////////////////////////////////////////////////////////
// REMEMBER ME //
//////////////////////////////////////////////////////////////////////////
@Bean
public JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(Boolean.valueOf(createTables));
return jdbcTokenRepository;
}
@Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
return new RememberMeAuthenticationProvider(secret);
}
@Bean
public PersistentTokenBasedRememberMeServices rememberMeService() {
PersistentTokenBasedRememberMeServices service =
new PersistentTokenBasedRememberMeServices(secret, userDetailsService(), jdbcTokenRepository());
service.setUseSecureCookie(true);
service.setParameter("rememberme");
service.setTokenValiditySeconds(AbstractRememberMeServices.TWO_WEEKS_S);
return service;
}
@Bean
public RememberMeAuthenticationFilter authenticationFilter() throws Exception {
return new RememberMeAuthenticationFilter(authenticationManager(), rememberMeService());
}
}
在我的 spring 引导配置中,目前与 thymeleaf 相关,并且用于开发目的
spring.thymeleaf.cache=false
thymeleaf 模板看起来像这样(我现在的登录页面,为了清楚起见,将只包含相关内容)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security/"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="thymeleaf/layouts/default">
<head>
... css and meta tags ...
</head>
<body>
... some html ...
<th:block sec:authorize="isAnonymous()">
<!-- Bad Credentials -->
<div th:if="${param.error}" class="alert alert-danger text-center">
Invalid username and/or password.
</div>
<!-- Logout -->
<div th:if="${param.logout}" class="alert alert-success text-center">
You have been logged out.
</div>
<!-- Login Form -->
<form id="f" th:action="@{/login}" method="post" role="form" autocomplete="off">
<!-- Username -->
<input type="text" class="form-control text-center" id="username" name="username" th:placeholder="#{form.login.username}" />
<!-- Password -->
<input type="password" class="form-control text-center" id="password" name="password" th:placeholder="#{form.login.password}" />
<!-- Remember me -->
<input type="checkbox" id="rememberme" name="rememberme" />
<!-- Submit -->
<button type="submit" class="btn btn-primary" th:utext="#{form.login.submit}">Login</button>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
... more html and javascript ...
</body>
</html>
Edit2 - 在按照 Faraj Farook 指出的方向进行了一些调试之后,我发现,在我 posted 配置的项目中,在 Spring 引导版本中,在此 class org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate
中,以下函数 returns 空处理器
public Map<String, String> getExtraHiddenFields(
final RequestContext requestContext, final HttpServletRequest request) {
final RequestDataValueProcessor processor = requestContext.getRequestDataValueProcessor();
if (processor == null) {
return null;
}
return processor.getExtraHiddenFields(request);
}
而非 Spring 引导版本,它 returns 处理器是 org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor
的一个实例。
根据 Thymeleaf
开发人员的说法,Thymeleaf 使用 RequestDataValueProcessor
接口来查找自动添加到表单 post 后面的额外隐藏字段。
下面 org/thymeleaf/spring3/processor/attr/SpringActionAttrProcessor.java
中的代码显示了这一点。
final Map<String,String> extraHiddenFields =
RequestDataValueProcessorUtils.getExtraHiddenFields(arguments.getConfiguration(), arguments);
整理问题,自动添加CSRF Token;在您的应用程序中创建一个自定义请求数据值处理器并将其注册到 spring。为此,您可以阅读下面的教程。
我还建议你检查你以前的 spring MVC 代码没有 spring 引导,以确认项目的配置 XML 有一个定制的 RequestDataValueProcessor
与否。
使用 Spring Boot + Thymeleaf + Spring Security 它与此一起工作:
应用程序属性
security.enable-csrf=true
2017 年 3 月 30 日更新:
一个重要的事情是:在你的表单中使用th:action,这将告诉Spring安全在表单中注入CSRF而不需要手动插入。
对于手动插入:
html 模板
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
2017 年 1 月 25 日更新:
pom.xml
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
我遇到了类似的问题。经过一番调查后,我发现只有使用 'th:action' 属性(不是普通 'action')的表单才注入 csrf 令牌。
对于登录表单,您似乎需要手动注入 csrf (link)。
在官方 spring 文档 (link) 中,建议在提交登录表单之前检索 csrf 令牌,以防止会话超时。在这种情况下,表单的隐藏输入中不会有 csrf 令牌。
你必须做两件事。声明一个 bean
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
... other beans ...
@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
}
确保 themeleaf 模板中的 html 表单使用 "th:action"
<form th:action="@{/youractionurl}">
... input tags
</form>
这会像这样自动插入 _csrf 令牌
<input type="hidden" name="_csrf" value="4568ad84-b300-48c4-9532-a9dcb58366f3" />
免责声明:我知道如何使用 thymeleaf 手动将令牌注入表单:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />`
这个post的目标是提高对平台的了解,更好地了解内部发生的事情Spring Boot
我还没有尝试过 Spring Boot,但最近我决定试一试,不得不承认它很棒,但是在 Spring MVC 上使用 Thymeleaf 和 Security,我没有不需要在表单上注入 CSRF 令牌 (POST),因为 Thymeleaf 会自动处理它,但现在由于某些原因在 Spring 启动时它不会。
从 Spring Boot Reference 中,我找到了 application.properties 文件中使用的常用属性列表,其中与 thymeleaf 和安全性相关的是:
Thymeleaf 特性
spring.thymeleaf.check-template-location=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.excluded-view-names= # comma-separated list of view names that should be excluded from resolution
spring.thymeleaf.view-names= # comma-separated list of view names that can be resolved
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.content-type=text/html # ;charset=<encoding> is added
spring.thymeleaf.cache=true # set to false for hot refresh
安全属性
security.user.name=user # login username
security.user.password= # login password
security.user.role=USER # role assigned to the user
security.require-ssl=false # advanced settings ...
security.enable-csrf=false
security.basic.enabled=true
security.basic.realm=Spring
security.basic.path= # /**
security.basic.authorize-mode= # ROLE, AUTHENTICATED, NONE
security.filter-order=0
security.headers.xss=false
security.headers.cache=false
security.headers.frame=false
security.headers.content-type=false
security.headers.hsts=all # none / domain / all
security.sessions=stateless # always / never / if_required / stateless
security.ignored= # Comma-separated list of paths to exclude from the default secured paths
但是如果有让 Thymeleaf 再次注入令牌的解决方案,我看不到它。
编辑:添加我的配置
该项目是使用上一个 STS 版本(在我看来很棒)中附带的初始化程序创建的,具有 Web、Thymeleaf、Security、JPA、MySQL、H2、Mail、Facebook、检查了 Twitter、LinkedIn 和 Actuator 项目,并在后面添加了一些额外内容
使用 Java 7 和 Tomcat 7 因为我打算在不久的将来在 Openshift 上部署该项目,接下来是我的配置文件:
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.3.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<start-class>com.adrisasws.springmvc.WebApplication</start-class>
<java.version>1.7</java.version>
<tomcat.version>7.0.59</tomcat.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>1.1.2.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</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-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-facebook</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-linkedin</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-social-twitter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.social</groupId>
<artifactId>spring-social-google</artifactId>
<version>1.0.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>openshift</id>
<build>
<finalName>webapp</finalName>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>2.1.1</version>
<configuration>
<outputDirectory>webapps</outputDirectory>
<warName>ROOT</warName>
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
安全配置(与我在非引导项目中使用的安全文件完全相同,其中 CSRF 令牌实际上是自动注入的)
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
//////////////////////////////////////////////////////////////////////////
// DEPENDENCIES //
//////////////////////////////////////////////////////////////////////////
@Autowired private DataSource dataSource;
@Autowired private UserRepository userRepository;
//////////////////////////////////////////////////////////////////////////
// PROPERTIES //
//////////////////////////////////////////////////////////////////////////
@Value("${custom.security.rememberme-secret}") private String secret;
@Value("${custom.security.rememberme-create-tables}") private String createTables;
private final static String[] adminRequests = new String[] { ... some matchers here... };
private final static String[] userRequests = new String[] { ... some matchers here... };
private final static String[] publicRequests = new String[] { ...some matchers here... };
//////////////////////////////////////////////////////////////////////////
// AUTHORIZATION //
//////////////////////////////////////////////////////////////////////////
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/css/**", "/images/**", "/js/**", "/error**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(adminRequests).access("hasRole('"+Role.ADMIN.toString()+"')")
.antMatchers(userRequests).access("hasRole('"+Role.USER.toString()+"')")
.antMatchers(publicRequests).permitAll()
.anyRequest().authenticated()
.and()
.requiresChannel()
.anyRequest().requiresSecure()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/", false)
.permitAll()
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.rememberMe()
.rememberMeServices(rememberMeService())
.and()
.apply(new SpringSocialConfigurer());
}
//////////////////////////////////////////////////////////////////////////
// AUTHENTICATION //
//////////////////////////////////////////////////////////////////////////
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.userDetailsService(userDetailsService())
.passwordEncoder(bCryptPasswordEncoder());
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(11);
}
@Bean
public UserDetailsService userDetailsService() {
return new UserRepositoryUserDetailsService(userRepository);
}
@Bean
public SocialUserDetailsService socialUserDetailsService() {
return new UserRepositorySocialUserDetailsService(userDetailsService());
}
//////////////////////////////////////////////////////////////////////////
// REMEMBER ME //
//////////////////////////////////////////////////////////////////////////
@Bean
public JdbcTokenRepositoryImpl jdbcTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
jdbcTokenRepository.setDataSource(dataSource);
jdbcTokenRepository.setCreateTableOnStartup(Boolean.valueOf(createTables));
return jdbcTokenRepository;
}
@Bean
public RememberMeAuthenticationProvider rememberMeAuthenticationProvider() {
return new RememberMeAuthenticationProvider(secret);
}
@Bean
public PersistentTokenBasedRememberMeServices rememberMeService() {
PersistentTokenBasedRememberMeServices service =
new PersistentTokenBasedRememberMeServices(secret, userDetailsService(), jdbcTokenRepository());
service.setUseSecureCookie(true);
service.setParameter("rememberme");
service.setTokenValiditySeconds(AbstractRememberMeServices.TWO_WEEKS_S);
return service;
}
@Bean
public RememberMeAuthenticationFilter authenticationFilter() throws Exception {
return new RememberMeAuthenticationFilter(authenticationManager(), rememberMeService());
}
}
在我的 spring 引导配置中,目前与 thymeleaf 相关,并且用于开发目的
spring.thymeleaf.cache=false
thymeleaf 模板看起来像这样(我现在的登录页面,为了清楚起见,将只包含相关内容)
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
xmlns:sec="http://www.thymeleaf.org/extras/spring-security/"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
layout:decorator="thymeleaf/layouts/default">
<head>
... css and meta tags ...
</head>
<body>
... some html ...
<th:block sec:authorize="isAnonymous()">
<!-- Bad Credentials -->
<div th:if="${param.error}" class="alert alert-danger text-center">
Invalid username and/or password.
</div>
<!-- Logout -->
<div th:if="${param.logout}" class="alert alert-success text-center">
You have been logged out.
</div>
<!-- Login Form -->
<form id="f" th:action="@{/login}" method="post" role="form" autocomplete="off">
<!-- Username -->
<input type="text" class="form-control text-center" id="username" name="username" th:placeholder="#{form.login.username}" />
<!-- Password -->
<input type="password" class="form-control text-center" id="password" name="password" th:placeholder="#{form.login.password}" />
<!-- Remember me -->
<input type="checkbox" id="rememberme" name="rememberme" />
<!-- Submit -->
<button type="submit" class="btn btn-primary" th:utext="#{form.login.submit}">Login</button>
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
</form>
... more html and javascript ...
</body>
</html>
Edit2 - 在按照 Faraj Farook 指出的方向进行了一些调试之后,我发现,在我 posted 配置的项目中,在 Spring 引导版本中,在此 class org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate
中,以下函数 returns 空处理器
public Map<String, String> getExtraHiddenFields(
final RequestContext requestContext, final HttpServletRequest request) {
final RequestDataValueProcessor processor = requestContext.getRequestDataValueProcessor();
if (processor == null) {
return null;
}
return processor.getExtraHiddenFields(request);
}
而非 Spring 引导版本,它 returns 处理器是 org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor
的一个实例。
根据 Thymeleaf
开发人员的说法,Thymeleaf 使用 RequestDataValueProcessor
接口来查找自动添加到表单 post 后面的额外隐藏字段。
下面 org/thymeleaf/spring3/processor/attr/SpringActionAttrProcessor.java
中的代码显示了这一点。
final Map<String,String> extraHiddenFields =
RequestDataValueProcessorUtils.getExtraHiddenFields(arguments.getConfiguration(), arguments);
整理问题,自动添加CSRF Token;在您的应用程序中创建一个自定义请求数据值处理器并将其注册到 spring。为此,您可以阅读下面的教程。
我还建议你检查你以前的 spring MVC 代码没有 spring 引导,以确认项目的配置 XML 有一个定制的 RequestDataValueProcessor
与否。
使用 Spring Boot + Thymeleaf + Spring Security 它与此一起工作:
应用程序属性
security.enable-csrf=true
2017 年 3 月 30 日更新:
一个重要的事情是:在你的表单中使用th:action,这将告诉Spring安全在表单中注入CSRF而不需要手动插入。
对于手动插入:
html 模板
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
2017 年 1 月 25 日更新:
pom.xml
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity4</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
我遇到了类似的问题。经过一番调查后,我发现只有使用 'th:action' 属性(不是普通 'action')的表单才注入 csrf 令牌。
对于登录表单,您似乎需要手动注入 csrf (link)。
在官方 spring 文档 (link) 中,建议在提交登录表单之前检索 csrf 令牌,以防止会话超时。在这种情况下,表单的隐藏输入中不会有 csrf 令牌。
你必须做两件事。声明一个 bean
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
... other beans ...
@Bean
public RequestDataValueProcessor requestDataValueProcessor() {
return new CsrfRequestDataValueProcessor();
}
}
确保 themeleaf 模板中的 html 表单使用 "th:action"
<form th:action="@{/youractionurl}">
... input tags
</form>
这会像这样自动插入 _csrf 令牌
<input type="hidden" name="_csrf" value="4568ad84-b300-48c4-9532-a9dcb58366f3" />