【问题标题】:CSRF Prevention with Spring Security and AngularJS使用 Spring Security 和 AngularJS 预防 CSRF
【发布时间】:2020-03-13 15:57:12
【问题描述】:

我使用的是 Spring 4.3.12.RELEASE 版本,AngularJS 1.4.8。我正在尝试阻止对应用程序的 CSRF 攻击。

@Configuration
    @Order(2)
    public static class SecurityConfig extends WebSecurityConfigurerAdapter {

        String[] pathsToRemoveAuthorizaton = {
                "/mobile/**",
                "/logout",
                "/j_spring_security_logout",
                "/login",
        };

        private final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);

        @Override
        public void configure(HttpSecurity http) throws Exception {

            logger.info("http configure");
            http.antMatcher("/**").authorizeRequests().antMatchers(pathsToRemoveAuthorizaton).permitAll()
                    .antMatchers("/**").authenticated()
                    .and().formLogin().loginPage("/login")
                    .usernameParameter("employeeId").passwordParameter("password")
                    .successForwardUrl("/dashboard").defaultSuccessUrl("/dashboard", true)
                    .successHandler(customAuthenticationSuccessHandler()).loginProcessingUrl("/j_spring_security_check")
                    .and().logout().logoutSuccessUrl("/logout").logoutUrl("/j_spring_security_logout")
                    .logoutSuccessHandler(customLogoutSuccessHandler()).permitAll().invalidateHttpSession(true)
                    .deleteCookies("JSESSIONID").and().sessionManagement().sessionFixation().newSession()
                    .maximumSessions(1).maxSessionsPreventsLogin(true).and()
                    .sessionCreationPolicy(SessionCreationPolicy.NEVER).invalidSessionUrl("/logout").and()
                    .exceptionHandling().accessDeniedPage("/logout");

//          http.csrf().csrfTokenRepository(csrfTokenRepository()).and()
//          .addFilterAfter(new StatelessCSRFFilter(), CsrfFilter.class);

            http.csrf().csrfTokenRepository(csrfTokenRepository());

//          http.csrf().disable();
//          http.csrf().ignoringAntMatchers("/mobile/**");

            http.authorizeRequests().anyRequest().authenticated();
        }

        private CsrfTokenRepository csrfTokenRepository() {
            HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
            repository.setHeaderName("X-XSRF-TOKEN");
            return repository;
        }


        @Bean
        public AuthenticationSuccessHandler customAuthenticationSuccessHandler() {
            return new CustomAuthenticationSuccessHandler();
        }

        @Bean
        public LogoutSuccessHandler customLogoutSuccessHandler() {
            return new CustomLogoutSuccessHandler();
        }
    }

以下是我的角度服务代码

govtPMS.service('Interceptor', function($q, $location, $rootScope, pinesNotifications, Auth) {

  return {

    request: function(config) {
        config.headers.Authorization = 'Bearer '+$rootScope.authToken;
//        document.cookie = 'CSRF-TOKEN=' + $rootScope.generateKey;

       return config;
    },
    requestError: function (rejection) {
        return $q.reject(rejection);
    },
    response: function(res) {

        if(res.status === 200 || res.status === 201){
            if(res.data.response !== undefined){
                if(res.data.status === 1 || res.data.status === 3 || res.data.status === 2) {
                    pinesNotifications.notify({
                        'title': 'Success',
                        'text': res.data.message,
                        'type': 'success',
                        'delay': 5000
                    });
                }
                 else if(res.data.status === 5) {
                    pinesNotifications.notify({
                        'title': 'Warning',
                        'text': res.data.message,
                        'type': 'warning',
                        'delay': 5000
                    });
                }
            }
        }
      return res || $q.when(res);
    },
    responseError: function(error) {
      return $q.reject(error);
    }
  };
}).config(['$httpProvider', function($httpProvider) {
    $httpProvider.defaults.xsrfHeaderName = 'X-CSRF-TOKEN';
  $httpProvider.defaults.xsrfCookieName = 'CSRF-TOKEN';

  $httpProvider.interceptors.push('Interceptor');

}])

我仍然无法看到 CSRF 令牌标头以及请求。

在这个应用程序中,我们使用了 3 个 jsp 页面 - login.jsp、logout.jsp 和 dashboard.jsp 角度范围在dashboard.jsp 中定义,因此登录和注销超出了AngularJS 的范围。 我还尝试了thisthis 示例中的无状态方式,其中 angular 生成 UUID 并附加 cookie 和请求标头,下面的过滤器做得很好。 直到注销攻击。在此攻击中,攻击者试图成功注销用户,因为要从应用程序中注销,我们只是使用了 href。

<li><a href="j_spring_security_logout" ><i class="fa fa-sign-out"></i><span>Logout</span></a></li>

现在,由于它的注销没有角度,所以 angularjs 拦截器无法在此处附加 UUID。 自上周以来,我一直在为此苦苦挣扎,我们将不胜感激。

StatelessCSRFFilter.java

package com.leadwinner.sms.config.filters;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.web.filter.OncePerRequestFilter;

public class StatelessCSRFFilter extends OncePerRequestFilter {



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


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        List<String> excludedUrls = new ArrayList<>();
        excludedUrls.add("/resources");
        excludedUrls.add("/j_spring_security_check");
        excludedUrls.add("/j_spring_security_logout");
        excludedUrls.add("/login");
        excludedUrls.add("/logout");
        excludedUrls.add("/mobile");
        excludedUrls.add("/migrate");
        excludedUrls.add("/dashboard");

        String path = request.getServletPath();
        System.out.println(path);

        AtomicBoolean ignoreUrl = new AtomicBoolean(false);

        excludedUrls.forEach(url -> {
            if (request.getServletPath().startsWith(url.toLowerCase()) || request.getServletPath().equals("/")) {
                ignoreUrl.set(true);
            }
        });

        if (!ignoreUrl.get()) {
            final String csrfTokenValue = request.getHeader(X_CSRF_TOKEN);
            final Cookie[] cookies = request.getCookies();
            System.out.println("**************************************************");
            System.out.println("--------------------------------------------------");
            String csrfCookieValue = null;
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (cookie.getName().equals(CSRF_TOKEN)) {
                        csrfCookieValue = cookie.getValue();
                    }
                }
            }
            System.out.println("csrfTokenValue = "+csrfTokenValue);
            System.out.println("csrfCookieValue = "+csrfCookieValue);

            System.out.println("--------------------------------------------------");
            System.out.println("**************************************************");
            if (csrfTokenValue == null || !csrfTokenValue.equals(csrfCookieValue)) {
                accessDeniedHandler.handle(request, response, new AccessDeniedException(
                        "Missing or non-matching CSRF-token"));
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

【问题讨论】:

  • 我有点困惑,因为您似乎正在使用 Spring Security 的 CSRF 保护(连接 CsrfTokenRepository),同时还禁用了保护(csrf().disable())。此外,您正在编写自己的 CSRF 过滤器。如果您尝试编写自己的 CSRF 保护,请您澄清一下吗?如果是的话,为什么Spring Security提供的还不够呢?
  • 对不起。已被评论。我只需要使用 Spring 的 CSRF 保护方式,并为某些移动 API 禁用 CSRF。由于我不能这样做,我尝试了另一种通过角度拦截器在 Header 和 Cookie 中附加 CSRF 令牌的替代方法,除了注销页面之外,它工作正常,因为对于注销,href 正在使用。在 Spring 中启用 CSRF 保护的任何帮助,我可以禁用移动 API 的 CSRF 检查就足够了。

标签: java angularjs spring-security csrf csrf-token


【解决方案1】:

如果可以通过浏览器发出请求并且自动提交凭据(会话 cookie、基本身份验证凭据),则 CSRF 保护是必要的,即使使用移动 API。

鉴于您将移动 API 作为应用程序的一部分,问题是浏览器能否成功处理这些 API?

我建议您创建两个单独的过滤器链,就像这样,一个用于网络应用程序,一个用于移动 API:

@Configuration
@Order(100)
public class WebAppConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) {
        http
            .requestMatchers()
                .antMatchers("/app/**")
                .and()
            .authorizeRequests()
                // ...
                .anyRequest().authenticated()
                .and()
            .formLogin();
    }
}

@Configuration
@Order(101)
public class MobileApiConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) {
        http
            .requestMatchers()
                .antMatchers("/api/**")
                .and()
            .authorizeRequests()
                // ...
                .anyRequest().authenticated()
                .and()
            .oauth2ResourceServer()
                .jwt();
    }
}

这样做的目的是将两种配置分开。与 Web 应用程序相关的端点使用一种设置,而与移动 API 相关的端点使用另一种设置。

现在,有几个关于移动 API 的问题。我假设您正在使用 OAuth 2.0 Bearer 令牌进行身份验证,这就是上面的配置使用 Spring Security 5.1+ 中的 oauth2ResourceServer() 的原因。这样做是有选择地为包含 Authorization: Bearer xyz 标头的请求禁用 CSRF。

但是,由于您使用的是 Spring Security 4.3,因此您可能需要执行以下操作(除非您可以升级):

@Order(101)
public class MobileApiConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) {
        http
            .requestMatchers()
                .antMatchers("/api/**")
                .and()
            .authorizeRequests()
                // ...
                .anyRequest().authenticated()
                .and()
            .sessionManagement().sessionCreationPolicy(NEVER)
                .and()
            .addFilterBefore(new MyMobileAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
            .csrf().disable();
    }
}

不过,您需要确保的是,您的自定义身份验证过滤器不使用浏览器自动从任何来源(会话 cookie,Authorization: Basic)发送的身份验证机制。

【讨论】:

  • 1.移动 API,我们不包括在此配置中,有一个 MobileSessionAspector 正在拦截我们正在检查 JWT 令牌授权的所有移动相关 API。 2. 我已经在使用 MultiHttpSecurity Config。 stackoverflow.com/questions/58231064/…
  • 3.我需要做的就是在 Spring 和 AngularJS 中启用 CSRF,以便保护所有请求(API)和注销功能免受 CSRF 攻击,并且我可以选择指定可以排除的模式列表CSRF 保护。
【解决方案2】:

`嗨湿婆,

您在 SecurityConfig 的 configure 方法中的代码应该如下所示:

 http
     .authorizeRequests()
     .antMatchers(patterns)
     .permitAll()
       .antMatchers("/hello/**")
       .hasRole("USER")
       .and()
       .csrf()
       .csrfTokenRepository(csrfTokenRepository())
       .requireCsrfProtectionMatcher(csrfProtectionMatcher(patterns))
       .and()
       .httpBasic()
       .and()
       .addFilterAfter(csrfFilter(patterns), FilterSecurityInterceptor.class)
       .addFilterAfter(new StatelessCSRFFilter(), CsrfFilter.class);

在StatelessCSRFFilter中,使用如下代码:

 @Override
 public void init(FilterConfig filterConfig) throws ServletException {}

 @Override
 public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
     CsrfToken csrf = (CsrfToken) servletRequest.getAttribute(CsrfToken.class.getName());
     String token = csrf.getToken();
     if (token != null && isAuthenticating(servletRequest)) {
         HttpServletResponse response = (HttpServletResponse) servletResponse;
         Cookie cookie = new Cookie("XSRF-TOKEN", token);
         cookie.setPath("/");
         response.addCookie(cookie);
     }
     filterChain.doFilter(servletRequest, servletResponse);
 }

 private boolean isAuthenticating(ServletRequest servletRequest) {
     HttpServletRequest request = (HttpServletRequest) servletRequest;
     return request.getRequestURI().equals("/login");
 }`

【讨论】:

  • 你也可以定义 csrfFilter(patterns) 方法吗?
【解决方案3】:

<pre>
	Add this code for the csrfTokenRepository method
	     private CsrfTokenRepository csrfTokenRepository() {
	         HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
	         repository.setHeaderName("X-XSRF-TOKEN");
	         return repository;
	     }
	Add this for the csrfFilter method
	     private Filter csrfFilter(String[] patterns) {
	         CsrfFilter csrfFilter = new CsrfFilter(csrfTokenRepository());
	         csrfFilter.setRequireCsrfProtectionMatcher(csrfProtectionMatcher(patterns));
	         return csrfFilter;
	     }
	Add this for the csrfProtectionMatcher method
	     private NoAntPathRequestMatcher csrfProtectionMatcher(String[] patterns) {
	         return new NoAntPathRequestMatcher(patterns);
	     }
	Also remove these lines in configure method
	       .csrfTokenRepository(csrfTokenRepository())
	       .requireCsrfProtectionMatcher(csrfProtectionMatcher(patterns))
	Move these lines below .csrf() in configure method:
	       .and()
	       .addFilterAfter(csrfFilter(patterns), FilterSecurityInterceptor.class)
</pre>

【讨论】:

    猜你喜欢
    • 2018-02-18
    • 2015-12-10
    • 2015-12-01
    • 2013-07-16
    • 2018-03-05
    • 2016-09-01
    • 2015-03-28
    • 2017-05-02
    相关资源
    最近更新 更多