Spring Security ‐ Persistence - taeyun-ham/andalos GitHub Wiki
Persisting Authentication
사용자가 보호된 리소스를 처음 요청할 때, 자격 증명을 입력하라는 메시지가 표시됩니다. 자격 증명을 요청하는 가장 일반적인 방법 중 하나는 사용자를 로그인 페이지로 리디렉션하는 것입니다. 인증되지 않은 사용자가 보호된 리소스를 요청할 때 HTTP 교환의 요약은 다음과 같을 수 있습니다:
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b
HTTP/1.1 302 Found
Location: /login
사용자가 자신의 사용자 이름과 비밀번호를 제출합니다.
사용자 이름과 비밀번호가 제출되었습니다.
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
인증된 사용자가 새 세션에 연결됩니다.
HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax
이후 요청에는 세션의 나머지 부분에 대해 사용자를 인증하는 데 사용되는 세션 쿠키가 포함됩니다.
인증된 세션을 자격 증명으로 제공합니다.
GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8
SecurityContextRepository
스프링 시큐리티에서는 사용자를 향후 요청에 연결하는 작업이 SecurityContextRepository를 사용하여 이루어집니다. SecurityContextRepository의 기본 구현은 DelegatingSecurityContextRepository이며, 다음에 위임합니다:
- HttpSessionSecurityContextRepository
- RequestAttributeSecurityContextRepository
HttpSessionSecurityContextRepository
HttpSessionSecurityContextRepository는 SecurityContext를 HttpSession에 연결합니다. 사용자는 HttpSessionSecurityContextRepository를 다른 SecurityContextRepository 구현으로 대체하여, 다른 방식으로 또는 전혀 연결하지 않고 사용자를 후속 요청과 연결하길 원할 경우 이를 대체할 수 있습니다.
NullSecurityContextRepository
SecurityContext를 HttpSession에 연결하는 것이 바람직하지 않은 경우(예: OAuth를 사용하여 인증할 때), NullSecurityContextRepository는 아무 것도 하지 않는 SecurityContextRepository의 구현입니다.
RequestAttributeSecurityContextRepository
RequestAttributeSecurityContextRepository는 SecurityContext를 요청 속성으로 저장하여, SecurityContext가 제거될 수 있는 여러 디스패치 유형에 걸쳐 발생하는 단일 요청에 대해 SecurityContext가 사용 가능하도록 합니다.
예를 들어, 클라이언트가 요청을 하고 인증된 다음 오류가 발생한다고 가정해 보겠습니다. 서블릿 컨테이너 구현에 따라, 오류는 설정된 SecurityContext가 제거된 다음 오류 디스패치가 이루어진다는 것을 의미합니다. 오류 디스패치가 이루어질 때 SecurityContext가 설정되어 있지 않습니다. 이는 오류 페이지가 SecurityContext를 인증이나 현재 사용자를 표시하는 데 사용할 수 없음을 의미합니다. 이를 위해서는 SecurityContext가 어떤 식으로든 유지되어야 합니다.
RequestAttributeSecurityContextRepository 사용하기
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new RequestAttributeSecurityContextRepository())
);
return http.build();
}
DelegatingSecurityContextRepository
DelegatingSecurityContextRepository는 SecurityContext를 여러 SecurityContextRepository 대리자에 저장하고 지정된 순서대로 어느 대리자에서든 검색을 허용합니다.
이에 대한 가장 유용한 배열은 다음 예제로 구성되며, RequestAttributeSecurityContextRepository와 HttpSessionSecurityContextRepository를 동시에 사용할 수 있게 합니다.
DelegatingSecurityContextRepository 구성하기
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.securityContext((securityContext) -> securityContext
.securityContextRepository(new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(),
new HttpSessionSecurityContextRepository()
))
);
return http.build();
}
Spring Security 6에서는 위에 표시된 예가 기본 구성입니다.
SecurityContextPersistenceFilter
SecurityContextPersistenceFilter는 SecurityContextRepository를 사용하여 요청 사이에 SecurityContext를 유지하는 책임이 있습니다.
- 애플리케이션의 나머지 부분을 실행하기 전에, SecurityContextPersistenceFilter는 SecurityContextRepository에서 SecurityContext를 로드하여 SecurityContextHolder에 설정합니다.
- 다음으로, 애플리케이션이 실행됩니다.
- 마지막으로, SecurityContext가 변경된 경우, SecurityContextPersistenceRepository를 사용하여 SecurityContext를 저장합니다. 이는 SecurityContextPersistenceFilter를 사용할 때 SecurityContextHolder를 설정하기만 하면 SecurityContextRepository를 사용하여 SecurityContext가 유지된다는 것을 의미합니다.
일부 경우에는 SecurityContextPersistenceFilter 메소드가 완료되기 전에 응답이 커밋되고 클라이언트에게 쓰여집니다. 예를 들어, 클라이언트에게 리디렉트가 전송되면 응답이 즉시 클라이언트에게 다시 쓰여집니다. 이는 3단계에서 HttpSession을 설정하는 것이 불가능하다는 것을 의미합니다. 왜냐하면 세션 ID를 이미 쓰여진 응답에 포함시킬 수 없기 때문입니다. 또 다른 상황은 클라이언트가 성공적으로 인증을 받고 SecurityContextPersistenceFilter가 완료되기 전에 응답이 커밋되며, 클라이언트가 SecurityContextPersistenceFilter가 완료되기 전에 두 번째 요청을 하는 경우, 두 번째 요청에서 잘못된 인증이 존재할 수 있습니다.
이러한 문제를 피하기 위해, SecurityContextPersistenceFilter는 SecurityContext가 변경되었는지 감지하고 변경된 경우 응답이 커밋되기 직전에 SecurityContext를 저장하기 위해 HttpServletRequest와 HttpServletResponse를 모두 래핑합니다.
SecurityContextHolderFilter
SecurityContextHolderFilter는 SecurityContextRepository를 사용하여 요청 사이의 SecurityContext를 로드하는 책임이 있습니다.
-
애플리케이션의 나머지 부분을 실행하기 전에, SecurityContextHolderFilter는 SecurityContextRepository에서 SecurityContext를 로드하여 SecurityContextHolder에 설정합니다.
-
다음으로, 애플리케이션이 실행됩니다.
SecurityContextPersistenceFilter와 달리, SecurityContextHolderFilter는 SecurityContext를 로드하기만 하고, SecurityContext를 저장하지 않습니다. 이는 SecurityContextHolderFilter를 사용할 때 SecurityContext가 명시적으로 저장되어야 함을 의미합니다.
SecurityContext의 명시적 저장
public SecurityFilterChain filterChain(HttpSecurity http) {
http
// ...
.securityContext((securityContext) -> securityContext
.requireExplicitSave(true)
);
return http.build();
}
구성을 사용할 때, 요청 사이에 유지되어야 할 경우 SecurityContextHolder를 SecurityContext로 설정하는 모든 코드가 SecurityContext를 SecurityContextRepository에도 저장해야 한다는 점이 중요합니다.
예를 들어, 다음과 같은 코드가 있습니다:
SecurityContextPersistenceFilter를 사용하여 SecurityContextHolder에 SecurityContext 설정하기
SecurityContextHolder.setContext(securityContext);
다음으로 교체되어야 합니다:
SecurityContextHolderFilter를 사용하여 SecurityContextHolder에 SecurityContext 설정하기
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);