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.

Antworten auf die Frage(8)

Ihre Antwort auf die Frage