Простой пример Spring Безопасность с Thymeleaf
hi Я пытаюсь выполнить простой пример о простой странице формы входа, которую я нашел на этой странице http://docs.spring.io/autorepo/docs/spring-security/4.0.x/guides/form.html
проблема в том, что я получаю эту ошибку каждый раз, когда я пытаюсь войти в систему, я получаю эту ошибку: Expected CSRF token not found. Has your session expired?
Когда я получу эту ошибку, я нажму кнопку "Назад" в своем проводнике и попробую второй раз войти в систему, и когда я сделаю это, я получу эту ошибку: HTTP 403 - Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'
на странице руководства это сообщение: 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}"/>
", потому что я использую тимелеаф тоже, я не добавил этот тег на свою страницу"
Я нашел другое решение, и оно работает, и это решение добавляет это в мой класс конфигурации безопасности .csrf().disable()
, это решение работает, но я полагаю, что это значит отключить защиту csrf на моей странице, и я не хочу отключать этот тип защиты.
это мой класс безопасности-конфигурации:
@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();
}
}
мой инициализатор безопасности:
public class InitSecurity extends AbstractSecurityWebApplicationInitializer {
public InicializarSecurity() {
super(ConfigSecurity .class);
}
}
мой класс app-config, где у меня есть конфигурация тимелеафа
@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;
}
}
мой инициализатор 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() };
}
}
мой класс контроллера входа
@Controller
public class ControllerLogin {
@RequestMapping(value = "/login", method = RequestMethod.GET)
public String pageLogin(Model model) {
return "login";
}
класс моего домашнего контроллера
@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>
Моя страница home.html отображается только после входа в систему и единственным способом, с помощью которого я могу войти, если это put.csrf(). disable() в моем классе конфигурации безопасности, но я не хочу отключать эту защиту, если я не помещайте это в мой класс конфигурации безопасности, я получаю ошибки, которые я упоминаю в начале этого вопроса.
Ответы
Ответ 1
В Spring документации по безопасности
Защита CSRF по умолчанию включена с конфигурацией Java. если ты хотел бы отключить CSRF, соответствующая конфигурация Java может см. ниже. Обратитесь к Javadoc csrf() за дополнительным настройки в настройке защиты CSRF.
И, когда включена защита CSRF
Последний шаг - убедиться, что вы включили токен CSRF во всех PATCH, POST, PUT и DELETE.
В вашем случае:
- У вас включена защита CSRF по умолчанию (потому что вы используете конфигурацию Java),
- вы отправляете форму входа в систему с помощью HTTP POST и
- не включают токен CSRF в форме входа. По этой причине ваш запрос на вход отклоняется при отправке, потому что фильтр защиты CSRF не может найти токен CSRF во входящем запросе.
Вы уже определили возможные решения:
- Отключить защиту CSRF как
http.csrf().disable()
; или
- Включить токен CSRF в форме входа в систему как скрытый параметр.
Поскольку вы используете Thymeleaf, вам нужно будет сделать что-то вроде следующего в вашем шаблоне HTML для страницы входа:
<form name="f" th:action="@{/login}" method="post">
<fieldset>
<input type="hidden"
th:name="${_csrf.parameterName}"
th:value="${_csrf.token}" />
...
</fieldset>
</form>
Обратите внимание, что вы должны использовать th:action
, а не HTML action
, поскольку процессор CSRF Thymeleaf будет работать только с первым.
Вы можете изменить способ отправки формы на GET
, чтобы преодолеть эту проблему, но это не рекомендуется, так как пользователи собираются отправлять конфиденциальную информацию в форму.
Я обычно создаю фрагмент Thymeleaf, который затем используется на всех страницах с формами для создания разметки для форм с включенным токеном CSRF. Это уменьшает код шаблона в приложении.
Использование @EnableWebMvcSecurity
вместо @EnableWebSecurity
, чтобы включить автоматическую вставку токена CSRF с тегами Thymeleaf. Также используйте <form th:action>
вместо <form action>
с Spring 3.2+ и Thymeleaf 2.1+, чтобы заставить Thymeleaf автоматически включать маркер CSRF в качестве скрытого поля (источник Spring JIRA).
Ответ 2
Вот решение, которое реализует его именно так, как хотел OP:
При использовании @EnableWebMvcSecurity
Spring Security регистрирует CsrfRequestDataValueProcessor
, а при использовании th:action
thymeleaf использует метод getExtraHiddenFields
для добавления, ну, дополнительных скрытых полей в форму. И csrf является дополнительным скрытым полем.
Поскольку Spring Безопасность 4.0, @EnableWebMvcSecurity устарела и требуется только @EnableWebSecurity. Защита _csrf продолжает применяться автоматически.
Ответ 3
Вам нужно добавить Thymleaf Spring Security Dialect.
1.) Добавьте модуль диалога Spring Security Dialect в свой путь к классам.
Пример Maven:
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity3</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
2.) Добавьте объект SpringSecurityDialect в свой SpringTemplateEngine
import org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect;
templateEngine.addDialect(new SpringSecurityDialect()); //add this line in your config
Источник: Spring в Action 4th Edition
Ответ 4
Может быть, этот маленький мир информации помогает кому-либо: также обязательно иметь форму, приписываемую th:action
. Просто приписывание простого HTML action
не будет выполнено, и скрытый вход CSRF не будет добавлен автоматически.
Не удалось найти, что мир информации документирован в любом месте и провел 2 часа исследований по этому вопросу. Я приписал форму action="#"
и установил соответствующее значение java script. Поле ввода токена CSRF не добавляется автоматически до добавления th:action="@{#}"
к форме. Сейчас работает как прелесть.