Home Spring Security, Csrf
Post
Cancel

Spring Security, Csrf

- Csrf(cross site request forgery)

  • A 사이트에 로그인, 사용자 권한 획득
  • 사용자가 웹 어플레케이션에 로그인 했다고 가정하며 사용자는 공격자에게 속아서
    직업중인 같은 어플리케이션에서 작업을 실행하는 스크립트가 포함된 페이지가 열리면서 공격

  • Spring Security에서는 해당 공격을 막기 위한 옵션이 자동으로 설정되어있음.
    • 서버쪽에 토큰값을 Cookie 형태로 저장.
    • 해당 구성을 확인하기 위한 Filter 추가.
    • CsrfTokenRepository를 직접 구현하여 설정을 바꿀 수 있음.


CsrfTokenLoggerFilter

  • Spring Security 가 자동으로 생성해주는 csrf 토큰 확인.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Slf4j
@Component
public class CsrfTokenLoggerFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
    Object o = request.getAttribute("_csrf");
    CsrfToken csrfToken = (CsrfToken) o;

    log.info("csrfToken.getParameterName = {}", csrfToken.getParameterName());
    log.info("csrfToken.getHeaderName = {}", csrfToken.getHeaderName());
    log.info("csrfToken.getToken = {}", csrfToken.getToken());
    filterChain.doFilter(request, response);
  }

}


CsrfTokenLoggerFilter

  • defaultSecurityFilterChain 해당 메소드안에 .addFilterAfter()로 추가.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Slf4j
@Configuration
@RequiredArgsConstructor
public class AngrySecurityConfiguration {

  private final CustomCorsConfig customCorsConfig;
  private final CsrfTokenLoggerFilter csrfTokenLoggerFilter;

  @Bean
  public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {

    CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
    requestHandler.setCsrfRequestAttributeName("_csrf");

    http
      .addFilterAfter(csrfTokenLoggerFilter, BasicAuthenticationFilter.class) //
      .cors((cors) -> cors.configurationSource(customCorsConfig))
      .authorizeHttpRequests(
        (requests) -> requests.anyRequest().authenticated()
      )
      .formLogin(withDefaults())
      .httpBasic(withDefaults());
    return http.build();
  }

  @Bean
  public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
    UserDetails admin = org.springframework.security.core.userdetails.User.withUsername("admin").password("12345").roles("admin").build();
    UserDetails user = org.springframework.security.core.userdetails.User.withUsername("user").password("12345").roles("user").build();
    UserDetails guest = org.springframework.security.core.userdetails.User.withUsername("guest").password("12345").roles("guest").build();
    return new InMemoryUserDetailsManager(admin, user, guest);
  }

  @Bean
  public PasswordEncoder passwordEncoder() {
    return NoOpPasswordEncoder.getInstance();   //  ToBE bcrypt
  }

}


로그 확인

csrf_token


  • 실제로 로그인 인증이 완료된 후에 값이 저장되는지 확인
    • csrf옵션을 활성화한다.
    • HttpServletRequest를 이용해 쿠키값을 확인해본다.

CsrfTokenValidFilter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Slf4j
@Component
public class CsrfTokenValidFilter implements Filter {

  @Override
  public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {

    HttpServletRequest httpServletRequest = (HttpServletRequest) request;

    Cookie[] cookies = ((HttpServletRequest) request).getCookies();

    Cookie _XSRF_TOKEN = null;
    if (cookies != null) {
      for (Cookie cookie : cookies) {
        if ("XSRF-TOKEN".equals(cookie.getName())) {
          _XSRF_TOKEN = cookie;
        }
      }
    }

    if (_XSRF_TOKEN != null) {
      log.info("XSRF-TOKEN = {}", _XSRF_TOKEN);
      log.info("XSRF-TOKEN.getName() = {}", _XSRF_TOKEN.getName());
      log.info("XSRF-TOKEN.getValue() = {}", _XSRF_TOKEN.getValue());
    }

    HttpSession session = httpServletRequest.getSession();
    Enumeration<String> attributeNames = session.getAttributeNames();

    while (attributeNames.hasMoreElements()) {
      String name = attributeNames.nextElement();
      Object attribute = session.getAttribute(name);
      log.info("attribute = {}", attribute);
    }

    filterChain.doFilter(request, response);
  }

}
  • csrfTokenLoggerFilter를 추가한것과 마찬가지로 필터 추가
    • 1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      
              http
                  .addFilterAfter(csrfTokenLoggerFilter, BasicAuthenticationFilter.class) //
                  .addFilterAfter(csrfTokenValidFilter, BasicAuthenticationFilter.class)
                  .cors((cors) -> cors.configurationSource(customCorsConfig))
                  .csrf((csrf)-> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
                  .authorizeHttpRequests(
                          (requests) -> requests.anyRequest().authenticated()
                  )
                  .formLogin(withDefaults())
                  .httpBasic(withDefaults());
      

csrf_valid

CsrfToken 설명

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public interface CsrfToken extends Serializable {

  /**
   * Gets the HTTP header that the CSRF is populated on the response and can be placed
   * on requests instead of the parameter. Cannot be null.
   * @return the HTTP header that the CSRF is populated on the response and can be
   * placed on requests instead of the parameter
   */
  String getHeaderName();

  /**
   * Gets the HTTP parameter name that should contain the token. Cannot be null.
   * @return the HTTP parameter name that should contain the token.
   */
  String getParameterName();

  /**
   * Gets the token value. Cannot be null.
   * @return the token value
   */
  String getToken();

}

This post is licensed under CC BY 4.0 by the author.