Простой пример 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:

  • Замените @EnableWebSecurity на @EnableWebMvcSecurity (что отсутствует OP)
  • Используйте th:action в теге <form>

При использовании @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="@{#}" к форме. Сейчас работает как прелесть.