AngularJS HTTP POST 未找到预期的 CSRF 令牌

Posted

技术标签:

【中文标题】AngularJS HTTP POST 未找到预期的 CSRF 令牌【英文标题】:AngularJS HTTP POST Expected CSRF token not found 【发布时间】:2016-05-24 07:06:33 【问题描述】:

我正在学习 Spring 博客 (https://spring.io/guides/tutorials/spring-security-and-angular-js/) 的教程。但是我从现有的 Spring 应用程序开始,因此我没有使用 Spring Boot 开始,我必须找到一种方法以 XML 和 Java 配置混合样式实现组件。

这是我的 CORS 过滤器:

@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class SimpleCORSFilter implements Filter 

    public SimpleCORSFilter()

    

    @Override
    public void init(FilterConfig filterConfig) throws ServletException 

    

    @Override
    public void doFilter(ServletRequest req, ServletResponse resp,
            FilterChain chain) throws IOException, ServletException 
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response=(HttpServletResponse) resp;

        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE, PUT, PATCH");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        //x-auth-token is a custom header for Spring Security AngularJS implementation
        response.setHeader("Access-Control-Allow-Headers", "Options, authentication, authorization, X-Auth-Token, Origin, X-Requested-With, Content-Type, Accept, XSRF-TOKEN");
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) 
            System.out.println("OPTIONS request from AngularJS");
            response.setStatus(HttpServletResponse.SC_OK);
        
        chain.doFilter(req, response);
    

    @Override
    public void destroy() 

这是我的 CsrfHeaderFilter.java,几乎是从教程中复制的:

@Component
public class CsrfHeaderFilter extends OncePerRequestFilter

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException 
        System.out.println("CsrfHeaderFilter vvv");
        CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
        if(csrf != null)
            Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
            String token = csrf.getToken();
            System.out.println("CSRFToken Value: "+token);
            if(cookie == null || token != null && !token.equals(cookie.getValue()) )
                cookie = new Cookie("XSRF-TOKEN", token); //use XSRF-TOKEN as the response header for CSRF token
                cookie.setPath("/");
                response.addCookie(cookie);
            
        
        System.out.println("CsrfHeaderFilter ^^^");
        filterChain.doFilter(request, response);
    

并且CsrfHeaderFilter配置在Spring的CsrfFilter之后:

<sec:custom-filter ref="csrfHeaderFilter" after="CSRF_FILTER" />
<sec:csrf token-repository-ref="csrfTokenRepository"/>

csrfTokenRepository:

@Configuration
public class CustomCsrfTokenRepository 

    @Bean
    public CsrfTokenRepository csrfTokenRepository()
        HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
        repository.setHeaderName("X-XSRF-TOKEN");
        repository.setParameterName("_csrf");
        return repository;
    

我有自己的带有自定义登录 URL 的身份验证过滤器:

public class CustomerAuthenticationTokenProcessingFilter extends AbstractAuthenticationProcessingFilter

    private static final String SECURITY_TOKEN_HEADER = "x-auth-token";
    private static final String AUTHORIZATION_HEADER = "authorization";

    @Autowired
    private CustomerTokenAuthenticationService tokenAuthenticationService;

    @Autowired
    CustomerAuthenticationService customerAuthenticationService;
    @Autowired
    @Qualifier("customerAuthenticationManager")
    AuthenticationManager authenticationManager;

    protected CustomerAuthenticationTokenProcessingFilter()
        super("/company/login"); 
    

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws AuthenticationException, IOException, ServletException 
            Authentication authentication = null;
            //Authentication Logics...
            ...
        return authentication;
    

    @Override
    protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
            Authentication authResult) throws IOException, ServletException 
        SecurityContextHolder.getContext().setAuthentication(authResult);
      

当然还有一个自定义的注销网址:

<sec:logout invalidate-session="true" delete-cookies="JSESSION,XSRF-TOKEN" 
    logout-url="/resource/logout" success-handler-ref="customerLogoutSuccessHandler"/>

customerLogoutSuccessHandler:

public class CustomerLogoutSuccessHandler implements LogoutSuccessHandler

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
            throws IOException, ServletException 

        if (authentication != null && authentication.getDetails() != null) 
            try 

                SecurityContextHolder.clearContext();
                System.out.println("User Successfully Logout"); 
                response.setStatus(HttpServletResponse.SC_OK);

             catch (Exception e)   
                e.printStackTrace();  
                e = null;  
              
          
    


AngularJS 代码非常简单。最初我显示登录表单,只是向 Spring 的 /company/login 端点发出 HTTP POST 请求,但不知何故我的 AngularJS 应用程序没有获得它需要的 CSRF 令牌......所以我在启动时添加了一个 HTTP GET 请求来请求来自一个开放的 URL (access="permitAll()") 以便为我即将到来的请求获取 XSRF-TOKEN。登录和注销工作正常,直到我再次登录。错误是“POST http://localhost:8080/company/login403 (Forbidden)”和“Invalid CSRF Token was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'”

我认为我的浏览器中的 cookie 有问题。当我在我的 CORS 过滤器中输出 cookie 数据时,我可以看到与传递给 Spring 之前相同的 XSRF-TOKEN,并且 Spring 的 CsrfFilter 拒绝了进一步的请求,因为 CSRF 令牌不正确。

FilterChainProxy DEBUG - /company/login at position 3 of 14 in additional filter chain; firing Filter: 'CsrfFilter'
CsrfFilter DEBUG - Invalid CSRF token found for http://localhost:8080/company/login

也许我在注销部分缺少一些功能?如果我的请求从未通过 Spring 的 CsrfFilter,我该如何更新 XSRF-TOKEN?

如有必要,请随时向我询问更多详细信息。我真的很想解决这个问题,因为我已经花了很多时间试图找出问题所在:(

【问题讨论】:

【参考方案1】:

所以,在我发布这个答案后不久,我就发现了问题......

后端服务器配置没问题,问题其实是来自AngularJS的HTTP请求。由于我正在学习 Spring 博客中的教程,因此我也使用了教程中的 AngularJS 代码,因为我对 AngularJS 还很陌生。我创建了更多的函数和控制器来满足我的需要,我正在使用 $http.get 和 .success() 和 .error(),这是一个异步调用。不知何故,这导致了 Csrf 令牌问题。所有这些异步http请求都让我很困惑。所以我决定想办法等到 Http 请求在下一个 Http 请求之前完成,看看会发生什么。 我有一个工厂来处理登录和注销。

这可能不是最好的方法,但这里是 AngularJS 代码的一部分:

//Move the initial GET request to a variable
            var init = function()
                  return $http(
                            
                                url : '/my/url/',
                                method : 'GET',
                            
                  ).then(function(result)
                      //bunch of debugging messages
                      return result.data;
                  )

            

下面是如何调用我的初始 GET 请求来获取 CSRF 令牌,然后在 GET 请求完成后发出登录 POST 请求:

                var initPromise = init();
                initPromise.then(function(result)
                    $http(
                            
                                url : '/url/to/login',
                                method : 'POST',
                                headers : headers
                            
                    ).success(function(data, config, headers) 
                       //debugging messages of course
                        //getting my data
                        ...

                        if(data_validation_is_true)
                            $http(
                                url : '/my/secured/resource/url',
                                method : 'GET',
                                headers : 
                                
                                    'X-Auth-Token' : token_I_Received
                                
                            ).success(function(data, config, headers)
                                console.log('/inventory/resource/user request with token, response headers: '+JSON.stringify(headers()));
                                if (authenticated) 
                                      //authentication successful logic
                                      ...
                                     else 
                                      //authentication unsuccessful logic
                                      ...
                                    

                            ).error(function(data)
                                //error handling
                                ...
                            );
                        
                      ).error(function(data, status) 
                        //error handling
                                ...
                      );
                )

如果有什么问题,请给我任何建议。

【讨论】:

以上是关于AngularJS HTTP POST 未找到预期的 CSRF 令牌的主要内容,如果未能解决你的问题,请参考以下文章

从 .Net 应用程序到 AWS API Gateway 的 HTTP POST 请求未按预期工作

AngularJS $http 对象未显示响应中的所有标头

angularJS通过post方法下载excel文件

cURL 未获取预期的 POST 响应 - 错误 405

AngularJS HTTP 发布到 PHP 和未定义

HTTP 状态 403 - 未找到预期的 CSRF 令牌。你的会话过期了吗?