Java 使用Spring Security和AngularJS预防CSRF

Java 使用Spring Security和AngularJS预防CSRF,java,angularjs,spring-security,csrf,csrf-token,Java,Angularjs,Spring Security,Csrf,Csrf Token,我使用的是Spring 4.3.12.0发行版AngularJS 1.4.8。我正试图阻止对应用程序的CSRF攻击 @Configuration @Order(2) public static class SecurityConfig extends WebSecurityConfigurerAdapter { String[] pathsToRemoveAuthorizaton = { "/mobile/**",

我使用的是Spring 4.3.12.0发行版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的范围内。 我也尝试过无状态的方式,例如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附加到那里。 从上个星期开始我就一直在努力,如果有任何帮助,我将不胜感激

    无状态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);
        }
    }
    
    package com.leadwenner.sms.config.filters;
    导入java.io.IOException;
    导入java.util.ArrayList;
    导入java.util.List;
    导入java.util.concurrent.AtomicBoolean;
    导入javax.servlet.FilterChain;
    导入javax.servlet.ServletException;
    导入javax.servlet.http.Cookie;
    导入javax.servlet.http.HttpServletRequest;
    导入javax.servlet.http.HttpServletResponse;
    导入org.springframework.security.access.AccessDeniedException;
    导入org.springframework.security.web.access.AccessDeniedHandler;
    导入org.springframework.security.web.access.AccessDeniedHandlerImpl;
    导入org.springframework.web.filter.OncePerRequestFilter;
    公共类无状态CSRFFilter扩展了OncePerRequestFilter{
    私有静态最终字符串CSRF_TOKEN=“CSRF-TOKEN”;
    私有静态最终字符串X_CSRF_TOKEN=“X-CSRF-TOKEN”;
    private final AccessDeniedHandler AccessDeniedHandler=new AccessDeniedHandlerImpl();
    @凌驾
    受保护的void doFilterInternal(HttpServletRequest请求、HttpServletResponse响应、FilterChain FilterChain)
    抛出ServletException、IOException{
    List excludedUrls=new ArrayList();
    添加(“/resources”);
    添加(“/j_-spring_-security_-check”);
    添加(“/j_spring_security_logout”);
    添加(“/login”);
    添加(“/注销”);
    添加(“/mobile”);
    添加(“/migrate”);
    不包括仪表盘。添加(“/仪表盘”);
    String path=request.getServletPath();
    System.out.println(路径);
    AtomicBoolean ignoreUrl=新的AtomicBoolean(false);
    excludedUrls.forEach(url->{
    if(request.getServletPath().startsWith(url.toLowerCase())| | request.getServletPath().equals(“/”){
    ignoreUrl.set(true);
    }
    });
    如果(!ignoreUrl.get()){
    最终字符串csrfTokenValue=request.getHeader(X_CSRF_TOKEN);
    最终Cookie[]cookies=request.getCookies();
    System.out.println(“*******************************************************************”);
    System.out.println(“-------------------------------------------------------------”;
    字符串csrfCookieValue=null;
    如果(cookies!=null){
    用于(Cookie:cookies){
    if(cookie.getName().equals(CSRF_令牌)){
    csrfCookieValue=cookie.getValue();
    }
    }
    }
    System.out.println(“csrfTokenValue=“+csrfTokenValue”);
    System.out.println(“csrfCookieValue=“+csrfCookieValue”);
    System.out.println(“-------------------------------------------------------------”;
    System.out.println(“*******************************************************************”);
    如果(csrfTokenValue==null | |!csrfTokenValue.equals(csrfCookieValue)){
    handle(请求、响应、新AccessDeniedException(
    “丢失或不匹配的CSRF令牌”);
    返回;
    }
    }
    filterChain.doFilter(请求、响应);
    }
    }
    

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

    考虑到应用程序中有一个移动API,问题是浏览器能否成功地处理这些API

    我建议您创建两个独立的过滤链,一个用于web应用程序,另一个用于移动API:

    @配置
    @订单(100)
    公共类WebAppConfig扩展了WebSecurity配置适配器{
    @凌驾
    受保护的无效配置(HttpSecurity http){
    http
    .requestMatchers()
    .antMatchers(“/app/**”)
    .及()
    .授权请求()
    // ...
    .anyRequest().authenticated()
    .及()
    .formLogin();
    }
    }
    @配置
    @命令(101)
    公共类MobileApiConfig扩展了WebSecurity配置适配器{
    @凌驾
    受保护的无效配置(HttpSecurity http){
    http
    .requestMatchers()
    .antMatchers(“/api/**”)
    .及()
    .授权请求()
    // ...
    .anyRequest().authenticated()
    .及()
    .oauth2ResourceServer()
    .jwt();
    }
    }
    
    这样做的目的是将两种配置分开。相对于web应用程序的端点使用一种设置,而相对于移动API的端点使用另一种设置

    现在,有几条关于移动API的评论。我假设您正在使用OAuth 2.0承载令牌进行身份验证,这就是为什么上面的配置使用Spring Security 5.1+中的
    oauth2ResourceServer()。这是什么
    
     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);
    
     @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");
     }`