如何将 csrf 令牌添加到 html 表单?

Posted

技术标签:

【中文标题】如何将 csrf 令牌添加到 html 表单?【英文标题】:How to add csrf token to the html form? 【发布时间】:2018-10-08 12:08:43 【问题描述】:

在我的应用程序中启用 csrf(removed line .csrf().disable()) 后,我的登录请求停止工作 - /login POST 将我重定向到主页:

请求如下所示:

在页面上我有以下 js:

<script type="text/javascript" src="/webjars/js-cookie/js.cookie.js"></script>
...
$('#login-form').submit(function (ev) 
    ev.preventDefault(); // to stop the form from submitting
    var headerName = "X-CSRF-TOKEN";
    var token = Cookies.get('XSRF-TOKEN')
    $('<input>').attr('type', 'hidden').attr('name', '_csrf').attr('value', Cookies.get('XSRF-TOKEN')).appendTo('#login-form');
    this.submit(); // If all the validations succeeded
    )

你能澄清什么做错了吗?

附言

我阅读了很多相关的东西,到处都发现了这样的行:

<meta name="_csrf" content="$_csrf.token"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="$_csrf.headerName"/>

我不明白我应该如何设置值_csrf.headerName

我尝试将这些数据添加到元位,但无法解析:

我尝试将其直接添加到表单中:

又没有成功。

另外我试过这个:

但正如您所见 - 它也无法解析我的属性

附言2

配置:

EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter 

    private static final String SECURE_ADMIN_PASSWORD = "rockandroll";

    @Override
    protected void configure(HttpSecurity http) throws Exception 
        http
                .formLogin()
                .loginPage("/index.html")
                    .loginProcessingUrl("/login")
                    .defaultSuccessUrl("/sender.html")
                    .permitAll()
                .and()
                .logout()
                    .logoutSuccessUrl("/index.html")
                    .permitAll()
                .and()
                .authorizeRequests()
                .antMatchers("/js/**", "/lib/**", "/images/**", "/css/**", "/index.html", "/","/*.css","/webjars/**", "/*.js").permitAll()
                .antMatchers("/websocket").hasRole("ADMIN")
                .requestMatchers(EndpointRequest.toAnyEndpoint()).hasRole("ADMIN")
                .anyRequest().authenticated();

    

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception 

        auth.authenticationProvider(new AuthenticationProvider() 

            @Override
            public boolean supports(Class<?> authentication) 
                return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
            

            @Override
            public Authentication authenticate(Authentication authentication) throws AuthenticationException 
                UsernamePasswordAuthenticationToken token = (UsernamePasswordAuthenticationToken) authentication;

                List<GrantedAuthority> authorities = SECURE_ADMIN_PASSWORD.equals(token.getCredentials()) ?
                        AuthorityUtils.createAuthorityList("ROLE_ADMIN") : null;

                return new UsernamePasswordAuthenticationToken(token.getName(), token.getCredentials(), authorities);
            
        );
    

build.gradle:

buildscript 
    repositories 
        mavenCentral()
    
    dependencies 
        classpath("org.springframework.boot:spring-boot-gradle-plugin:2.0.1.RELEASE")
    


apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

jar 
    baseName = 'gs-messaging-stomp-websocket'
    version = '0.1.0'

sourceCompatibility = 1.8
targetCompatibility = 1.8

repositories 
    mavenCentral()


dependencies 
    compile("org.springframework.boot:spring-boot-starter-websocket")
    compile("org.webjars:webjars-locator-core")
    compile("org.webjars:sockjs-client:1.0.2")
    compile("org.webjars:stomp-websocket:2.3.3")
    compile("org.webjars:bootstrap:3.3.7")
    compile("org.webjars:jquery:3.1.0")
    compile("org.webjars:js-cookie:2.1.0")

    compile("org.springframework.boot:spring-boot-starter-actuator")
    //security
    compile ('org.springframework.security:spring-security-messaging')
    compile group: 'org.springframework.security', name: 'spring-security-web'
    compile group: 'org.springframework.security', name: 'spring-security-config'

    testCompile("org.springframework.boot:spring-boot-starter-test")

【问题讨论】:

好的,我可以看到你添加了更多信息。我想请您添加更多详细信息,说明您遇到的错误(或您期望的行为,但它不起作用)。 你使用服务器模板语言JSP还是Thymeleaf? @holmis83 我使用原始 html @john 结​​果相同)。 /login 响应 302 而不是成功登录。如果我明确禁用 csrf (.csrf().disable()),则登录成功 【参考方案1】:

我尝试了不同的方法,最终我找到了解决方案:

之前index.html 位于文件夹/resources/static

我把它放在文件夹/resources/templates。我还添加了依赖项:

compile('org.thymeleaf:thymeleaf-spring5');

并明确声明控制器:

@RequestMapping("/")
public String index()
   return "index";
 

index.html 看起来像这样:

<html lang="en" ng-app="springChat" xmlns:th="http://www.springframework.org/schema/beans">
<head>
    ...
    <meta name="_csrf" th:content="$_csrf.token"/>
    <meta name="_csrf_parameter_name" th:content="$_csrf.parameterName"/>

....
</body>     
<script type="application/javascript">

    $('#login-form').submit(function (ev) 
        ev.preventDefault(); // to stop the form from submitting
        var token = $("meta[name='_csrf']").attr("content");
        var paramName = $("meta[name='_csrf_parameter_name']").attr("content");
        $('<input>').attr('type', 'hidden').attr('name', paramName).attr('value', token).appendTo('#login-form');

        this.submit(); 
    );
</script>
</html>

如果我们没有显式声明映射并且不尝试将 th:content="$_csrf.token" 之类的值插入变量中,则看起来像在 /resources/static 原始 html 中的 spring boot 搜索。

【讨论】:

谢谢!只是为了引起注意 th: 在这里是非常重要的前缀。【参考方案2】:

尝试将此输入直接添加到您的表单中(而不是使用 jQuery):

<form>
    <!-- all the rest inputs -->

    <input type="hidden" name="$_csrf.parameterName" value="$_csrf.token"/>
</form>

如果您通过 AJAX 将数据发送到服务器,则可以从您的 &lt;meta&gt;s 获取这些值。

$(function () 
    var token = $("meta[name='_csrf']").attr("content");
    var header = $("meta[name='_csrf_header']").attr("content");

    $(document).ajaxSend(function(e, xhr, options) 
        xhr.setRequestHeader(header, token);
    );
);

如果您需要将CsrfToken 保存在cookie 中,默认情况下CookieCsrfTokenRepository 将写入名为XSRF-TOKEN 的cookie 并从名为X-XSRF-TOKEN 的标头或HTTP 参数@987654328 中读取它@。

【讨论】:

我使用的是原始 html,所以我不能使用它 这个解决方案有效,但是你知道为什么在jsp表单中我们可以发送表单数据,但是当我们在做Ajax时,我们需要将它作为标题而不是表单数据添加到正文中?令人困惑

以上是关于如何将 csrf 令牌添加到 html 表单?的主要内容,如果未能解决你的问题,请参考以下文章

Symfony 4 - 如何在不构建表单的情况下添加 csrf 令牌?

Codeigniter CSRF - 它是如何工作的

如何将 Django 的 CSRF 令牌添加到 jQuery POST 请求的标头?

查询如何存储和处理框架CSRF令牌

当我将 dropzone 添加到 Django 中的现有表单时,CSRF 令牌问题

如何在 Javascript 生成的 HTML 表单中包含 Django 1.2 的 CSRF 令牌?