web-dev-qa-db-fra.com

Exemple simple de Spring Security avec Thymeleaf

salut j'essaie de suivre un exemple simple pour faire une page de formulaire de connexion simple que j'ai trouvée dans cette page http://docs.spring.io/autorepo/docs/spring-security/4.0.x/guides/form.html 

le problème est que je reçois cette erreur à chaque fois que j'essaie de me connecter et que je reçois cette erreur: Expected CSRF token not found. Has your session expired? 

Quand j'obtiens cette erreur, j'appuie sur le bouton retour dans mon explorateur et essaie une seconde fois de me connecter et quand je fais ça, j'obtiens cette erreur: HTTP 403 - Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'

dans la page du didacticiel se trouve le message suivant: We use Thymeleaf to automatically add the CSRF token to our form. If we were not using Thymleaf or Spring MVCs taglib we could also manually add the CSRF token using <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> 

"donc parce que j'utilise aussi thymeleaf je n'ai pas ajouté cette balise à ma page"

j'ai trouvé une autre solution et cela fonctionne et cette solution ajoute ceci à ma classe de configuration de sécurité .csrf().disable() cette solution fonctionne mais je suppose que ce que cela fait est de désactiver la protection de CSRF dans ma page et je ne veux pas désactiver ce type de protection. 

voici ma classe de configuration de sécurité:

@Configuration
@EnableWebSecurity
public class ConfigSecurity extends WebSecurityConfigurerAdapter {

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
            .inMemoryAuthentication()
                .withUser("user").password("password").roles("USER");
    }


    @Override
    protected void configure( HttpSecurity http ) throws Exception {
        http

        //.csrf().disable() is commented because i dont want disable this kind of protection 
        .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin()
                .loginPage("/login")
                .permitAll()
                .and()
            .logout()                                    
                .permitAll();
    }
}

mon initialzer de sécurité:

public class InitSecurity extends AbstractSecurityWebApplicationInitializer {

    public InicializarSecurity() {
        super(ConfigSecurity .class);

    }
}

ma classe app-config où j'ai ma configuration thymeleaf

@EnableWebMvc
@ComponentScan(basePackages = {"com.myApp.R10"})
@Configuration
public class ConfigApp extends WebMvcConfigurerAdapter{

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/css/**").addResourceLocations("/css/**");
        registry.addResourceHandler("/img/**").addResourceLocations("/img/**");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/**");
        registry.addResourceHandler("/sound/**").addResourceLocations("/sound/**");
        registry.addResourceHandler("/fonts/**").addResourceLocations("/fonts/**");
    }

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }

    @Bean
      public MessageSource messageSource() {
        ReloadableResourceBundleMessageSource messageSource = new       ReloadableResourceBundleMessageSource();
        messageSource.setBasenames("classpath:messages/messages");
        messageSource.setUseCodeAsDefaultMessage(true);
        messageSource.setDefaultEncoding("UTF-8");
        messageSource.setCacheSeconds(0);// # -1 : never reload, 0 always reload
        return messageSource;
    }
//  THYMELEAF

        @Bean 
        public ServletContextTemplateResolver templateResolver() {
            ServletContextTemplateResolver resolver = new ServletContextTemplateResolver();
            resolver.setPrefix("/WEB-INF/views/pagLogin/");
            resolver.setSuffix(".html");
            resolver.setTemplateMode("HTML5");
            resolver.setOrder(0);
            resolver.setCacheable(false);
            return resolver;
        }

        @Bean 
        public SpringTemplateEngine templateEngine() {
            SpringTemplateEngine engine  =  new SpringTemplateEngine();
            engine.setTemplateResolver( templateResolver() );
            engine.setMessageSource( messageSource() );



            return engine;
        }

        @Bean 
        public ThymeleafViewResolver thymeleafViewResolver() {
            ThymeleafViewResolver resolver  =  new ThymeleafViewResolver();

            resolver.setTemplateEngine( templateEngine() );
            resolver.setOrder(1);

            resolver.setCache( false );
            return resolver;
        }

        @Bean
        public SpringResourceTemplateResolver thymeleafSpringResource() {
            SpringResourceTemplateResolver Vista = new SpringResourceTemplateResolver();
            Vista.setTemplateMode("HTML5");
            return Vista;
        }
}

mon initialiseur app-config

public class InicializarApp extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }
@Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { ConfigApp .class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter() };
    }
}

ma classe de contrôleur de connexion

@Controller
public class ControllerLogin {



    @RequestMapping(value = "/login", method = RequestMethod.GET)
    public String pageLogin(Model model) {



         return "login";
    }

ma classe de contrôleur à la maison

@Controller
public class HomeController {

        @RequestMapping(value = "/", method = RequestMethod.GET)
        public String home(Model model) {


        return "home";
        }


}

mon login.html

<html xmlns:th="http://www.thymeleaf.org" xmlns:tiles="http://www.thymeleaf.org">
  <head>
    <title tiles:fragment="title">Messages : Create</title>
  </head>
  <body>
    <div tiles:fragment="content">
        <form name="f" th:action="@{/login}" method="post">               
            <fieldset>
                <legend>Please Login</legend>
                <div th:if="${param.error}" class="alert alert-error">    
                    Invalid username and password.
                </div>
                <div th:if="${param.logout}" class="alert alert-success"> 
                    You have been logged out.
                </div>

                <label for="username">Username</label>
                    <input type="text" id="username" name="username"/>        
                <label for="password">Password</label>
                    <input type="password" id="password" name="password"/>    

                <div class="form-actions">
                    <button type="submit" class="btn">Log in</button>
                </div>

                <!-- THIS IS COMMENTED it dont work beacuse i am already using thymeleaf <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>  -->


            </fieldset>
        </form>
    </div>


  </body>
</html>

ma page home.html ne s'affiche qu'après que je me suis connecté et que je ne puisse me connecter que si un put .csrf (). disable () dans ma classe de configuration de sécurité mais je ne veux pas désactiver cette protection, si je ne le mets pas Dans ma classe de configuration de sécurité, je reçois les erreurs que je mentionne au début de cette question.

15
stackUser2000

À partir de documentation Spring Security

La protection CSRF est activée par défaut avec la configuration Java. Si vous souhaite désactiver CSRF, la configuration Java correspondante peut être vu ci-dessous. Reportez-vous à la Javadoc de csrf () pour plus d'informations personnalisations dans la configuration de la protection CSRF.

Et quand la protection CSRF est activée

La dernière étape consiste à vous assurer que vous incluez le jeton CSRF à tous Méthodes PATCH, POST, PUT et DELETE.

Dans ton cas: 

  • vous avez activé la protection CSRF par défaut (parce que vous utilisez la configuration Java), 
  • vous soumettez le formulaire de connexion à l'aide d'un HTTP POST et 
  • n'incluent pas le jeton CSRF dans le formulaire de connexion. Pour cette raison, votre demande de connexion est refusée lors de la soumission car le filtre de protection CSRF ne peut pas trouver le jeton CSRF dans la demande entrante.

Vous avez déjà déterminé les solutions possibles:

  1. Désactiver la protection CSRF en tant que http.csrf().disable(); ou
  2. Incluez le jeton CSRF dans le formulaire de connexion en tant que paramètre masqué.

Puisque vous utilisez Thymeleaf, vous devrez procéder de la manière suivante dans votre modèle HTML pour la page de connexion:

<form name="f" th:action="@{/login}" method="post">               
  <fieldset>

    <input type="hidden" 
           th:name="${_csrf.parameterName}" 
           th:value="${_csrf.token}" />

    ...
  </fieldset>
</form>

Notez que vous devez utiliser th:action et non HTML action car le processeur CSRF de Thymeleaf n'entre en jeu qu'avec l'ancien.

Vous pouvez modifier la méthode de soumission du formulaire en GET uniquement pour résoudre le problème, mais cela n'est pas recommandé car les utilisateurs vont soumettre des informations sensibles dans le formulaire.

Je crée généralement un fragment Thymeleaf qui est ensuite utilisé dans toutes les pages avec des formulaires pour générer le balisage des formulaires avec le jeton CSRF inclus. Cela réduit le code passe-partout dans l'application.


Utilisez @EnableWebMvcSecurity au lieu de @EnableWebSecurity pour activer l'injection automatique de jeton CSRF avec des balises Thymeleaf. Utilisez également <form th:action> au lieu de <form action> avec Spring 3.2+ et Thymeleaf 2.1+ pour forcer Thymeleaf à inclure automatiquement le jeton CSRF en tant que champ masqué (source Spring JIRA ).

39
manish

Voici la solution qui l'implémente exactement comme l'OP voulait:

  1. Remplacez @EnableWebSecurity par @EnableWebMvcSecurity (c'est ce qui manque à OP)
  2. Utilisez th:action sur la balise <form>

Lorsque vous utilisez @EnableWebMvcSecurity, Spring Security enregistre la CsrfRequestDataValueProcessor et lorsque vous utilisez th:action, thymeleaf utilise sa méthode getExtraHiddenFields pour ajouter des champs cachés supplémentaires au formulaire. Et le CSRF est le champ caché supplémentaire.

Depuis Spring Security 4.0 , @EnableWebMvcSecurity est obsolète et seul @EnableWebSecurity est nécessaire. La protection _csrf continue de s'appliquer automatiquement .

13
name_no

Vous devez ajouter le dialecte de sécurité Spring de Thymleaf.

1.) Ajoutez le module Spring Security Dialect à votre chemin de classe.

Exemple Maven:

<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity3</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>

2.) Ajoutez l'objet SpringSecurityDialect à votre SpringTemplateEngine 

import org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect;
templateEngine.addDialect(new SpringSecurityDialect()); //add this line in your config

Source: Spring in Action 4ème édition

3
Hack of All Codes

Peut-être que cette petite quantité d’informations aide tout le monde: il est également obligatoire d’attribuer le formulaire avec th:action. Il ne suffit pas d'attribuer du HTML pur action et le fichier d'entrée CSRF masqué ne sera pas ajouté automatiquement. 

Impossible de trouver cette paix d'informations documentée nulle part et de passer 2h de recherche à ce sujet. J'avais attribué le formulaire avec action="#" et défini la valeur correspondante à l'aide d'un script Java. Le champ de saisie de jeton CSRF n'a pas été ajouté automatiquement jusqu'à ce que th:action="@{#}" soit ajouté au formulaire. Fonctionne comme un charme maintenant.

1
Mario Eis

N'a travaillé pour moi qu'après avoir ajouté ce qui suit:

protected void configure(HttpSecurity http) throws Exception {
    ...

    http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    ...
}
0
Alex Rewa