Thymeleaf的Spring Security的简单示例


问题内容

您好,我正在尝试按照一个简单的示例来做一个在此页面中找到的简单登录表单页面
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保护,并且我不想禁用这种类型的保护。

这是我的security-config类:

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

    }
}

我的应用程序配置类,其中有我的百里香叶配置

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

我的应用程序配置初始化程序

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


}

我的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页面仅在我登录后显示,并且我登录的唯一方法是在Security
config类中放置一个.csrf()。disable(),但是我不想禁用该保护,如果我不这样做的话在我的安全配置类中,我收到了在此问题开头提到的错误。


问题答案:

Spring Security文档

默认情况下,使用Java配置会启用CSRF保护。如果要禁用CSRF,则可以在下面看到相应的Java配置。有关如何配置CSRF保护的更多自定义信息,请参考csrf()的Javadoc。

并且,启用CSRF保护后

最后一步是确保在所有PATCH,POST,PUT和DELETE方法中都包含CSRF令牌。

在您的情况下:

  • 您默认情况下启用了CSRF保护(因为您使用的是Java配置),
  • 您正在使用HTTP POST提交登录表单,并且
  • 在登录表单中不包含CSRF令牌。因此,您的登录请求在提交时被拒绝,因为CSRF保护筛选器无法在传入请求中找到CSRF令牌。

您已经确定了可能的解决方案:

  1. 禁用CSRF保护为http.csrf().disable();要么
  2. 在登录表单中包含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而不是HTML, action 因为Thymeleaf
CSRF处理器只会在前者中起作用。

您可以将表单提交方法更改为GET仅解决问题,但是不建议这样做,因为用户将要在表单中提交敏感信息。

我通常创建一个Thymeleaf片段,然后在所有带有表单的页面中使用该片段,以生成包含CSRF令牌的表单标记。这减少了整个应用程序的样板代码。


使用@EnableWebMvcSecurity代替@EnableWebSecurity启用带有Thymeleaf标签的CSRF令牌自动注入。还可以<form th:action>代替<form action>Spring 3.2+和Thymeleaf
2.1+使用,以强制Thymeleaf自动将CSRF令牌包括为隐藏字段(来源Spring
JIRA
)。