stackUser2000 stackUser2000 - 5 months ago 175
Java Question

Simple example of Spring Security with Thymeleaf

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.

Answer

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).