免责声明 :我知道如何以这种方式手动将标记与百里香叶一起注入:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />`
这篇文章的目的是增进对平台的了解,并更好地了解Spring Boot内部的情况。
我没有尝试过Spring Boot,但最近我决定尝试一下,不得不承认它很棒,但是有了Spring MVC上的Thymeleaf和Security,我不需要在表单(POST)上注入CSRF令牌,因为Thymeleaf会自动处理它,但是由于某些原因,现在在Spring Boot中却没有。
从Spring Boot Reference中,我找到了application.properties文件上使用的常用属性的列表,与百里香和安全性有关的是:
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靴子配置中,并用于开发目的
spring.thymeleaf.cache=false
和百里香叶模板如下所示(此刻我的登录页面为清楚起见仅包含相关内容)
<!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,以下函数返回空处理器
org.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate
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。
org.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor
根据Thymeleaf开发人员的说法,RequestDataValueProcessorThymeleaf使用接口来查找多余的隐藏字段,这些字段会自动添加到回发的表单中。
Thymeleaf
RequestDataValueProcessor
下面的代码org/thymeleaf/spring3/processor/attr/SpringActionAttrProcessor.java显示了这一点。
org/thymeleaf/spring3/processor/attr/SpringActionAttrProcessor.java
final Map<String,String> extraHiddenFields = RequestDataValueProcessorUtils.getExtraHiddenFields(arguments.getConfiguration(), arguments);
要对问题进行排序,并自动添加CSRF令牌;在您的应用程序中,创建一个自定义请求数据值处理器,并在spring中注册它。为此,您可以阅读下面的教程。
Spring-MVC中的Csrf防御
我还建议您在不使用Spring Boot的情况下检查以前的Spring MVC代码,以确认项目的配置XML是否自定义RequestDataValueProcessor。