사용자가 보호된 리소스를 처음 요청할 때 자격 증명이 요구됩니다. 자격 증명을 요청하는 가장 일반적인 방법 중 하나는 사용자를 로그인 페이지로 리디렉션하는 것입니다. 인증되지 않은 사용자가 보호된 리소스를 요청할 때의 요약된 HTTP 교환은 다음과 같습니다.
이 예시는 인증되지 않은 사용자가 보호된 리소스에 접근하는 과정을 설명합니다. 전체 흐름을 순서대로 자세히 설명드리겠습니다.
초기 요청 - 인증되지 않은 사용자:
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7bSESSION ID가 포함되어 있지만, 현재 상태는 인증되지 않았습니다.서버 응답 - 로그인 페이지로 리디렉션:
HTTP/1.1 302 Found
Location: /login302 Found 응답 코드는 리디렉션을 의미하며, Location 헤더에 사용자가 로그인 페이지(/login)로 이동하도록 지시합니다.사용자 자격 증명 제출 - 로그인 요청:
사용자는 POST 요청을 통해 사용자 이름과 비밀번호를 제출하여 로그인합니다. 이 요청은 인증을 위해 서버에 전송됩니다.
예시 요청:
POST /login HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e
요청 본문에는 username(사용자 이름)과 password(비밀번호)가 포함되어 있으며, _csrf 토큰이 포함됩니다. CSRF 토큰은 보안 강화를 위한 추가 요소입니다.
서버 응답 - 새로운 세션 ID 할당:
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=LaxSESSION 쿠키가 할당되며, 이제 사용자는 인증된 상태로 보호된 리소스에 접근할 수 있습니다. HttpOnly와 SameSite 속성은 보안을 더욱 강화합니다.후속 요청 - 인증된 세션 사용:
SESSION 쿠키를 사용하여 인증된 상태를 유지할 수 있습니다.GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8SESSION 쿠키가 포함되어 있는 이 요청은 서버에서 인증된 상태로 처리되며, 사용자는 추가 인증 절차 없이도 보호된 리소스에 접근할 수 있습니다.이렇게 함으로써, 사용자는 한 번 로그인한 후 세션이 지속되는 동안 추가적인 인증 없이 보호된 리소스에 접근할 수 있게 됩니다.
이 부분에서는 Spring Security에서 SecurityContextRepository가 사용자의 인증 상태를 이후 요청에서도 유지하는 방식을 설명하고 있습니다. 자세히 설명하겠습니다.
SecurityContextRepository란?
SecurityContextRepository는 인증된 사용자의 정보를 저장하고, 이후 요청에서 해당 정보를 다시 사용할 수 있도록 도와주는 역할을 합니다.DelegatingSecurityContextRepository (위임 보안 컨텍스트 저장소)
SecurityContextRepository의 기본 구현체는 DelegatingSecurityContextRepository입니다.HttpSessionSecurityContextRepository (세션 보안 컨텍스트 저장소)
HttpSessionSecurityContextRepository는 인증된 사용자 정보를 HttpSession에 저장합니다.RequestAttributeSecurityContextRepository (요청 속성 보안 컨텍스트 저장소)
RequestAttributeSecurityContextRepository는 인증 정보를 특정 요청 내에서만 사용하는 방식으로 저장합니다.이 부분은 Spring Security에서 요청과 관련된 인증 정보를 어떻게 유지할지 결정하는 SecurityContextRepository의 다양한 구현체를 설명하고 있습니다. 각 구현체의 역할을 순서대로 자세히 설명하겠습니다.
HttpSessionSecurityContextRepository는 사용자의 인증 정보를 세션(HttpSession)에 저장합니다.SecurityContext(즉, 인증된 사용자 정보)를 세션에 저장하여 여러 요청에 걸쳐 인증 상태를 유지할 수 있도록 합니다.HttpSessionSecurityContextRepository를 다른 SecurityContextRepository 구현체로 변경하여 다른 방식으로 사용자 인증 상태를 유지할 수도 있습니다.NullSecurityContextRepository는 인증 정보를 저장하지 않으며, 아무 작업도 하지 않는 SecurityContextRepository입니다.SecurityContext를 HttpSession에 저장하지 않으므로, 인증 상태가 지속되지 않습니다. 사용자 인증이 단일 요청에만 적용되고, 이후 요청에서는 인증 상태가 유지되지 않습니다.RequestAttributeSecurityContextRepository는 인증 정보를 요청 속성에 저장합니다. 이는 특정 요청 내에서만 인증 정보를 유지하게 하며, 요청이 끝나면 인증 정보도 사라집니다.SecurityContext를 유지하며, 이는 주로 특정 디스패치 흐름(예: 오류 발생 시 재디스패치)에 걸쳐 인증 정보를 유지하고자 할 때 유용합니다.SecurityContext를 초기화하고 새로운 오류 디스패치를 수행합니다. 이 경우, 오류 페이지에 도달했을 때는 설정된 인증 정보가 사라져 현재 사용자 정보를 활용할 수 없습니다.RequestAttributeSecurityContextRepository는 이러한 상황에서 오류 디스패치 중에도 SecurityContext가 유지되도록 도와줍니다. 오류 페이지에서도 인증 정보를 참조하여 권한을 부여하거나 사용자 정보를 표시할 수 있게 됩니다.public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
);
return http.build();
}
해석:
이 코드는 Spring Security에서 RequestAttributeSecurityContextRepository를 사용하도록 SecurityFilterChain을 설정하는 예시입니다.
filterChain(HttpSecurity http): 이 메서드는 HttpSecurity 객체를 받아 보안 필터 체인을 설정하고, 설정이 완료된 필터 체인을 반환합니다..securityContext((securityContext) -> securityContext.securityContextRepository(new RequestAttributeSecurityContextRepository())): 이 부분은 SecurityContext를 설정하면서, RequestAttributeSecurityContextRepository를 사용해 인증 정보를 요청 속성에 저장하도록 설정합니다. RequestAttributeSecurityContextRepository를 사용하면 인증 정보가 단일 요청 내에서만 유지됩니다.return http.build();: 설정된 보안 필터 체인을 반환합니다.@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
))
);
return http.build();
}
해석:
DelegatingSecurityContextRepository는 SecurityContext를 여러 SecurityContextRepository에 저장하고, 지정된 순서대로 각 저장소에서 인증 정보를 가져올 수 있도록 합니다.RequestAttributeSecurityContextRepository와 HttpSessionSecurityContextRepository를 동시에 사용하는 설정 예시입니다.RequestAttributeSecurityContextRepository는 요청 내에서만 인증 정보를 유지하는 반면, HttpSessionSecurityContextRepository는 세션을 통해 인증 상태를 여러 요청에 걸쳐 유지합니다.
SecurityContextPersistenceFilter는 Spring Security에서 중요한 역할을 하며, 요청 간에 사용자의 인증 상태(즉, SecurityContext)가 유지되도록 보장하는 필터입니다. 각 단계별로 자세히 설명드리겠습니다.
SecurityContextPersistenceFilter는 애플리케이션의 나머지 부분이 실행되기 전에 SecurityContextRepository에서 SecurityContext를 로드하여 SecurityContextHolder에 설정합니다.SecurityContextHolder에 인증 정보가 자동으로 설정되어, 이후 애플리케이션의 요청 처리 과정에서 이 인증 정보를 사용할 수 있게 됩니다.SecurityContextHolder에 설정된 후, 애플리케이션의 나머지 로직이 실행됩니다.애플리케이션 로직이 실행된 후, SecurityContextPersistenceFilter는 요청이 종료되기 전에 SecurityContext에 변경이 있었는지 확인합니다.
변경이 확인되면, SecurityContextRepository를 통해 SecurityContext가 저장됩니다. 이렇게 하면 이후 요청에서도 사용자의 인증 상태가 유지됩니다.
예를 들어, 사용자가 로그인한 후 SecurityContext에 변경이 생겼다면, 이 변경이 SecurityContextRepository에 저장되어 이후 요청에서 다시 사용될 수 있도록 합니다.
일부 경우에서는 SecurityContextPersistenceFilter가 완료되기 전에 응답이 클라이언트로 전송되어 문제가 발생할 수 있습니다.
예시 1: 리디렉션 응답
SecurityContext를 설정할 수 없습니다.예시 2: 빠른 두 번째 요청
SecurityContextPersistenceFilter는 HttpServletRequest와 HttpServletResponse를 모두 래핑합니다.SecurityContext가 변경되었는지 여부를 감지하고, 변경이 확인되면 응답이 커밋되기 직전에 SecurityContext를 저장합니다.요약: SecurityContextPersistenceFilter는 SecurityContextRepository와 함께 요청 간 인증 정보를 안전하게 유지하고, 불완전한 응답 전송으로 인한 인증 정보 손실을 방지합니다.

SecurityContextHolderFilter는 SecurityContextPersistenceFilter와 비슷한 역할을 하지만 몇 가지 중요한 차이점이 있습니다. 이 차이와 SecurityContextHolderFilter의 동작 방식을 순서대로 설명드리겠습니다.
SecurityContextHolderFilter는 SecurityContextRepository에서 인증 정보(SecurityContext)를 불러와 SecurityContextHolder에 설정합니다.SecurityContextHolderFilter는 요청마다 SecurityContext를 로드하지만, 이 필터는 요청이 끝난 후 인증 상태를 저장하지 않습니다. 따라서 인증 정보를 유지하려면, 인증 정보를 명시적으로 저장하는 추가 작업이 필요합니다.SecurityContextHolderFilter의 동작 방식은 크게 세 단계로 요약할 수 있습니다:
SecurityContextHolderFilter는 SecurityContextRepository에서 SecurityContext를 로드하여 SecurityContextHolder에 설정합니다.SecurityContext 정보가 SecurityContextHolder에 설정된 상태에서 애플리케이션 로직이 실행됩니다.SecurityContextPersistenceFilter와 달리, SecurityContextHolderFilter는 변경된 SecurityContext를 자동으로 저장하지 않기 때문에, 필요한 경우 개발자가 이를 명시적으로 저장해야 합니다.이 필터를 사용할 때는 SecurityContext가 변경되면 반드시 이를 명시적으로 저장해야 합니다. Spring Security 설정에서는 다음과 같이 .requireExplicitSave(true) 옵션을 사용하여 명시적 저장을 요구하도록 설정할 수 있습니다:
public SecurityFilterChain filterChain(HttpSecurity http) {
http
.securityContext((securityContext) -> securityContext
.requireExplicitSave(true)
);
return http.build();
}
이 설정은 SecurityContextHolder에 SecurityContext를 설정하는 코드가 있을 때, SecurityContextRepository에 이 정보를 명시적으로 저장하도록 강제합니다.
SecurityContextHolderFilter를 사용할 때는 SecurityContextHolder에 SecurityContext를 설정한 후, SecurityContextRepository를 통해 SecurityContext를 명시적으로 저장해야 합니다.
잘못된 예시:
SecurityContextHolder.setContext(securityContext);
이 코드는 SecurityContextHolder에 securityContext를 설정하지만, SecurityContext가 이후 요청에 자동으로 저장되지 않기 때문에 상태가 유지되지 않습니다.
올바른 예시:
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
여기서는 SecurityContextHolder에 securityContext를 설정한 뒤, securityContextRepository.saveContext 메서드를 호출하여 인증 정보를 명시적으로 저장합니다. 이렇게 해야 이후 요청에서도 인증 상태가 일관되게 유지됩니다.
SecurityContextHolderFilter는 SecurityContext를 로드하는 역할만 하며, 자동으로 저장하지 않기 때문에 명시적으로 저장해야 합니다..requireExplicitSave(true) 옵션을 사용하면, SecurityContextHolder에 SecurityContext를 설정하는 경우 반드시 SecurityContextRepository를 통해 저장하도록 강제됩니다.