0

我正在使用 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 的范围。我也从这个这个例子中尝试了无状态的方式,其中 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);
    }
}

没有添加 CSRF 令牌

4

3 回答 3

1

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

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

我建议您创建两个单独的过滤器链,就像这样,一个用于 Web 应用程序,一个用于移动 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 令牌进行身份验证,这就是上述配置使用oauth2ResourceServer()Spring Security 5.1+ 的原因。这样做是有选择地为包含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)自动发送的身份验证机制。

于 2019-11-19T18:27:48.487 回答
0

<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>

于 2019-11-23T15:19:17.937 回答
0

`嗨湿婆,

您在 SecurityConfig 的配置方法中的代码应类似于以下代码:

 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");
 }`
于 2019-11-22T16:16:35.740 回答