17

hi I'm trying to follow a simple example about doing a simple login form page that i found in this page http://docs.spring.io/autorepo/docs/spring-security/4.0.x/guides/form.html

the problem is that i´m getting this error everytime that i try to login i get this error: Expected CSRF token not found. Has your session expired?

When i get this error i press the back button in my explorer and try a second time to log in and when i do that i get this error: HTTP 403 - Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'

in the tutorial page is this message: 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}"/>

"so because i am using thymeleaf too i didnt add that tag to my page"

i found another solution and it works and this solution is adding this to my security config class .csrf().disable() this solution works but i suppose that what this do is to disable csrf protection in my page and i dont want to disable this type of protection.

this is my security-config class :

@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();
    }
}

my security initialzer :

public class InitSecurity extends AbstractSecurityWebApplicationInitializer {

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

    }
}

my app-config class where i have my thymeleaf configuration

@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;
        }
}

my app-config initializer

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() };
    }
}

my login controller class

@Controller
public class ControllerLogin {



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



         return "login";
    }

my home controller class

@Controller
public class HomeController {

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


        return "home";
        }


}

my 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>

my home.html page only shows after i log in and the only way i can log in if is a put .csrf().disable() in my Security config class but i dont want to disable that protection , if i dont put that in my security config class i get the errors that i mention at the start of this question.

manish
  • 19,695
  • 5
  • 67
  • 91
stackUser2000
  • 1,615
  • 11
  • 32
  • 55

5 Answers5

42

From the Spring Security documentation

CSRF protection is enabled by default with Java configuration. If you would like to disable CSRF, the corresponding Java configuration can be seen below. Refer to the Javadoc of csrf() for additional customizations in how CSRF protection is configured.

And, when CSRF protection is enabled

The last step is to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods.

In your case:

  • you have CSRF protection enabled by default (because you are using Java configuration),
  • you are submitting the login form using an HTTP POST and
  • are not including the CSRF token in the login form. For this reason, your login request is denied upon submission because the CSRF protection filter cannot find the CSRF token in the incoming request.

You have already determined the possible solutions:

  1. Disable CSRF protection as http.csrf().disable(); or
  2. Include the CSRF token in the login form as a hidden parameter.

Since you are using Thymeleaf, you will have to do something like the following in your HTML template for the login page:

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

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

    ...
  </fieldset>
</form>

Note that you must use th:action and not HTML action as the Thymeleaf CSRF processor will kick-in only with the former.

You could change the form submission method to GET just to get over the problem but that isn't recommended since the users are going to submit sensitive information in the form.

I typically create a Thymeleaf fragment that is then used in all pages with forms to generate the markup for the forms with the CSRF token included. This reduces boilerplate code across the app.


Using @EnableWebMvcSecurity instead of @EnableWebSecurity to enable automatic injection of CSRF token with Thymeleaf tags. Also use <form th:action> instead of <form action> with Spring 3.2+ and Thymeleaf 2.1+ to force Thymeleaf to include the CSRF token as a hidden field automatically (source Spring JIRA).

tharindu_DG
  • 8,900
  • 6
  • 52
  • 64
manish
  • 19,695
  • 5
  • 67
  • 91
  • thanks man it worked, i dont know why the tutorial says this: ""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 ""but y try what you say and it worked – stackUser2000 Sep 08 '14 at 14:08
  • What Spring, Spring Security and Thymeleaf versions are you using for your project? – manish Sep 08 '14 at 16:15
  • Spring version: 4.0.6.RELEASE ----- thymeleaf: 2.1.3.RELEASE ----- thymeleaf-spring4: 2.1.3.RELEASE ----- Spring secuirty web: 3.2.5.RELEASE ----- spring-security-config: 3.2.4.RELEASE – stackUser2000 Sep 08 '14 at 16:41
  • 11
    As per [this Spring Security JIRA](https://jira.springsource.org/browse/SEC-2436), you need to add `@EnableWebMvcSecurity`, which is not the same as `@EnableWebSecurity` to your Spring web application context initializer. After this, Thymeleaf 2.1+ should start injecting the CSRF token automatically with Spring Security 3.2+. – manish Sep 09 '14 at 01:38
  • 11
    Maybe that small peace of information helps anybody out: It is also mandatory to have the form attributed with th:action. Just attributing plain HTML action won't do and the hidden CSRF input filed won't be added automatically. Couldn't find that peace of information documented anywhere and spent 2h research on that. I had attributed the form with action="#" and set the corresponding value by java script. The CSRF token input field wasn't added automatically until added th:action="@{#}" to the form. Works as a charm now. – Mario Eis Oct 23 '14 at 12:21
  • 1
    Thank you Mario. I wondered and your comment made it worth my search! – user216661 Jan 28 '16 at 22:35
  • As @MarioEis said. I had to ensure the forms action was blank (or not added) and only use `th:action` before the token element was automatically inserted by Thymeleaf. – syncdk Mar 02 '16 at 02:08
  • @MarioEis I have to buy you a beer :) I specify in Spring Security config that registration page should not be authenticated, but I got ` Could not verify the provided CSRF token because your session was not found`. I had to add `th:action="@{/user/register}"` (before it sent POST to proper URL without any action) and I can intercept request properly. Thank you! – Radek Anuszewski Jun 28 '16 at 20:02
  • I'm confused, this [article](http://www.concretepage.com/spring-4/spring-4-security-thymeleaf-integration-custom-login-page-and-logout-example-with-csrf-token-using-javaconfig) say that `@EnableWebSecurity` alone is enough for automatic inclusion of csrf token in forms. Can you please clarify? – Lucky Jul 02 '16 at 14:28
  • ++ on th: tag prefix. Why isn't that even mentioned anywhere in the Spring documentation pages I've been scouring all day? – geneSummons Feb 02 '17 at 22:08
  • @geneSummons, this is [documented in the Spring Security documentation](https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-include-csrf-token) (`If you are using Spring MVC tag or Thymeleaf 2.1+`) and hinted at (in a very obscure way) in the [Thymeleaf 2.1 release notes](http://www.thymeleaf.org/whatsnew21.html#reqdata) (`th:action calls RequestDataValueProcessor.processAction(...)`). One would have to know that a call to `RequestDataValueProcessor.processAction(...)` includes the CSRF token as a hidden form field. – manish Feb 03 '17 at 04:17
  • This is the most important part: "Note that you must use th:action and not HTML action as the Thymeleaf CSRF processor will kick-in only with the former. " – Bruno Morais Mar 03 '17 at 18:25
  • I just wanted to let you know. My friend's uni course chapter on CSRF is just a link to this post. So you must explained it right! – Reinard Jun 16 '22 at 17:34
16

Here is the solution that implements it exactly the way OP wanted:

  1. Replace @EnableWebSecurity with @EnableWebMvcSecurity (that's what OP is missing)
  2. Use th:action on <form> tag

When you use @EnableWebMvcSecurity Spring Security registers the CsrfRequestDataValueProcessor, and when you use th:action thymeleaf uses it's getExtraHiddenFields method to add, well, extra hidden fields to the form. And the csrf is the extra hidden field.

Since Spring Security 4.0, @EnableWebMvcSecurity has been deprecated and only @EnableWebSecurity is necessary. The _csrf protection continues to apply automatically.

ben3000
  • 4,629
  • 6
  • 24
  • 43
name_no
  • 354
  • 2
  • 9
  • The is counter to what I am reading in the current spring documents:https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html which says:If you are using Spring MVC tag or Thymeleaf 2.1+ and are using @EnableWebSecurity, the CsrfToken is automatically included for you (using the CsrfRequestDataValueProcessor). – user216661 Jan 28 '16 at 22:39
  • And it should be an accepted answer, I lost 2 hours to find what is wrong with my form (lack of `th:action`) – Radek Anuszewski Jun 28 '16 at 20:04
3

You need to add Thymleaf's Spring Security Dialect.

1.) Add the Spring Security Dialect module to your classpath.

Maven Example:

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

2.) Add the SpringSecurityDialect object to your SpringTemplateEngine

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

Source: Spring in Action 4th Edition

1

Maybe that small piece of information helps anybody out: It is also mandatory to have the form attributed with th:action. Just attributing plain HTML action won't do and the hidden CSRF input filed won't be added automatically.

Couldn't find that piece of information documented anywhere and spent 2h research on that. I had attributed the form with action="#" and set the corresponding value by javascript. The CSRF token input field wasn't added automatically until added th:action="@{#}" to the form. Works like a charm now.

Nirro
  • 746
  • 4
  • 18
Mario Eis
  • 2,724
  • 31
  • 32
0

Worked for me only after added the following:

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

    http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    ...
}
Alex Rewa
  • 319
  • 4
  • 11