简体   繁体   English

带有 Thymeleaf 的 Spring Security 的简单示例

[英]Simple example of Spring Security with Thymeleaf

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嗨,我正在尝试遵循一个简单的示例,即我在此页面中找到的一个简单的登录表单页面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?问题是我每次尝试登录时都会收到此错误我收到此错误: Expected CSRF token not found. Has your session expired? 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'当我收到此错误时,我按资源管理器中的后退按钮并尝试第二次登录,当我这样做时,我收到此错误: 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}"/>在教程页面中有这样一条消息: 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}"/> 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.我找到了另一个解决方案,它有效,这个解决方案将它添加到我的安全配置类.csrf().disable()这个解决方案有效,但我想这样做是为了在我的页面中禁用 csrf 保护,我不想禁用这种类型的保护。

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我的 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;
        }
}

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

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

From the Spring Security documentation来自Spring Security 文档

CSRF protection is enabled by default with Java configuration.使用 Java 配置默认启用 CSRF 保护。 If you would like to disable CSRF, the corresponding Java configuration can be seen below.如果您想禁用 CSRF,可以在下面看到相应的 Java 配置。 Refer to the Javadoc of csrf() for additional customizations in how CSRF protection is configured.有关如何配置 CSRF 保护的其他自定义,请参阅 csrf() 的 Javadoc。

And, when CSRF protection is enabled并且,当启用 CSRF 保护时

The last step is to ensure that you include the CSRF token in all PATCH, POST, PUT, and DELETE methods.最后一步是确保在所有 PATCH、POST、PUT 和 DELETE 方法中包含 CSRF 令牌。

In your case:在你的情况下:

  • you have CSRF protection enabled by default (because you are using Java configuration),您默认启用了 CSRF 保护(因为您使用的是 Java 配置),
  • you are submitting the login form using an HTTP POST and您正在使用 HTTP POST 提交登录表单,并且
  • are not including the CSRF token in the login form.不包括登录表单中的 CSRF 令牌。 For this reason, your login request is denied upon submission because the CSRF protection filter cannot find the CSRF token in the incoming request.因此,您的登录请求在提交时被拒绝,因为 CSRF 保护过滤器无法在传入请求中找到 CSRF 令牌。

You have already determined the possible solutions:您已经确定了可能的解决方案:

  1. Disable CSRF protection as http.csrf().disable() ;禁用 CSRF 保护为http.csrf().disable() ; or或者
  2. Include the CSRF token in the login form as a hidden parameter.在登录表单中包含 CSRF 令牌作为隐藏参数。

Since you are using Thymeleaf, you will have to do something like the following in your HTML template for the login page:由于您使用的是 Thymeleaf,因此您必须在登录页面的 HTML 模板中执行以下操作:

<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.请注意,您必须使用th:action而不是 HTML action因为 Thymeleaf CSRF 处理器只会与前者一起启动。

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

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.我通常会创建一个 Thymeleaf 片段,然后在所有带有表单的页面中使用该片段为包含 CSRF 令牌的表单生成标记。 This reduces boilerplate code across the app.这减少了整个应用程序的样板代码。


Using @EnableWebMvcSecurity instead of @EnableWebSecurity to enable automatic injection of CSRF token with Thymeleaf tags.使用@EnableWebMvcSecurity而不是@EnableWebSecurity来启用带有 Thymeleaf 标签的 CSRF 令牌的自动注入。 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 ).还使用<form th:action>而不是<form action>与 Spring 3.2+ 和 Thymeleaf 2.1+ 强制 Thymeleaf 自动将 CSRF 标记作为隐藏字段包含在内(来源Spring JIRA )。

Here is the solution that implements it exactly the way OP wanted:这是完全按照OP想要的方式实现它的解决方案:

  1. Replace @EnableWebSecurity with @EnableWebMvcSecurity (that's what OP is missing)更换@EnableWebSecurity@EnableWebMvcSecurity (这是缺什么OP)
  2. Use th:action on <form> tag<form>标签上使用th:action

When 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.当您使用@EnableWebMvcSecurity Spring Security 注册CsrfRequestDataValueProcessor ,当您使用th:action thymeleaf 时,它使用getExtraHiddenFields方法向getExtraHiddenFields添加额外的隐藏字段。 And the csrf is the extra hidden field.而 csrf 是额外的隐藏字段。

Since Spring Security 4.0 , @EnableWebMvcSecurity has been deprecated and only @EnableWebSecurity is necessary. 从 Spring Security 4.0 开始,@EnableWebMvcSecurity 已被弃用,只需要 @EnableWebSecurity。 The _csrf protection continues to apply automatically . _csrf 保护继续自动应用

You need to add Thymleaf's Spring Security Dialect.您需要添加 Thymleaf 的 Spring Security 方言。

1.) Add the Spring Security Dialect module to your classpath. 1.) 将 Spring Security Dialect 模块添加到您的类路径中。

Maven Example: Maven 示例:

<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 2.) 将 SpringSecurityDialect 对象添加到您的 SpringTemplateEngine

import org.thymeleaf.extras.springsecurity3.dialect.SpringSecurityDialect;
templateEngine.addDialect(new SpringSecurityDialect()); //add this line in your config

Source: Spring in Action 4th Edition来源: Spring in Action 第 4 版

Maybe that small piece of information helps anybody out: It is also mandatory to have the form attributed with th:action .也许那一小段信息可以帮助任何人:将表单归因于th:action也是强制性的。 Just attributing plain HTML action won't do and the hidden CSRF input filed won't be added automatically.仅仅归因于纯 HTML action行不通的,并且不会自动添加隐藏的 CSRF 输入文件。

Couldn't find that piece of information documented anywhere and spent 2h research on that.找不到在任何地方记录的那条信息,并花了 2 小时研究它。 I had attributed the form with action="#" and set the corresponding value by javascript.我已将表单归因于action="#"并通过 javascript 设置相应的值。 The CSRF token input field wasn't added automatically until added th:action="@{#}" to the form.在将th:action="@{#}"到表单之前,CSRF 令牌输入字段不会自动添加。 Works like a charm now.现在就像一个魅力。

Worked for me only after added the following:仅在添加以下内容后才为我工作:

protected void configure(HttpSecurity http) throws Exception {
    ...

    http.csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
    ...
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM