【问题标题】:Simple example of Spring Security with Thymeleaf带有 Thymeleaf 的 Spring Security 的简单示例
【发布时间】:2014-10-30 19:29:16
【问题描述】:

您好,我正在尝试按照一个简单的示例来做一个简单的登录表单页面,我在这个页面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);

    }
}

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

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


}

我的登录.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 页面仅在我登录后显示,我可以登录的唯一方法是在我的安全配置类中放置 .csrf().disable() 但我不想禁用该保护,如果我不要把它放在我的安全配置类中,我会收到我在这个问题开头提到的错误。

【问题讨论】:

    标签: java security spring-mvc spring-security thymeleaf


    【解决方案1】:

    来自Spring Security documentation

    默认情况下,Java 配置启用 CSRF 保护。如果你 想禁用CSRF,对应Java配置即可 见下文。有关其他信息,请参阅 csrf() 的 Javadoc 自定义 CSRF 保护的配置方式。

    并且,当启用 CSRF 保护时

    最后一步是确保您将 CSRF 令牌包含在所有 PATCH、POST、PUT 和 DELETE 方法。

    在你的情况下:

    • 您默认启用了 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 action,因为 Thymeleaf CSRF 处理器只能与前者一起使用。

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

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


    使用 @EnableWebMvcSecurity 而不是 @EnableWebSecurity 来启用带有 Thymeleaf 标签的 CSRF 令牌的自动注入。还可以在 Spring 3.2+ 和 Thymeleaf 2.1+ 中使用 &lt;form th:action&gt; 而不是 &lt;form action&gt; 来强制 Thymeleaf 自动将 CSRF 令牌包含为隐藏字段(来源 Spring JIRA)。

    【讨论】:

    • 感谢它的工作,我不知道为什么教程这样说:“”我们使用 Thymeleaf 自动将 CSRF 令牌添加到我们的表单中。如果我们不使用 Thymleaf 或 Spring MVCs taglib,我们也可以使用 "" 手动添加 CSRF 令牌但是你试试你说的,它奏效了
    • 您的项目使用哪些 Spring、Spring Security 和 Thymeleaf 版本?
    • 根据this Spring Security JIRA,您需要将@EnableWebMvcSecurity 添加到您的Spring Web 应用程序上下文初始化器中,这与@EnableWebSecurity 不同。在此之后,Thymeleaf 2.1+ 应该开始使用 Spring Security 3.2+ 自动注入 CSRF 令牌。
    • 也许这些小小的信息可以帮助任何人:必须使用 th:action 赋予表单属性。仅仅归因于普通的 HTML 动作是行不通的,隐藏的 CSRF 输入文件也不会自动添加。找不到任何地方记录的信息和平,并为此花费了 2 小时的研究。我用 action="#" 为表单赋予了属性,并通过 java 脚本设置了相应的值。在将 th:action="@{#}" 添加到表单之前,不会自动添加 CSRF 令牌输入字段。现在可以作为魅力使用。
    • 谢谢马里奥。我想知道,您的评论值得我搜索!
    【解决方案2】:

    也许那条小信息可以帮助任何人:还必须将表单归因于th:action。仅仅归因于纯 HTML action 是不行的,隐藏的 CSRF 输入文件也不会自动添加。

    无法在任何地方找到记录在案的那条信息,为此花了 2 小时研究。我已经用action="#" 为表单赋予了属性,并通过javascript 设置了相应的值。在将th:action="@{#}" 添加到表单之前,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 in Action 4th Edition

      【讨论】:

        【解决方案4】:

        这是完全按照 OP 想要的方式实现它的解决方案:

        1. @EnableWebSecurity 替换为@EnableWebMvcSecurity(这就是缺少的OP)
        2. &lt;form&gt; 标签上使用th:action

        当你使用@EnableWebMvcSecurity Spring Security 注册CsrfRequestDataValueProcessor,当你使用th:action thymeleaf 使用它的getExtraHiddenFields 方法来添加额外的隐藏字段到表单。而csrf是额外的隐藏字段。

        Since Spring Security 4.0, @EnableWebMvcSecurity 已被弃用,只有 @EnableWebSecurity 是必需的。 The _csrf protection continues to apply automatically.

        【讨论】:

        • 这与我在当前 Spring 文档中阅读的内容相反:docs.spring.io/spring-security/site/docs/current/reference/html/… 说:如果您使用 Spring MVC
          标签或 Thymeleaf 2.1+ 并且正在使用 @EnableWebSecurity, CsrfToken 会自动为您包含在内(使用 CsrfRequestDataValueProcessor)。
        • 这应该是一个可以接受的答案,我花了 2 个小时才发现我的表格出了什么问题(缺少th:action
        【解决方案5】:

        仅在添加以下内容后才为我工作:

        protected void configure(HttpSecurity http) throws Exception {
            ...
        
            http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
            ...
        }
        

        【讨论】:

          猜你喜欢
          • 2017-02-09
          • 2013-01-09
          • 2017-11-21
          • 2012-03-13
          • 2014-08-19
          • 2015-10-21
          • 2020-05-11
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多