双提交CSRF保护跨域

时间:2015-08-26 11:50:19

标签: javascript java angularjs spring cookies

我对如何进行CSRF保护感到困惑。我有单独的前端(angularjs)和后端(Spring)。它们部署在完全独立的地方,并通过REST进行通信。

我的问题如下。 Angular拒绝发送我的CSRF cookie跨域 - 我可以发送的是CSRF头。我尝试将withCredentials添加到我后端的角度和CORS过滤器,并按照here under Usage所述设置xsrf标头和Cookie。

任何想法我可能做错了什么?如果您需要我的代码的某些特定部分,请发布并发送。

@添加相关代码:

CORSFilter

public class CORSFilter implements Filter {

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) res;
        HttpServletRequest request = (HttpServletRequest) req;
        response.setHeader("Access-Control-Allow-Origin", "http://localhost:9000");
        response.setHeader("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE");
        response.setHeader("Access-Control-Allow-Headers", "x-requested-with,origin,content-type,accept,X-XSRF-TOKEN, authorization, customer-id, X-AUTH-TOKEN");
        response.setHeader("Access-Control-Expose-Headers", "employee_name, employee_id, employee_customer_id, X-AUTH-TOKEN");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        if (request.getMethod()!="OPTIONS") {
            chain.doFilter(req, res);
        } else {
        }
    }

CSRF过滤器

public class StatelessCSRFFilter extends OncePerRequestFilter {

    private static final String CSRF_TOKEN = "CSRF-TOKEN";
    private static final String X_CSRF_TOKEN = "X-XSRF-TOKEN";
    private final RequestMatcher requireCsrfProtectionMatcher = new DefaultRequiresCsrfMatcher();
    private final AccessDeniedHandler accessDeniedHandler = new AccessDeniedHandlerImpl();

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        if (requireCsrfProtectionMatcher.matches(request)) {
            final String csrfTokenValue = request.getHeader(X_CSRF_TOKEN);
            final Cookie[] cookies = request.getCookies();

            String csrfCookieValue = null;
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(CSRF_TOKEN)) {
                        csrfCookieValue = cookie.getValue();
                    }
                }
            }
            if (csrfTokenValue == null || !csrfTokenValue.equals(csrfCookieValue)) {
                accessDeniedHandler.handle(request, response, new AccessDeniedException(
                        "Missing or non-matching CSRF-token"));
                return;
            }
        }
        filterChain.doFilter(request, response);
    }

    public static final class DefaultRequiresCsrfMatcher implements RequestMatcher {
        private final Pattern allowedMethods = Pattern.compile("^(GET|HEAD|TRACE|OPTIONS)$");

        @Override
        public boolean matches(HttpServletRequest request) {
            return !allowedMethods.matcher(request.getMethod()).matches();
        }
    }

app.js

(...)
$httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
$httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN';
$httpProvider.interceptors.push('InterceptorCsrf');
$httpProvider.defaults.withCredentials = true;
(...)

InterceptorCsrf.js

angular.module('EnterprisePortalApp')
        .factory('InterceptorCsrf',function($cookies, $cookieStorage){
            function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e16]+1e16).replace(/[01]/g,b)};
            return {
                //With each request generate new csrf token
                request: function(config) {
                    $cookieStorage.put("CSRF-TOKEN", b());
                    config.headers['X-XSRF-TOKEN'] = $cookies.get('CSRF-TOKEN');
                    return config;
                }
            }   
});

2 个答案:

答案 0 :(得分:1)

您的代码块似乎没问题。您是否尝试"Access-Control-Allow-Origin", "http://localhost:9000"更改此*

BTW it's a bug in chrome pointing localhost with its port无法修复(SO discussion)。

此外,您可以尝试提供不同的域名(而不是localhost,您可以使用nginx代理设置等,这可能有些棘手)休息服务器和客户端主机。

根据这种情况的额外信息:

如果对REST服务使用基于令牌的身份验证,则不需要另外实施csrf保护。

如果用户需要在每次请求此休息服务时发送其访问令牌(例如jwt),那么您的服务将受到csrf的保护,并且还具有与csrf保护类似的方法。 User gets token->request messages with token->decode token on backend->getuserid(基本)并使他的流程成为基于令牌的请求流程,就像这些一样。在这种情况下,如果用户没有令牌,他就无法进行任何操作。

答案 1 :(得分:-1)

使用以下代码为我工作

  1. 服务器端,我已根据官方CSRF指南中的说明对CORSSpring Angular过滤器进行了编码。
  2. 客户端,我必须编写一个 $ http拦截器,如下所示,因为AngularJS doesn't automatically add是跨域请求的标头。

    angular.module('appBoot')
      .factory('XSRFInterceptor', function ($cookies, $log) {
    
        var XSRFInterceptor = {
    
          request: function(config) {
    
            var token = $cookies.get('XSRF-TOKEN');
    
            if (token) {
              config.headers['X-XSRF-TOKEN'] = token;
              $log.info("X-XSRF-TOKEN: " + token);
            }
    
            return config;
          }
        };
        return XSRFInterceptor;
      });
    
    angular.module('appBoot', ['ngCookies', 'ngMessages', 'ui.bootstrap', 'vcRecaptcha'])
        .config(['$httpProvider', function ($httpProvider) {
    
          $httpProvider.defaults.withCredentials = true;
          $httpProvider.interceptors.push('XSRFInterceptor');
    
        }]);