Reputation: 1695
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.
Upvotes: 17
Views: 36573
Reputation: 2901
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.
Upvotes: 1
Reputation: 318
Worked for me only after added the following:
protected void configure(HttpSecurity http) throws Exception {
...
http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
...
}
Upvotes: 0
Reputation: 354
Here is the solution that implements it exactly the way OP wanted:
@EnableWebSecurity
with @EnableWebMvcSecurity
(that's what OP is missing)th:action
on <form>
tagWhen 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.
Upvotes: 16
Reputation: 20135
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 already determined the possible solutions:
http.csrf().disable()
; orSince 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).
Upvotes: 42
Reputation: 31
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
Upvotes: 3