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

时间:2016-03-07 10:44:49

标签: javascript angularjs ajax spring-security csrf

背景/原样

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

目标/待

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

问题

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

没有CSRF令牌

enter image description here

  1. 学生登录其日历
  2. 学生添加活动
  3. 警报触发'newEventData function'
  4. 警报触发'我们失败'
  5. Chrome中的403错误

    403(禁止) “在请求参数'_csrf'或标题'X-XSRF-TOKEN'上找到”无效的CSRF令牌'null'。“

  6. 使用CSRF令牌

    enter image description here

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

    1. 学生登录其日历
    2. 学生添加活动
    3. 警报触发'newEventData function'
    4. 警报触发'beforesend'
    5. 警报触发'我们失败'
    6. 抛出错误

      SyntaxError:无法在'XMLHttpRequest'上执行'setRequestHeader':'$(_ csrf.headerName}'不是有效的HTTP标头字段名称。

    7. 援助范围

      • 如何查看有关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.
      

      enter image description here

      使用我的chrome控制台时,它们将作为文本变量返回。我认为这是不正确的? enter image description here

      我在index.html标题内的代码(其他页面加载为'partials')是:

      <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:content”可能会有所帮助。

      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>
      

1 个答案:

答案 0 :(得分:0)

经过多次混乱,我们终于解决了这个问题。虽然我发现大多数文档指向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时,我们似乎正在“咆哮错误的树”。

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

<强>解决方案

我们将此功能添加到我们的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);
        } 
 });

}

希望如果他们遇到类似的问题,这会有所帮助。