Spring/Spring Security

[Spring Security] 인증 저장(Persisting Authentication)

공대키메라 2022. 7. 21. 22:50

지난 시간에는 Password Storage의 종류에 대해 알아보았다.

(궁금하면 여기 클릭해주세요  👍👍👍👍 클릭!)

 

이번 시간에는 지속 인증(Persisting Authenticaion)에 대해 알아볼 것이다. 

 

참고한 사이트는 다음과 같다. 

 

출처:

https://docs.spring.io/spring-security/reference/servlet/authentication/persistence.html

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=moonv11&logNo=220699898987 

https://docs.spring.io/spring-security/site/docs/3.2.8.RELEASE/apidocs/org/springframework/security/web/context/SecurityContextRepository.html

https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0/unit/29843?tab=curriculum 

 

1. Persisting Authentication

처음에 사용자가 보호된 자원에 요청을 하고 credentials을 유도한다. credentials를 유도하기 위한 가장 흔한 방식중의 하나는 로그인 페이지로 사 redirect하는 것이다. 


Example 1. 인증되지 않은 사용자가 보호된 자원을 요청 (Unauthenticated User Requests Protected Resource)

GET / HTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7bHTTPCopied!

HTTP/1.1 302 Found
Location: /login

 

사용자가 username과 password를 제출한다. 

 

Example 2. 사용자 이름과 비밀번호 제출(Username and Password Submitted)

POST /loginHTTP/1.1
Host: example.com
Cookie: SESSION=91470ce0-3f3c-455b-b7ad-079b02290f7b

username=user&password=password&_csrf=35942e65-a172-4cd4-a1d4-d16a51147b3e

 

사용자를 인증하는데, 사용자는 세션 고정 공격(session fixation attack)을 막기 위해 새로운 세션 아이디와 연동된다. 

 

Example 3. 인증된 사용자가 새로운 세션과 연동됨(Authenticated User is Associated to New Session)

HTTP/1.1 302 Found
Location: /
Set-Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8; Path=/; HttpOnly; SameSite=Lax

 

연속적인 요청은  session cookie를 포함하는데, 이것은 session의 나머지 사용자를 인증하기 위해 사용된다.

 

Example 4. Credentials로 제공된 인증받은 세션(Authenticated Session Provided as Credentials)

GET / HTTP/1.1
Host: example.com
Cookie: SESSION=4c66e474-3f5a-43ed-8e48-cc1d8cb1d1c8

 

여기서 이러고 SecurityContextRepository에 대해 바로 설명을 하는데, (왜케 불친절? ㅠㅠ)

내 생각에는 이렇게 로그인을 한 후 세션 정보, 쿠키 정보 같은 것을 우리가 SecurityContext에 사용자 인증(authentication)정보를 저장하는데, 이것을 SecurityContextRepository의 구현체를 통해 읽고 다시 사용자 정보를 저장하고 하는 것 같다.

그래서 이후에 SecurityContextRespository와 연관된 class가 설명되는 것 같다. 

SecurityContextRepository

Spring Security에서 미래 요청에 대한 사용자와의 연관은 SecurityContextRepository를 사용해서 생성된다. 

 


여기서 SecurityContextRepository가 무슨 역할을 하는지 물어본다면? 공식문서에는 아쉽게도 이에 대해 설명을 자세히 해놓지는 않았다.

 

그래서 필자 키메라는 apidoc에서 이에 대한 정보를 찾고자 했다. SecurityContextRepository가 정확히 어떤 친구(절교하자)인지 궁금다옹...

 

SecurityContextRepository는 SecurityContext를 요청들 사이에서 저장하기 위해 사용되는 전략이다.


SecurityContextRepository는 3가지 메소드 loadContext, saveContext, containsContext로 구성되어 있다.

 

loadContext

뭔가 이름 그대로 저장된 context를 불러올거 같은 느낌인데...

공급된 요청으로부터 security context를 얻는다고 한다. (진짜네 ㅎㅎ...)

 

saveContext

요청의 완료에서 security context를 저장한다.

 

containsContext

현재 요청을 위한 security context를 포함하고 있는지 아닌지를 repository가 확인하도록한다.

 

요청에서 context가 발견되면 true, 아니면  false를 반환한다.

 

더 궁금하면 다음 사이트를 참고하길 바란다. (여기 클릭!)

 


HttpSecurityContextRepository

SecurityContextRepository의 기본 구현체는 HttpSession을 SecurityContext와 연관시키는HttpSessionSecurityContextRepository다. 사용자는 HttpSessionSecurityContextRepository를 SecuriyContextRepositorty의 다른 구현체로 대체가 가능하다. 

 

스프링 시큐리티는 기본적으로 HttpSessionSecurityContextRepository 를 사용하기 때문에 인증정보가 HttpSession 에 저장된다.

그런데 만약 동시접속자 수가 많은 서비스라면 세션에 인증정보를 저장하는 것은 서버에 부담을 주게 된다. 따라서 그런 경우 인증정보를 암호화하여 쿠키에 저장하거나(물론 복호화되지 않는 암호화 알고리즘을 사용하더라도 개인정보가 쿠키에 저장되는 일은 없어야 한다.)

, Redis 와 같은 cache 서버를 사용해야 하는데 그러려면 SecurityContextRepository 를 implement 하여 새로운 SecurityContextRepository 를 정의해야 한다.

출처: https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=moonv11&logNo=220699898987

 

NullSecurityContextRepository

만약 HttpSession와 SecurityContex를 연관시킬 필요가 없다면, NullSecurityContextRepository가 SecurityContextRepository의 구현체다.

 

RequestAttributeSecurityContextRepository

RequestAttributeSecurityContextRepository는 요청 속성으로서 SecurityContext를 저장하는데, SecurityContext가 지울 수 도 있는 dispatch type들 사이에서 발생하는 하나의 요청에도 사용할 수 있다는 것을 확실히 한다.

(무슨 말이야...? ㅠㅠ)

 

예를 들어, client가 만든 요청이 인증되었고 에러가 발생한다고 가정하자. servlet container 구현체에 의존해서, 에러는 생성된 SecurityContext가 지워지고 그리고 나서 error dispatch가 만들어졌다는 걸 의미한다. 

 

error dispatch가 만들어지면, SecuriyContext는 없는데, 이것은 error page가 인증 혹은 현재 사용자를 보여주기 위해 SecurityContext를 사용할 수 없다는 것이다. SecurityCOntext가 다소 지속되지 않는다면 말이다.

 

Example 5. Use RequestAttributeSecurityContextRepository

public SecurityFilterChain filterChain(HttpSecurity http) {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.securityContextRepository(new RequestAttributeSecurityContextRepository())
		);
	return http.build();
}

 

2. SecurityContextPersistenceFilter

SecurityContextPersistenceFilter는 요청들 사이에서 SecurityContextRepository를 사용해서 SecurityContext를 지속시키는데 책임이 있다. 

 

 

1. 애플리케이션을 작동시키기 전에, SecurityContextPersistenceFilter가 SecurityContextRepository로 부터 SecurityContext를 담고 SecurityContextHolder에 그것을 세팅한다. 

 

지난 글에서 Architecture에 대해 공부할 때 SecurityContextHolder에 대해 알아본 적 있다. 

SecurityContext사용자의 로그인 정보를 가지고 있는데,
이것을 또 가지고 있는 곳이 SecurityContextHolder라고 했다. 

그런데 이제보니 SecurityContextPersistenenceFilter에서 SecurityContextRespository를 이용해서 이 정보를 담아준거네!

 

2. 다음으로, 어플리케이션이 작동한다.

 

3. 마지막으로, SecurityContext가 변했다면, 우리는 SecurityContext를 SecurityContextPersistenceRepsitory를 사용해서 저장한다. SecurityContextPersistenceFilter를 사용할 때 SecurityContextHolder를 세팅하는것은 SecurityContext가 SecurityContextRepository사용하는것을 지속할 수 있다는 말이다.

3. SecurityContextHolderFilter

SecurityContextHolderFilter는 SecuriyContextRepository를 사용하는 요청들 사이에서 SecurityContext를 불러오는데 책 임이 있다.

 

 

SecurityContextHolderFilter인데 그림상에서는 SecurityContextPersistenceFilter가 적혀있네?

그리고 찾아보니 Spring 5.7.2부터는 Deprecated 되었다고하니...

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/web/context/SecurityContextPersistenceFilter.html


떡하니 SecurityContextHolderFilter를 사용하라고 적혀있다.

필자가 그나마 가장 최근 버전으로 다운받은 Spring boot는 deprecated 되고 SecurityContextHolderFilter가 적용되어있지는 않다. 

하여간 찾아보니 그렇다고 하니 알면 좋을 것 같다.

 

 

1. 어플리케이션을 작동시키기 전에, SecurityContexyHolderFilter가 SecurityContextRepository에서 SecurityContext를 불러온다.  그리고 SecurityContextHolder에 세팅한다. 

 

2. 다음으로, 어플리케이션이 작동한다. 

 

SecurityContextPersistenceFilter와 달리, SecurityContextHolderFilter는 오직 SecurityContext를 불러오고 SecurityContext를 저장하지 않는다. 

 

이것은 SecurityContextHolder를 사용할 때, SecurityContext 확실히 저장되어 있어야 한다는 말이다. 

 

Example 6. Explicit Saving of SecurityContext

public SecurityFilterChain filterChain(HttpSecurity http) {
	http
		// ...
		.securityContext((securityContext) -> securityContext
			.requireExplicitSave(true)
		);
	return http.build();
}

여기서 흐름에 대한 설명이 부족한 것 같아서 다른 곳을 참고했다.

 

인프런 정수원 강사님의 코어 스프링 시큐리티를 참고했다.

 

 

먼저 사용자가 요청을 SecurityContextPersistenceFilter에서 처리한다. 

 

그리고 내부적으로  HttpSecurityContextRepository가 있는데,

 

인증 전이면 현재 익명 사용자가 접근하면 당연 session에서는 SecurityContext가 없으니 새로운 SecurityContext를 생성한다. 

 

그리고 그 다음 필터로 넘어간다. 

 

그리고 인증 성공 후 SecurityContext에 인증 객체(Authentication) 을 저장한다. 

 

계속적으로 다음 필터로 이동 하면...

 

이게 사용자에게 응답하는 시점에 SecurityContext를 session에 저장한다. 그리고 SecuriyContextHolder에서 Securitycontext를 제거한 후 최종적으로 client에게 응답한다. 

 

인증 이후의 로직은 뭐 간단하다. 보이는 것 처럼 이해하면 된다.


다시 한 번 큰 흐름을 생각해보자. 

 

SecurityRepository를 다루기 위해서 현재 SecurityContextPersistenceFilter, SecurityContextHolderFilter가 있다.

 

SecurityContextPersistenceFilter는 SecurityContext를 SecurityContextRepository를 이용해서 불러오고 저장할 수 있다.

 

SecurityContextHolderFilter는 SecurityContext를 읽기만 한다.

 

이게 전부다!

 


이번 시간에는 어떻게 인증 정보를 저장하고, 이것이 어떤 filter에서 어떻게 저장되고 읽는지를 알아보았다.