Angular SpringBoot SpringSecurity 应用程序 Ajax POST 与 csrf 令牌在令牌/标题未定义时失败

Posted

技术标签:

【中文标题】Angular SpringBoot SpringSecurity 应用程序 Ajax POST 与 csrf 令牌在令牌/标题未定义时失败【英文标题】:Angular SpringBoot SpringSecurity Application Ajax POST with csrf Token Fails on Token/Header being undefined 【发布时间】:2016-06-20 21:10:47 【问题描述】:

上下文/现状

我目前正在使用带有 Spring Security 实现的 SpringBoot 开发一个单页 Angular JS 应用程序。该应用程序适用于“学习计划者”,学生可以在日历中输入休学假。到目前为止,我们已经让学生能够登录,并将详细信息输入到日历客户端。

目标/成为

在我们的客户端能够捕获事件添加之后 javascript,我们正在尝试使用 Ajax POST 将其发送回我们的服务器。一旦发送到服务器,目标就是将其保存在我们的数据库中以针对学生,以便下次学生查看日历时可以加载它。

问题

我们遇到的问题是围绕 Ajax POST 方法,我相信是由于在 POST 中引入了 csrf 标头以试图通过 Spring Security。如果没有 csrf 标头,我会在 Chrome 中看到网络流量并收到 403(未经授权)错误。引入 csrf 标头后,没有记录网络流量,没有命中服务器,也命中了 ajax“错误”功能。

没有 CSRF 令牌

    学生登录日历 学生参加活​​动 警报触发“newEventData 函数” 警报触发“我们失败了”

    Chrome 出现 403 错误

    403(禁止) “在请求参数 '_csrf' 或标头 'X-XSRF-TOKEN' 上发现无效的 CSRF Token 'null'。”

使用 CSRF 令牌

(改编自 spring docs 以在 'beforesend' 中。如果我只是将文档中的函数添加到我的 js 文件的底部,也会发生同样的情况)

    学生登录日历 学生参加活​​动 警报触发“newEventData 函数” “发送前”触发警报 警报触发“我们失败了”

    抛出错误

    语法错误:无法对“XMLHttpRequest”执行“setRequestHeader”:“$(_csrf.headerName”不是有效的 HTTP 标头字段名称。

援助领域

如何查看有关 Ajax 错误的更多信息。 什么可能导致根本没有显示网络流量,即使 ajax 正在触发并返回错误。 有关配置弹簧安全装置的任何提示,以避免这种情况(除了简单地关闭它)

其他相关信息

我们扩展了 WebSecurityConfigAdapter
    @Configuration
    @Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
    protected static class SecurityConfiguration extends WebSecurityConfigurerAdapter 

    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    /**
     * Set up url based security
     */
    protected void configure(HttpSecurity http) throws Exception 
        http.httpBasic().and().authorizeRequests().antMatchers("/index.html", "/home.html", 
                "/login.html", "/", "/user","/login")
                .permitAll().anyRequest().authenticated().and()
                .addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class).csrf()
                .csrfTokenRepository(csrfTokenRepository()).and().logout();
    

    @Override
    /**
     * Set up a service to handle authentication requests
     */
    public void configure(AuthenticationManagerBuilder auth) throws Exception 
        // auth.userDetailsService(userDetailsService).passwordEncoder(new
        // BCryptPasswordEncoder());
        auth.userDetailsService(userDetailsService);
    

    private CsrfTokenRepository csrfTokenRepository() 
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        return repository;
    
     
我们还扩展了“OncePerRequestFilder”(尽管这些系统输出都没有在 Ajax POST 上打印)
public class CsrfHeaderFilter extends OncePerRequestFilter 

    /**
     * Overrides SpringSecurity filter
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException 
        System.out.println("In doFilterInternal");
        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        if (csrf != null) 
            System.out.println("csrf is null");
            Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
            String token = csrf.getToken();
            if (cookie == null || token != null && !token.equals(cookie.getValue())) 
                System.out.println("cookie null token not and token doesnt equal cookie");
                cookie = new Cookie("XSRF-TOKEN", token);
                cookie.setPath("/");
                response.addCookie(cookie);
            
        
        System.out.println("doing filter chain");
        filterChain.doFilter(request, response);
    
    
这是我们尝试使用的方法。如果我从我们的 HttpSecurity 中删除 csrf 安全性,这会很好并打印出所有详细信息。
@RequestMapping(method = RequestMethod.POST, value = "/Student/studentusername/universitydays")
public void newEvent(String id, String text, String start, String end, @PathVariable String studentusername) 
    System.out.println(text);
    System.out.println(studentusername);
    System.out.println(id);
    System.out.println(start);
    System.out.println(new Date(Long.parseLong(start)));
    System.out.println(new Date(Long.parseLong(end)));

为这篇超长的帖子道歉,这是我一直在寻找的东西,但对网络应用安全性并没有很好的了解,所以我正在努力将各个部分拼凑在一起。任何指导将不胜感激。

干杯, 斯科特。

更新

添加 Paqman 建议的错误参数后,我现在有了以下信息,但仍不确定如何处理。

SyntaxError: Failed to execute 'setRequestHeader' on 'XMLHttpRequest': '$_csrf.headerName' is not a valid HTTP header field name.

当使用我的 chrome 控制台时,这些会作为文本变量返回。我认为这是不正确的?

我在 index.html 标头中的代码(其他页面作为“部分”加载)是:

<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" content="$_csrf.token" />
<meta name="_csrf_header" content="$_csrf.headerName" />
</head>

进一步更新

我们设法通过使用 th:content 而不是 'content' 解决了上述文字字符串问题,但这仍然给我们带来了关于未定义标头/令牌的错误。我已经发布了对我们问题的实际解决方案作为答案,但是如果有人在 Angular 项目中检索 html 文件中的元数据时遇到类似问题,我相信将“内容”更改为“th:内容”可能会有所帮助。

See this related question

<head>
<title>Study Planner</title>
...css files...
<meta name="_csrf" th:content="$_csrf.token" />
<meta name="_csrf_header" th:content="$_csrf.headerName" />
</head>

【问题讨论】:

您使用的是哪个版本的 jQuery?您是否尝试过使用 $.ajax 的 headers 属性? var myHeaders = ;我的标头[标头]=令牌; beforeSend : function(xhr) // ..., headers : myHeaders 还有一件事,“错误”函数有 3 个参数:( jqXHR jqXHR, String textStatus, String errorThrown )。也许你会在里面找到一些有用的信息。 您的“$(_csrf.headerName”似乎拼写错误。您以“(”开头,但以“”结尾。或者只是检查您的 HTML内容,尤其是 __csrf 标签,它似乎没有正确的值。 道歉@Paqman 错字只是***,因为它不会让我C&P 退出警报。我认为问题在于设置元,我有这些行: 添加到我的普通 .html 页面中,并在检查时将这些维护为带有 var 名称的字符串。这是页面是 .html 而不是 JSP 的问题吗?我没有编写应用程序的启动代码,但我的同事最初遵循了指南,所以我认为页面类型可以 那么您的 $ vars 似乎没有被处理。也许您的 html 没有被解析,如果没有项目设置,我真的无法在这一点上为您提供帮助。您可以尝试按照您所说的将 .html 文件重命名为 .jsp。 【参考方案1】:

经过一番困惑,我们终于解决了这个问题。尽管我发现的大多数文档都指向存储在会话中的 csrf 令牌,但在我们的应用程序中,我们覆盖了默认的 SpringSecurity 行为以将其存储在 cookie 中(取自 SpringBoot Angular Guide)。

 public class CsrfHeaderFilter extends OncePerRequestFilter 

/**
 * Overrides SpringSecurity filter
 */
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException 
    CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
    if (csrf != null) 
        System.out.println("csrf is null");
        Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
        String token = csrf.getToken();
        if (cookie == null || token != null && !token.equals(cookie.getValue())) 
            cookie = new Cookie("XSRF-TOKEN", token);
            cookie.setPath("/");
            response.addCookie(cookie);
        
    
    filterChain.doFilter(request, response);
  

因此,在关注 Spring Docs here 时,我们似乎在“找错了树”。

一旦意识到这一点,我们实际上遇到了一些Django docs,它指导我们从 cookie 中检索令牌值。

解决方案

我们将此函数添加到我们的 javascript 中以检索令牌:

function getCookie(name) 
    var cookieValue = null;
    if (document.cookie && document.cookie != '') 
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) 
            var cookie = jQuery.trim(cookies[i]);
            // Does this cookie string begin with the name we want?
            if (cookie.substring(0, name.length + 1) == (name + '=')) 
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            
        
    
    return cookieValue;

将 var 设置为该函数返回的值

 var csrftoken = getCookie('XSRF-TOKEN');

最后硬编码(不确定我们稍后是否会遇到问题)HEADER,并使用上面创建的变量作为令牌。

function newEventData(ev) 
    $.ajax(
        "url" : "/Student/" + loggedinusername
                     + "/universitydays?id=" + ev.id + "&text="
                     + ev.text + "&start="
                     + Date.parse(ev.start_date) + "&end="
                     + Date.parse(ev.end_date),

        "method" : "POST",
        beforeSend : function(xhr) 
              xhr.setRequestHeader("X-XSRF-TOKEN", csrftoken);
        ,
        "success" : function() 

        ,
        "error" : function(jqXHR, textStatus, errorThrown) 
              alert("error: " + errorThrown);
         
 );


希望这对遇到类似问题的其他人有所帮助。

【讨论】:

以上是关于Angular SpringBoot SpringSecurity 应用程序 Ajax POST 与 csrf 令牌在令牌/标题未定义时失败的主要内容,如果未能解决你的问题,请参考以下文章

Spring Boot + Angular 应用程序工作

Spring Boot + Angular 2 结构

Spring Boot + Angular 应用程序

maven - Spring Boot/Angular 2/4 项目战构建

基于 Angular 角色的身份验证密钥斗篷和 Spring Boot

Angular 4/5 + Spring Boot + Oauth2 支持