2023년 1월 1일
08:00 AM
Buffering ...

최근 글 👑

6장 스프링 시큐리티 Authentication,SecurityContextHolder 인증흐름

2023. 12. 28. 00:24ㆍ스프링 시큐리티 -세션/Chatper02 스프링 시큐리티 인증 흐름

 

 

 

 

[Authentication]

 

스프링 시큐리티에서 가장 중요한 클래스 중 하나라고 해도 무방한 Authentication 인터페이스에 대해

알아보도록 한다.

 

 

 

1. Authentication 인터페이스는 사용자의 인증정보를 나타내는 핵심적인 인터페이스이다.

 

이 인터페이스는 사용자의 인증과정을 거쳐 스프링 시큐리티에 의해 인증이 되었을 때

사용자의 인증정보와 권한을 나타내는데 사용된다.

 

 

 

 

 

 

대충 흐름을 보자면 이러한 느낌인데

 

인증과정에 성공적으로 등록이 된다면

 

Authentication는 이러한 구성요소를 가지게 된다.

 

principal : 사용자 아이디혹은 사용자의 객체를 저장한다.

 

Credentials : password를 저장한다. 

 

authorities : 사용자의 권한을 설정한다(has_Role)을 사용 할시의 권한

 

Authenticated : 사용자가 인증된 사용자인지 아닌지 bool 타입으로 정의한다.

인증의 정상적으로 된다면 true , 아니면 false를 갖게되는데 false일 경우

자격증명 에러를 터뜨리게 된다.

 

그리고 여기서부터 핵심적인 내용인데

Authentication의 객체가 인증되었을 때 SecurityContext의 객체의 저장이 된다.

그리고 이 SecuirtyContext를 감싸는 객체SecuirtyContextHolder이다.

 

이때부터 이제 이 Authentication의 인터페이스를 전역적으로 사용할 수 있다.

서버가 꺼지지 않는한 해당 사용자의 인증정보는 Authentication에 들어가있고

 

Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

 

이렇게 SecuirtyContextHolder로 꺼내면 사용자의 인증정보를 로드할 수 있다.

 

여기서 이러한 의문이 들 것이다.

왜 SecuirtyContext 객체를 SecuirtyContextHolder가 감싸는 이유가 뭘까?

이유는 여러가지로 중요한데

 

1.쓰레드별 저장소 

SecurityContextHolder는 SecurityContext를 현재 실행중인 쓰레드에 저장한다.

이것은 TheardLocal 매커니즘을 사용하여 , 각 쓰레드마다 독립적인

SecurityContext 인스턴스 가질 수 있게 해준다.

 

즉 각 클라이언트마다 각각의 유저 정보를 독립적으로 가질 수 있게끔 해주어

각 유저의 정보를 저장할 수 있게 만들어준다는 뜻이다.

 

 

2.전역 접근성 

 

위에서 말했듯이 Authentication에서 인증정보를 받고 SecurityContext객체에 저장하는데

이 저장한 정보를 전역적으로 사용할 수 있다.

즉 SecurityContextHolder를 통해 언제 어디서나 사용자의 정보를 찾을 수 있다.

이는 사용자의 인증정보를 확인하는데 필수적인 요소를 한다.

 

3.유연한 저장 전략

 

SecurityContextHolder는 다양한 전략을 지원하는데

총 3가지 전략을 지원하게된다.

 

MODE_THREADLOCAL (기본 설정): 이 전략을 각 스레드 독립적인 

SecurityContext를 유지할 수 있게 해준다.

ThreadLocal 저장소를 사용하는데 쉽게 생각해서 각 클라이언트의

유저 정보들을 Authentication이 독립적으로 유지시켜준다.

이런 전략을 짜게되면 유저 하나하나의 정보가 SecurityContext에 저장되어 있다.

 

 

MODE_INHERITABLETHREADLOCAL :

 

이 전략은 SecurityContext의 쓰레드 (부모) 쓰레드를 자식 쓰레드에게 상속 해줄 수 있다.

쉽게 생각해서 쓰레드의 개념을 알아야한다.

쓰레드의 개념을 안다는 가정하에

 

멀티 스레드의 경우

예 스레드 1번)

   스레드 2번)

일 경우 2번에서 SecurityContext 1번의 SecurityContext로 유저 정보를 꺼낼려고 하면

스레드가 다르기 때문에 꺼낼 수 없다.

 

반대로 2번 스레드의 유저 정보가 SecurityContext에 저장되어있어

1번 스레드에서 SecurityContext로 유저정보를 꺼낼려고해도 스레드가 다르기 때문에

유저의 정보를 꺼낼 수 없다.

그렇기 때문에 이 전략은 부모 스레드가 각 자식 스레드를 상속하게 끔 해주어

스레드가 다르더라도 유저의 정보를 일관되게 꺼낼 수 있게 해준다.

 

이 전략은 보통 비동기화 방식에 사용된다.

 

MODE_GLOBAL : 이 전략은 애플리케이션 내에 모든 스레드가 단일 전

SecurityContext를 공유하도록 한다.

즉 스태틱변수로 놔두고 사용하는데 보통은 권장하지 않는 방법이다.

 

 

사용방법은 시큐리티 설정에서

 

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL);

 

내가 원하는 전략을 넣어주면 된다.

대부분에 웹에서는 기본전략을 사용하기 때문에 꼭 필요한 경우 상태전략을 바꾸도록 하자.

 

 

 

[SecurityContextHolder란?]

 

 

 

위에서 대충 설명을 했기에 대략적으로 그림으로 보도록 하자.

 

 

 

 

마찬가지로 이런식으로 그림으로 흐름이 흘러가는데

 

1. 로그인 시도

사용자가 로그인을 시도하면 서버는 해당 요청을 처리하기 위해 쓰레드를 생성한다.

이 쓰레드는 사용자의 로그인 정보 처리한다.

 

2.ThreadLocal

 

스프링 시큐리티는 ThreadLocal을 사용하여 SecurityContext에 저장하는데 이 로컬쓰레드는 말 그대로

로컬이기 때문에 다른 스레드와 공유하지 않는다.

 

3. Authentication 객체:

로그인 과정에서 Authentication 객체가 생성된다.

처음에는 이 객체에 principal(사용자 이름) , creditials(비밀번호)등이 저장되고

authenticated의 상태는 false(인증되지 않음)으로 저장된다.

 

 

4.SecurityContextHolder.clearContext()

 

이 부분이 중요한데 스프링 시큐리티는 매 요청마다 이 Security에 있는 유저 정보를 삭제한다.

인증이 되었건 안되었건 무조건 삭제한다.

그렇다면 이런 의문이 들 것이다. 

.SecurityContextHolder를 삭제하면 인증된 유저는 어떻게 인증되었다는걸 알 수 있는가?

 

그건 쉽다.

SecurityContextHolder는 인증된 유저를 Authentication의 객체에 저장함과 동시에

Session영역에 SecurityContext( 유저의 정보)를 같이 저장한다.

 

그렇게 되면 인증된 유저는 .SecurityContextHolder에서 객체를 확인하지 않고

Session영역에서 유저의 정보를 꺼내서 찾아본다.

만약 인증된 유저라면 Session이 있기 때문에 세션에서  SecurityContext를 찾아서

확인한다.

 

SecurityContext attribute = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
Authentication authentication1 = attribute.getAuthentication();

 

사용방법은 이러하며 이 때 session에서 꺼낼때에는 반드시 유저의 정보가 저장되어 있어야 한다.

없으면 NPE에러 발생

 

5. 인증 과정

 

사용자의 인증정보를 검증하는 과정을 거친다.

인증이 성공하면 Authentication 객체는 principal에 UserDetail의 객체를 포함되게 되고,

credetials의 정보는 숨겨지게 된다.

또한 authorities은 맨 처음에 false였지만 true(인증됨)으로 바뀌게 된다.

 

6. SecurityContext 저장

 

Authentication 객체는 SecurityContextHolder를 통해 SecurityContext에 저장된다.

이제 이 정보는 어플리케이션 내에 인증된 사용자로써 접근권한을 확인하는데 사용된다.

 

security.authorizeHttpRequests(auth ->auth.anyRequest().authenticated());

 

즉 이러한 형식에 인증인가에 통과되는 것 이다.

 

7. HttpSession 

인증 정보가 세션에 저장된다.

이는 SPRING_SECURITY_CONTEXT라는 키 아래에 보관되며 세션이 유지되는 동안 사용자가

인증된 상태를 유지 할 수 있게 된다.

 

 

[인증 흐름]

 

 

인증 흐름은 대략 이러한 형태로 흘러가는데

 

클라이언트가 로그인 시도 시 

 

1.UsernamePasswordAuthentcationFilter가 받는다.

 

UsernamePasswordAuthenticationFilter을 가로채서 사용자가 제출한 사용자 이름과 비밀번호를 추출한다.

 

이 필터는 추출한 정보를 사용해서 UsernamePasswordAuthenticationToken를 만드는데 해당 사용자는

인증되지 않은 사용자 임으로(로그인 시도)이기 때문에 authenticatedfalse가 된다.

 

그렇게 생성된 "인증되지 않은" 토큰은 AuthenticationManager에 전달되게 되며 실제 인증과정을 거친다.

 

 

2. AuthenticationManager

 

Authentication(인증되지 않은 객체)는 AuthenticationManager에 전달되는데 인증을 처리할 적절한

AuthenticationProvider를 결정한다.

 

여기서 AuthenticationProvider를 결정한다.가 무슨뜻이냐면

 

1. AuthenticationManager는 인스턴스의 리스트를 관리하는데 특정한 인증유형등을 관리한다.

(Oauth2 , LDAP)등의 인증을 결정한다.

 

 

 

3. AuthenticationProvider

 

선택된 AuthenticationProvider객체들은

 

Authenticate(Authentication authentication)

 

통해 인증을 시도하는데 인증에 성공하면 성공하면 완전히 채워진 Authentication 객체 

즉 (인증된 사용자의 정보와 권한)을 반환한다.

 

여기서 AuthenticationProvider에 의해 호출되는 클래스가 있는데

 

UserDetailService이다. (매우중요)

 

위에서 말했듯이 인증에 성공하면 완전히 채워진 Authentication 객체 

즉 (인증된 사용자의 정보와 권한)을 반환한다. 다고 적혀있는데

어떤식으로 인증을 확인하냐면

 

DB를 사용할 경우 이 UserDetailService 클래스를 상속받아서

 

이 클래스안에서 유저의 정보들을 db에서 찾아서 검색하게 된다.

 

만약에 있다면 return으로 UserDetailService 타입의 객체를 반환 해주면 된다.

 

그렇게 인증을 성공했다고 처리한 후 유저의 정보를 Authentication 객체에 담게된다.

 

 

 

 

 

4. 인증 객체 완성 

 

AuthenticationProvider는 반환된 UserDetails와 사용자의 권한을

Authentication 객체에 추가하고 이 객체의 상태를 인증된 상태로 업데이트한다.

 

5. SecurityContext에 저장

 

인증된 Authentication 객체는 SecurityContext에 저장되며 , 이후 보안 컨텍스트를 통해 애플리케이션

전반에 걸쳐 사용자 인증 상태와 권한을 참조 할 수 있게 된다.

 

이 과정을 통해 스프링 시큐리티는 사용자가 제공한 자격 증명을 검증하고, 인증이 성공적으로 완료되었을 때만 사용자에게 적절한 접근 권한을 부여한다