Spring Boot + Security + Thymeleaf和CSRF令牌不会自动注入 [英] Spring Boot + Security + Thymeleaf and CSRF token not injected automatically
问题描述
免责声明:我知道如何通过以下方式手动将标记以百里香叶形式注入:
Disclaimer: I know how to inject the token in a form with thymeleaf manually with this:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />`
这篇文章的目的是提高平台的知识,并更好地了解Spring Boot的内部情况
The goal of this post is to improve the knowledge of the platform and get a better understanding of what's going on inside Spring Boot
我还没有尝试过Spring Boot,但是最近我决定尝试一下,不得不承认它的出色,但是有了Thymeleaf和Spring MVC上的Security,我不需要注入CSRF表单上的令牌(POST),因为Thymeleaf会自动处理它,但是现在由于某些原因它不在Spring Boot中。
I haven't tried Spring Boot, but recently i just decided to give it a try, and have to admit its awesome, but with Thymeleaf and Security on Spring MVC, i didn't need to inject CSRF token on forms (POST), because Thymeleaf took care of it automatically, but now in Spring Boot for some reason it doesn't.
来自 Spring Boot参考,我发现了一个application.properties文件上使用的常用属性以及相关属性的列表到thymeleaf和安全性方面:
From the Spring Boot Reference, i found a list of the common properties used on application.properties file, and the ones related to thymeleaf and security are:
Thymeleaf属性
Thymeleaf Properties
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
但是如果解决方案n使Thymeleaf再次注入令牌,我看不到它。
But if the solution to make Thymeleaf inject the token again is there, i fail to see it.
编辑:添加我的配置
该项目是使用以下初始化程序创建的已在最新的STS版本(我认为很棒)中发货,并检查了Web,Thymeleaf,安全性,JPA,MySQL,H2,邮件,Facebook,Twitter,LinkedIn和Actuator项,并在船尾添加了一些额外功能
The project was created using the initializer that was shipped in the last STS version (which in my opinion is awesome), with Web, Thymeleaf, Security, JPA, MySQL, H2, Mail, Facebook, Twitter, LinkedIn and Actuator items checked, and added some extras aftwerwards
使用Java 7和Tomcat 7是因为我打算在不久的将来在Openshift上部署该项目,接下来还有我的配置文件:
Using Java 7 and Tomcat 7 because i intend to deploy the project on Openshift in a near future, and next there are my config files:
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令牌实际上会自动注入)
Security config (exactly the same security file i'm using in a non-boot project in which the CSRF token actually gets injected automatically)
@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());
}
}
在与百里香有关的当下我的春季靴子配置中,并出于开发目的
in my spring boot configt at the moment related to thymeleaf, and for development purposes
spring.thymeleaf.cache=false
和百里香叶模板如下(我的登录页面此刻,为清楚起见仅包含相关内容)
and thymeleaf templates look like this (my login page at the moment, will include only the relevant content for the sake of clarity)
<!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 指出,在具有我发布的配置的项目中,我在Spring Boot版本中发现该类 org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate
,以下函数返回空处理器
Edit2 - after doing some debugging in the direction Faraj Farook pointed, i found out that, in a project with the configuration i posted, in the Spring Boot version, in this class org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate
, the following function returns a null processor
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引导版本,它返回一个处理器是 org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor
的实例。
whereas the non Spring boot version, it returns a processor which is an instance of org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor
.
推荐答案
根据 Thymeleaf
开发人员,Thymeleaf使用 RequestDataValueProcessor
接口查找多余的内容。
According to the Thymeleaf
developers, RequestDataValueProcessor
interface is used by Thymeleaf to find the extra hidden fields which is automatically added to the form post back.
org / thymeleaf / spring3 / processor / attr / SpringActionAttrProcessor.java中的以下代码
The below code in org/thymeleaf/spring3/processor/attr/SpringActionAttrProcessor.java
shows this.
final Map<String,String> extraHiddenFields =
RequestDataValueProcessorUtils.getExtraHiddenFields(arguments.getConfiguration(), arguments);
要对问题进行排序,并自动添加CSRF令牌;在您的应用程序中,创建一个自定义请求数据值处理器,并在spring中注册它。为此,您可以阅读下面的教程。
To sort the issue, and automatically add the CSRF Token; In your application create a custom request data value processor and register it with spring. To do this, you may go through the tutorial below.
我还建议您在没有Spring Boot的情况下检查以前的Spring MVC代码,以确认该项目的配置XML是否已自定义 RequestDataValueProcessor
。
这篇关于Spring Boot + Security + Thymeleaf和CSRF令牌不会自动注入的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!