Spring Boot + Security + Thymeleaf- und CSRF-Token werden nicht automatisch injiziert
Haftungsausschlus: Ich weiß, wie ich das Token in einem Formular mit Thymeleaf manuell einspritzen kann:
<input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />`
Das Ziel dieses Beitrags ist es, das Wissen über die Plattform zu verbessern und ein besseres Verständnis für die Vorgänge in Spring Boot zu erlangen.
Ich habe Spring Boot noch nicht ausprobiert, aber kürzlich habe ich mich dazu entschlossen, es zu versuchen und muss zugeben, dass es großartig ist, aber mit Thymeleaf und Security auf Spring MVC musste ich kein CSRF-Token auf Formulare (POST) injizieren. , weil Thymeleaf sich automatisch darum gekümmert hat, aber jetzt in Spring Boot aus irgendeinem Grund nicht.
Von demSpring Boot Reference, fand ich eine Liste der allgemeinen Eigenschaften für application.properties-Datei, und diejenigen im Zusammenhang mit Thymeleaf und Sicherheit sind:
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
Sicherheitseigenschaften
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
Aber wenn die Lösung für die erneute Injektion des Tokens durch Thymeleaf da ist, sehe ich es nicht.
Bearbeite: meine Konfiguration hinzufügen
Das Projekt wurde mit dem Initialisierer erstellt, der in der letzten STS-Version ausgeliefert wurde (was meiner Meinung nach fantastisch ist). Dabei wurden Web-, Thymeleaf-, Sicherheits-, JPA-, MySQL-, H2-, Mail-, Facebook-, Twitter-, LinkedIn- und Actuator-Elemente überprüft. und einige Extras nachträglich hinzugefügt
Verwende Java 7 und Tomcat 7, da ich beabsichtige, das Projekt in naher Zukunft auf Openshift bereitzustellen. Als Nächstes sind meine Konfigurationsdateien verfügbar:
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>
Security config (genau dieselbe Sicherheitsdatei, die ich in einem Nicht-Boot-Projekt verwende, in das das CSRF-Token automatisch eingefügt wird)
@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 im Moment in Bezug auf Thymeleaf und für Entwicklungszwecke
spring.thymeleaf.cache=false
und Thymeleaf-Vorlagen sehen so aus (meine Anmeldeseite enthält zur besseren Übersicht derzeit nur den relevanten Inhalt)
<!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 - nach einigem Debuggen in die RichtungFaraj Farook wies darauf hin, fand ich heraus, dass in einem Projekt mit der Konfiguration, die ich in der Spring Boot-Version gepostet habe, in dieser Klasseorg.thymeleaf.spring4.requestdata.RequestDataValueProcessor4Delegate
gibt die folgende Funktion einen Nullprozessor zurück
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);
}
ei der Nicht-Spring-Boot-Version wird ein Prozessor zurückgegeben, der eine Instanz von @ isorg.springframework.security.web.servlet.support.csrf.CsrfRequestDataValueProcessor
.