java - 使用 Spring Security 和 AngularJS 预防 CSRF

标签 java angularjs spring-security csrf csrf-token

我正在使用 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();
        }
    }

下面是我的 angular.service 代码

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 token header 以及请求。

在此应用程序中,我们使用 3 个 jsp 页面 - login.jsp、logout.jsp 和仪表板.jsp Angular 范围是在dashboard.jsp中定义的,因此登录和注销超出了AngularJS的范围。 我也尝试过从 this 开始的无状态方式。和 this例如,Angular 生成 UUID 并附加 cookie 和请求 header ,下面的过滤器可以很好地完成工作。 直到注销攻击。在这次攻击中,攻击者试图成功注销用户,因为要从应用程序注销,我们只需使用 href。

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

现在,由于它的注销超出了 Angular ,所以 angularjs 拦截器无法在那里附加 UUID。 自上周以来我一直在努力解决这个问题,任何帮助将不胜感激。

无状态CSRFFilter.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);
    }
}

No CSRF Token being added

最佳答案

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

鉴于您有一个移动 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 token 进行身份验证,这就是上面的配置使用 Spring Security 5.1+ 中的 oauth2ResourceServer() 的原因。其作用是选择性地禁用包含 Authorization: Bearer xyz header 的请求的 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();
    }
}

不过,您需要确保的是,您的自定义身份验证过滤器不使用浏览器从任何来源自动发送的身份验证机制( session cookie、授权:基本)。

关于java - 使用 Spring Security 和 AngularJS 预防 CSRF,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/58911887/

相关文章:

java - 从 JTable 中删除行

java - Spring Integration AMQP - 响应上的 JSON 类型错误

angularjs - 在 AngularJS 中使服务异步

java - 带有 Spring Security 的 JSON REST API

java - 扩展 spring security UsernamePasswordAuthenticationFilter

java - 搜索并更改数组列表中的对象

java - 从外部运行的 jar 输出到 Eclipse 控制台

javascript - 使用 ng-include 的最简单方法

javascript - 模型窗口内的 AngularJS 范围

spring - 我可以将 Spring MVC servlet 的 WebApplicationContext 注入(inject) DelegatingFilterProxy 吗?