ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • SpringSecurity 04
    Spring&SpringBoot/SpringSecurity 2025. 9. 18. 14:34

    예외처리

    예외처리 - exceptionHandling()

    • 예외 처리는 필터 체인 내에서 발생하는 예외를 의미하며 인증예외/인가예외가 있다
    • 예외를 처리하는 필터는 ExceptionTranslationFilter가 사용되며 사용자의 인증 및 인가 상태에 따라 로그인 재시도, 401, 403 코드 등으로 응답

     

     

    [예외처리 유형]

    • AuthenticationExcepion
      • SecurityContext 인증 정보 삭제
      • AuthenticationEntryPoint 호출 - 인증 시도 화면으로 이동
      • 인증 프로세스의 요청 정보를 저장하고 검색
    • AccessDeniedException
      • AccessDeniedException 감지되면 필터는 사용자가 익명 사용자인지 여부 판단하고 익명 사용자인 경우 인증예외처리가 실행되고 익명 사용자가 아닌 경우 AccessDeniedHandler에게 위임

     

     

    @RestController
    @RequiredArgsConstructor
    public class IndexController {
        ...
        
        @GetMapping("/login")
        public String login() {
            return "login";
        }
        
        @GetMapping("/denied")
        public String denied() {
            return "denied";
        }
    }
    @EnableWebSecurity
    @Configuration
    public class SecurityConfig {
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.authorizeHttpRequests(auth -> auth
                            .requestMatchers("/login").permitAll()
                            .requestMatchers("/adming").hasRole("ADMIN")
                            .anyRequest().authenticated())
                    .formLogin(Customizer.withDefaults())
                    .exceptionHandling(exception -> exception
                            .authenticationEntryPoint(new AuthenticationEntryPoint() {
                                @Override
                                public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
                                    System.out.println("exception: " + authException.getMessage());
                                    response.sendRedirect("/login");
                                }
                            })
                            .accessDeniedHandler(new AccessDeniedHandler() {
                                @Override
                                public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
                                    System.out.println("exception: " + accessDeniedException.getMessage());
                                    response.sendRedirect("/denied");
                                }
                            }))
            ;
            return http.build();
        }
    }

     

     

     

    예외필터 - ExceptionTranslationFilter

    • ExceptionTranslationFilter는 인가 처리 담당하는 AuthorizationFilter에서 발생하는 예외 처리

     

     

     

     

     

    악용보호

    CORS(Cross Origin Resource Sharing, 교차 출처 리소스 공유)

    • 웹에서는 보안을 위해 기본적으로 한 웹 페이지에서 다른 웹 페이지의 데이터를 직접 불러오는 것을 제한하는데 이를 동일 출처 정책(Same-Origin Policy)이라고 한다
    • CORS는 특별한 HTTP 헤더를 통해 한 웹페이지가 다른 출처의 리소스에 접근할 수 있도록 허가를 구하는 방법
    • 출처를 비교하는 로직은 서버에 구현된 스펙이 아닌 브라우저에 구현된 스펙 기준으로 처리되며 브라우저는 클라이언트의 요청 헤어오 서버의 응답헤더를 비교해 최종 응답한다
    • 두 개의 출처 비교 방법은 URL 구성 요소 중 Protocol, Host, Port 세 가지가 동일한지 확인하면 된다

     

     

    CORS 종류

    1. Simple Request

    • 예비 요청 과정 없이 자동으로 CORS가 작동하여 서버에 본 요청을 한 후 서버가 응답 헤더에 Access-Control-Allow-Origin과 같은 값을 전송하면 브라우저가 서로 비교 후 CORS 정책 위반여부를 검사하는 방식
    • 제약 사항
      • GET, POST, HEAD 중 한 가지 Method 사용
      • 헤더는 Accept, Accept-Language, Contnet-Language, Content-Type, DDR, Downlink, Viewport-Width Width만 가능하고 Custom Header는 허용하지 않는다
      • Content-tyoe은 application/x-www-form-urlencoded, multipart/form-data, text/plain만 허용

     

     

    2. Preflight Request

    • 브라우저는 요청을 한 번에 보내지 않고 예비 요청, 본 요청으로 나누어 서버에 전달하는데 브라우저가 예비 요청을 보내는 것을 Preflight라고 하며 이 예비 요청의 메서드에는 OPTIONS가 사용된다
    • 예비 요청의 역할은 본 요청을 보내기 전에 브라우저 스스로 안전한 요청인지 확인하는 것으로 요청 사양이 Simple Request에 해당하지 않을 경우 브라우저가 Preflight Request를 실행

     

     

    [동일 출처 기준]

     

     

    CORS 해결 -서버에서 Access-Control-Allow-* 세팅 

    • Access-Control-Allow-Origin - 헤더에 작성된 출처만 브라우저가 리소스를 접근할 수 있도록 허용
    • Access-Control-Allow-Methods - preflight request에 대한 응답으로 실제 요청 중에 사용할 수 있는 메서드
      • 기본값은GET,POST,HEAD,OPTIONS , * •
    • Access-Control-Allow-Headers - preflight request에 대한 응답으로 실제 요청 중에 사용할 수 있는 헤더 필드 이름
      • 본값은 Origin,Accept,X-Requested-With,Content-Type, Access-Control-Request-Method,Access-Control-Request-Headers, Custom Header , * •
    • Access-Control-Allow-Credentials - 제 요청에 쿠기나 인증 등의 사용자 자격 증명이 포함될 수 있음
      • Client의credentials:include 옵션일 경우 true는 필수
    • Access-Control-Max-Age - preflight 요청 결과를 캐시 할 수 있는 시간을 나타내는 것으로 해당 시간 동안은 preflight 요청을 다시 하지 않게 됨

     

     

    cors() & CorsFilter

    • CORS 사전 요청에는 쿠키(JSESSION)가 포함되어 있지 않기 때문에 Spring Security 이전에 처리되어야 함
    • 사전 요청에 쿠키가 없고 Spring Sequrity가 먼저 처리되면 요청은 사용자가 인증되지 않았다고 판단하고 거부할 수 있음
    • DORS가 먼저 처리되도록 하기 위해서 CorsFilter를 사용할 수 있고 CorsFilter에 CorsConfigurationSource를 제공함으로써 Spring Security와 통합 가능
    • CorsConfigurationSource를 설정하지 않으면 Spring MVC의 CORS 구성을 사용

     

     

    @EnableWebSecurity
    @Configuration
    public class SecurityConfig {
        @Bean
        public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            http.authorizeHttpRequests(auth -> auth
                            .requestMatchers("/login").permitAll()
                            .requestMatchers("/adming").hasRole("ADMIN")
                            .anyRequest().authenticated())
                    .formLogin(Customizer.withDefaults())
                    .cors(cors -> cors.configurationSource(corsConfigurationSource()))
            ;
            return http.build();
        }
    
        @Bean
        public CorsConfigurationSource corsConfigurationSource() {
            CorsConfiguration configuration = new CorsConfiguration();
            configuration.addAllowedOrigin("http://localhost:8080");
            configuration.addAllowedHeader("*");
            configuration.addAllowedHeader("*");
            configuration.setAllowCredentials(true);
            configuration.setMaxAge(36000L);
    
            UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
            source.registerCorsConfiguration("/**", configuration);
    
            return source;
        }
    }

     

     

    CSRF (Cross Site Request Forgery, 사이트 간 요청 위조)

    • 웹 애플리케이션의 보안 취약점으로 공격자가 사용자로 하여금 이미 인증된 다른 사이트에 대해 원치 않는 작업을 수행하게 만드는 기법
    • 이 공격은 사용자의 브라우저가 자동으로 보낼 수 있는 인증 정보(쿠키, 인증 세션)를 이용하여 사용자가 의도하지 않는 요청을 서버로 전송
    • 이는 사용자가 로그인한 상태에서 악의적인 웹 사이트를 방문하거나 이메일 등을 통해 악의적인 링크를 클릭할 때 발생

     

     

    CSRF 기능 활성화

    • 토큰이 서버에 의해 생성되어 클라이언트 세션에 저장되고 폼을 통해 서버로 전송되는 모든 변경 요청에 포함되어야 하며 서버는 이 토큰을 검증하여 요청의 유효성 확인
    • 기본 설정은 GET, HEAD, TRACE, OPTIONS와 같은 안전한 메서드를 무시하고 POST, PUT, DELETE와 같은 변경 요청 메서드에서만 CSRF 토큰 검사를 수행
    • CSRF 토큰이 브라우저에 의해 자동으로 포함되지 않는 요청 부분에 위치해야 하며 HTTP 매개변수나 헤더에 실제 CSRF 토큰을 요구하는 것이 CSRF 공격을 방지하는데 효과적이라고 할 수 있다
    • 쿠키에 토큰을 요구하는 것은 브라우저가 쿠키를 자동으로 요청에 포함시키기 때문에 효과적이지 않다

     

     

    CSRF 토큰 유지 및 검증

    CSRF 토큰 유지 - CsrfTokenRepository

    • CsrfToken은 CsrfTokenRepository를 사용하여 영속화하며 HttpSessionCsrfTokenRepository와 CookieCsrfTokenRepository를 지원한다

    • 두 군데 중 원하는 위치에 토큰을 저장하도록 설정을 통해 지정할 수 있다

     

     

    1. 세션에 토큰 저장 - HttpSessionCsrfTokenRepository (기본값)

    • 토큰을 세션에 저장하기 위해 HttpSessionCsrfTokenRepository를 사용한다

    • HttpSessionCsrfTokenRepository는 기본적으로 HTTP 요청 헤더인 X-CSRF-TOKEN 또는 요청 매개변수인 _csrf에서 토큰을 읽는다

     

     

    2. 쿠키에 토큰 저장 - CookieCsrfTokenRepository

    • JavaScript 기반 애플리케이션을 지원하기 위해 CsrfToken을 쿠키에 유지할 수 있으며 구현체로 CookieCsrfTokenRepository를 사용할 수 있다

    • CookieCsrfTokenRepository는 기본적으로 XSRF-TOKEN 명을 가진 쿠키에 작성하고 HTTP 요청 헤더인 X-XSRF-TOKEN 또는 요청 매개변수인 _csrf에서 읽는다

    • JavaScript 에서 쿠키를 읽을 수 있도록 HttpOnly를 명시적으로 false로 설정할 수 있다

    • JavaScript로 직접 쿠키를 읽을 필요가 없는 경우 보안을 개선하기 위해 HttpOnly를 생략하는 것이 좋다

     

     

    CSRF 토큰 처리 CsrfTokenRequestHandler

    • CsrfToken 은 CsrfTokenRequestHandler를 사용하여 토큰을 생성 및 응답하고 HTTP 헤더 또는 요청 매개변수로부터 토큰의 유효성을 검증하도록 한다

    • XorCsrfTokenRequestAttributeHandler와 CsrfTokenRequestAttributeHandler를 제공하며 사용자 정의 핸들러를 구현할 수 있다

    • “_csrf ” 및 CsrfToken.class.getName() 명으로 HttpServletRequest 속성에 CsrfToken을 저장하며 HttpServletRequest 으로부터 CsrfToken 을 꺼내어 참조할 수 있다

    • 토큰 값을 요청 헤더 (기본적으로 X-CSRF-TOKEN 또는 X-XSRF-TOKEN 중 하나) 또는 요청 매개변수 (_csrf) 중 하나로부터 토큰의 유효성 비교 및 검증을 해결한다

    • 클라이언트의 매 요청마다 CSRF 토큰 값(UUID)에 난수를 인코딩하여 변경한 CsrfToken 이반환 되도록 보장한다. 세션에 저장된 원본 토큰 값은 그대로 유지한다

    • 헤더 값 또는 요청 매개변수로 전달된 인코딩 된 토큰은 원본 토큰을 얻기 위해 디코딩되며, 그런 다음 세션 혹은 쿠키에 저장된 영구적인 CsrfToken과 비교된다

     

     

    CSRF 토큰 지연 로딩

      기본적으로 Spring Security는 CsrfToken을 필요할 때까지 로딩을 지연시키는 전략을 사용한다. 그러므로 CsrfToken 은 HttpSession 에 저장되어 있기 때문에 매 요청마다 세션으로부터 CsrfToken 을 로드할 필요가 없어져 성능을 향상할 수 있다

    • CsrfToken 은 POST와 같은 안전하지 않은 HTTP 메서드를 사용하여 요청이 발생할 때와 CSRF 토큰을 응답에 렌더링 하는 모든 요청에서 필요하기 때문에 그 외 요청에는 지연로딩 하는 것이 권장된다

     

     

    CSRF 통합

    • CSRF 공격을 방지하기 위한 토큰 패턴을 사용하려면 CSRF 토큰을 HTTP 요청에 포함해야 함
    • 그래서 브라우저에 의해 HTTP 요청에 자동으로 포함되지 않는 요청 부분(폼 매개변수, HTTP 헤더 또는 기타 부분) 중 하나에 포함되어야 함

     

     

    1. HTML Forms

    • HTML 폼을 서버에 제출하려면 CSRF 토큰을 hidden 값으로 Form에 포함해야 함
    • 폼에 실제 CSRF 토큰을 자동으로 생성하는 뷰
      • Thymeleaf
      • 스프링의 폼 태그 라이브러리 사용

     

     

    2. JavaScript Applications

    2-1. single page application

    • CookieCsrfTokenRepository.withHttpOnlyFalse 를 사용해서 클라이언트가 서버가 발행한 쿠키로 부터 CSRF 토큰을 읽을 수 있도록 한다
    • 사용자 정의 CsrfTokenRequestHandler 을 만들어 클라이언트가 요청 헤더나 요청 파라미터로 CSRF 토큰을 제출할 경우 이를 검증하도록 구현한다
    • 클라이언트의 요청에 대해 CSRF 토큰을 쿠키에 렌더링해서 응답할 수 있도록 필터를 구현한다

     

     

    2-2. multi page application

    • JavaScript 가 각 페이지에서 로드되는 멀티 페이지 애플리케이션의 경우 CSRF 토큰을 쿠키에 노출시키는 대신HTML 메타 태그 내에 CSRF 토큰을 포함시킬 수 있다
    • HTML 메타 태그에 CSRF 토큰 포함
    • AJAX 요청에서 CSRF 토큰 포함

     

     

     

     

    SameSite

     

     

     

     

     

     

     

    728x90

    'Spring&SpringBoot > SpringSecurity' 카테고리의 다른 글

    SpringSecurity 03  (0) 2025.09.17
    SpringSecurity 02  (0) 2025.09.16
    SpringSecurity 01  (0) 2025.09.15
Designed by Tistory.