-
SpringSecurity 04Spring&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