Spring/Spring Security

[Spring Security] 인증(Authentication) & 서블릿 인증 구조(Servlet Authentication Architevture)

공대키메라 2022. 7. 17. 14:43

[Spring Security] 인증(Authentication) & 서블릿 인증 구조(Servlet Authentication Architevture)

 

지난 시간에는 공식 문서에서 Spring Security의 구조(Architecture)에 대해 공부했다. 

(Spring Security Architecture 공부 내용 여기 😁 클릭!)

 

이번시간에는 위 내용에 이어서 다음 섹션인 Authentication을 공식 문서에서 알아볼 것이다. 

 

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

 

출처:

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

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

1. 인증(Authentication)

인증에는 여러 메커니즘이 존재한다. 

 

 

2. 서블릿 인증 구조(Servlet Authentication Architecture)

이 내용은 지난 시간에 알아본 구조 관련 내용의 연장선이다. (Spring Security Architecture 공부 내용 여기 😁 클릭!)

 

https://docs.spring.io/spring-security/reference/_images/servlet/architecture/exceptiontranslationfilter.png

마지막에 봤던 구조가 이 그림이었다. 

 

우리는 여기서 2번 흐름인 인증 처리에 관한 자세한 구조를 알아볼 것이다. 

 

3. SecurityContextHolder

Spring Security의 인증의 핵심 모델은 SecurityContextHolder이다. 

 

 SecucirtyContextHolder는 SecurityContext를 포함한다. 

 

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/securitycontextholder.png

 

SecurityContextHolder는 Spring Security가 누구의 정보가 인증된건지 저장하는 곳이다. 

 

Spring Security는 SecurityContextHolder가 어떻게 값이 있는지 신경을 안쓴다.

 

값을 포함하면, 현재 인증된 사용자로 사용된다. 

 

Example 1. Setting SecurityContextHolder

SecurityContext context = SecurityContextHolder.createEmptyContext(); 
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER"); 
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context);

 

이게 Context라는 것이 해석하면 문맥인데, 이 어떤 사람이 로그인 됐는지, 그러한 정보를 저장한 내용을 Context로 표현하고 있다.  

그리고 이러한 로그인 정보를 가지고 있으니 Holder인 것이다.

결국 Security에서 제공하는 로그인 정보(Context)를 저장하는 곳 이라해서 SecurityContextHolder! 인 것이다. 

위의 예시 코드는 이러한 로그인 정보 객체를 생성해서 인증 정보용 객체(Authentication)을 생성 후 context에 저장한다. 다시말하면, 이것을 우리가 로그인 정보를 찾을 수 있게 저장시키는 것이다.

그것을 관리하는 곳이 SecurityContextHolder라는 것이다. 그러면 다시 setContext해주면 되겠지?

실제로 내가 생각하는것이 맞는지 코드 뒤에 오는 공식문서의 설명을 알아보겠다.

 

1. 빈(Empty) SecurityContext를 생성하는 걸로 시작한다. multi thread로 인한 race condition을 피하기 위해 SecurityContextHolder.getContext().setAuthentication(authentication)을 사용하는 것 대신에 새로운 SecurityContext 인스턴스를 생성하는것이 중요하다. 

 

2. 다음으로, 새로운 Authentication 객체를 생성한다. Spring Security는 Authentication 구현체의 어떤 종류가 SecurityContext에 세팅되어 있는지 신경쓰지 않는다. 여기 TestingAuthenticationToken을 사용하는데, 이유는 매우 간단해서이다. 더 흔한 생성 시나리오는 UsernamePasswordAuthenticationToken(userDetails, password, authorities)를 사용하는 것이다. 

 

3. 마지막으로, SecurityContextHolder에 SecurityContext를 세팅한다. Spring Security는 인가에서 이 정보를 사용한다.

 

대강 내가 예상한 흐름에 크게 다른게 없다. 물론 왜 그래야 하는지에 대한 자세한 이유는 빼먹었지만 말이다. 

spring은 정말 실망할 것이 없는게 이름도 그렇거니와 공식 문서도 장난없다. 

정말로 알아보고자 한다면 모든 정보를 제공하기에 시간만 투자하면 되는것이 넘모좋다옹~

 

만약 인증 원칙에 대해 정보를 얻고 싶다면, SecurityContextHolder에 접근해서 할 수 있다.

 

Example 2. Access Currently Authenticated User

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

 

기본적으로 SecurityContextHolder는 이러한 정보를 저장하기 위해 ThreadLocal을 사용하는데, 이것은 SecurityContext가 항상 같은 쓰레드에서 접근이 가능하다는 것을 의미한다. 

 

ThreadLocal을 사용하는 것은 현재 원칙의 요청이 처리된 후에 thread를 지우려고 한다면 매우 안전하다. Spring Security의 FilterChainProxy는 SecurityContext가 항상 지워졌는지 확인한다. 

 

애플리케이션들이 전부 ThreadLocal을 사용하는 것은 아니다. 이것을 SecurityContextHolder의 설정을 변경해서 설정이 가능하다. 더 궁금하면 여기서는 Javadoc에서 SecurityContextHolder에 대해 알아보라고 한다. 

 

SecurityContext를 저장하는 곳이 SecurityContextHolder아닌가?

찾아보니 SecurityContextHolder에서 SecurityContext 객체 저장 방식을 설정할 수 있다. 

MODE_THREADLOCAL : 스레드당 SecurityContext 객체를 할당. 기본값이다.
MODE_INHERITABLETHREADLOCAL : 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext 를 유지
MODE_GLOBAL : 응용 프로그램에서 단 하난의 SecurityContext를 저장한다.

방법은 원하는것을 다음처럼 선택하주면 된다. 

SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_THREADLOCAL );
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_GLOBAL );

SecurityContextHolder 자체가 전역으로 선언되어있어서 어디서는 사용할 수 있다. 별도의 선언 없이 말이다. 

4. SecurityContext

SecurityContext는 SecurityContextHolder에 있다. SecurityContext는 Authentication 객체를 포함한다.

5.Authentication

Authentication 은 Spring Security에서 두개의 메인 목적을 제공한다. 

 

  • 사용자에게 credentials을 제공하는 AuthenticationManager 에 input이 인증을 위해 제공되어왔다. 
  • 현재 인증된 사용자를 나타낸다. 현재 Authentication이 SecurityContext로부터 얻어진다. 

Authentication은 다음 기능을 포함한다. 

  • principal : 사용자를 식별한다. username/password를 인증할 때, 이것은 자주 UserDetails의 인스턴스다. 
  • credentials : 주로 비밀번호다. 많은 경우에 사용자가 정보가 세지 않는지 확실히 하기 위해 인증된 후 지워진다.
  • authorities : GrantedAuthority는 사용자가 허가받은 높은 레벨의 허가이다. 

 

다시 다음 구조를 보면서 정리를 해보자.

 

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/securitycontextholder.png

 

SecurityContextHolder에 인증된 사용자의 정보가 저장되어 있고,  SecurityContext가 인증된 객체를 가지고 있다. 

그리고 다시 Authentication에서 principal, credentials, authorities같은 정보를 가지고 있다.

6. GrantedAuthority

GrantedAuthority는 사용자가 허가된 high level permission이다.

 

GrantedAuthority는 Authentication.getAuthorities() 메소드에서 얻을 수 있다. 이 메소드는 GrantedAuthority 객체의 Collection을 제공한다. GrantedAuthority는 놀랍지 않게, principal을 허가받은 권한이다. 

 

그러한 권한들은 대게 ROLE_ADMINISTRATOR 혹은 ROLE_HR_SUPERVISOR같은 역할들이다. 이러한 역할들은 나중에 web 인증, 메소드 인증, 도메인 객체 인증에서 설정할 수 있다.  

 

다른 Spring Security의 부분들에서 이러한 권한을 해석할 수 있고 그들이 존재하는지 예측할 수 있다. 인증에 기반한username/password를 사용할 때 GrantedAuthority가 대게 UserDetailService에 의해 추가된다.

7. AuthenticationManager

AuthenticationManager는 어떻게 Spring Security의 Filter가 인증(authentication)을 수행하는지 정의하는 API다.

 

반환된 Authenticaion은 SecurityContextHolder로 controller에 의해 세팅된다.

 

만약 Spring Security Filter들을 통합하지 않는다면, SecurityContextHolder에 세팅할 수 있고 AuthenticationManager를 사용할 필요가 없다.

 

AuthenticationManager의 구현체가 어떤것이든 될 수 있지만, 대게 보통의 구현체는 ProviderManager다.

 

8. ProviderManager

ProviderManager는 대체로 흔히 사용되는 AuthencationManger의 구현체이다. 

 

실제로 찾아보니 그렇다.

 

ProviderManager는 AuthenticationProvider의 목록의 일들을 위임한다.

 

각각의 AuthenticationProvider는 인증이 성공적인지, 실패인지 나타내거나 결정을 할 수 없는지 나타내거나, 하향적으로 AuthenticationProvider가 결정하는 것을 가능케 하는 등 할 수 있다.

(그러니까 실행할지 안할지 설정이 가능하다는 말 같다. )

 

AuthenticationProvider의 구현체들

 

 

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/providermanager.png

 

설정된 AuthenticationProvider가 인증할 수 없다면, 인증은 ProviderNotFountException로 실패할 것이다. 

 

실제로 각각의 AuthenticationProvider는 어떻게 구체적인 인증 종류를 수행할 지 안다.

 

예를 들어, 하나의 AuthenticationProvider가 username/password를 검증할 수 있다. 이것은 AuthenticationProvider가 매우 구체적인 종류의 인증을 할 수 있도록 해준다. 다양한 종류의 인증과 오직 하나의 AuthenticationManager 빈만 가지고 말이다. 

 

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/providermanager-parent.png

 

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/providermanagers-parent.png

 

기본적으로 ProviderManager는 성공적인 인증 요청으로 반환되는 Authentication 객체로부터 민감한 credentials 정보들을 지우려 시도한다. 이것은 HttpSession에 필요하다면 비밀번호 같은 정보가 더 길게 남아잇는것을 방지한다.

 

Spring Security 공식 사이트의 설명에 좀 더해서 이해를 위해 인프런 코어 스프링 시큐리티(정수원 강사님)

의 AuthenticationManager 강의 중 사진을 첨부한다.

 

출처 : 인프런 코어 스프링 시큐리티(정수원 강사님) 의 AuthenticationManager

 

인증 요청(form 인증, remember 인증, Oauth인증)에 따라 Provider에서 원하는 인증처리를 위임한다고 했는데, 다음과 같이 보니 이해가 잘 된다!

 

또한, 부모 ProviderManager를 설정하여 AuthenticationProvider를 계속 탐색할 수 있다고 한다. (신기방기)

 

9. AuthenticationProvider

 

많은 AuthenticationProvider는 ProviderManager안으로 주입될 수 있다. 

 

각각의 AuthenticationProvider는 인증의 구체적인 종류를 수행한다.

 

예를 들어, DaoAuthenticationProvider는 JwtAuthenticationProvider가 JWT token을 인증하는 반면 username/password 기반의 인증을 지원한다. 

공식 사이트의 내용도 역시 좀 부족한 것 같아서 필자가 자주 참고하는 정수원 강사님의 인프런 코어 스프링 시큐리티 중 AuthenticationProvider를 참고했다.

출처 :&nbsp;&nbsp;인프런 코어 스프링 시큐리티 중 AuthenticationProvider

docs에서 AuthenticationProvider의 method에 대해 찾아보았다.

authenticate 
AuthenticationManager.authenticate(authentication)과 같은 계약으로 인증을 수행한다.(???)
그러니까 이곳에서 실제적인 인증 처리를 한다고 한다.

supports(Class<?> authentication)
AuthenticationProvider가 가리키는 Authentication 객체를 지원하면 true를 반환한다. 
여기서는 현재 인증을 처리할 수 있는가에 대한 여부를 반환하는 것이다. 

 

10. Request Credentials with AuthenticationEntryPoint

 

AuthenticationEntryPoint는 client로부터 credentials을 요청하는 HTTP 응답을 보낼 때 사용된다. 

 

인증이 안되어있으면 로그인 페이지로 redirect하던지 말이다.

 

11. AbstractAuthenticationProcessingFilter

 

AbstractAuthenticationProcessingFilter는 사용자의 credentials를 인증하기 위해 기본 Filter로 사용된다. 

 

credentials가 인증받기 전에, Spring Security는 전형적으로 AuthenticationEntryPoint를 사용해 credential을 요청한다. 

 

위에서 AuthenticationEntryPoint는 client로부터 요청 credentials를 보내기 위해 사용된다고 한다. 

 

다음으로, AbstractAuthenticationProcessingFilter 제출된 모든 인증 요청을 인증할 수 있다.

 

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/architecture/abstractauthenticationprocessingfilter.png

 

1. 사용자가 그들의 credentials를 제출할 때, AbstractAuthenticationProcessingFilter가 HttpServletRequest로 부터 인증을 생성한다.

 

생성된 Authentication의 종류는 AbstractAuthenticationProcessingFilter의 하위 클래스에 의존한다.

 

예를 들어, UsernamePasswordAuthenticationFilter는 HttpServletRequest에서 제출된 username과 password로 부터UsernamePasswordAuthenticationToken을 생성한다.

 

2. Authentication은 인증되기위해 AuthenticationManager로 넘겨진다.

 

3. 만약 인증이 실패한다면, Failure에서 다음이 일어난다.

 

    - 1. SecurityContextHolder가 정보를 지운다.

 

    - 2. RememberMeServices.loginFail이 호출된다. 만약 remember me가 설정되지 않았다면, 아무것도 안일어난다.

 

    - 3. AuthenticationFailureHandler가 호출된다.

 

 

4. 인증이 성공하면 Success에서 다음이 일어난다.

 

   - 1. SessionAuthenticationStrategy가 새로운 로그인을 알린다. 

 

   - 2. Authentication이 SecurityContextHolder를 설정한다. 후에 SecurityContextPersistenceFilter가 HttpSession

    SecurityContext를 저장한다. 

 

   - 3. RememberMeServices.loginSucces가 호출된다. remember me가 설정되지 않았다면, 아무것도 안일어난다.

 

   - 4. ApplicationEventPublisher는 InteractiveAuthenticationSuccessEvent를 발행한다. 

 

   - 5. AuthenticaionSuccessHandler가 호출된다.


여태 설명한 흐름을 그림 하나로 이해할 수 있다면 정말 좋을 것이다.

 

이것을 도와주는 그림을 하나 첨부한다.

 

출처 : 인프런 코어-스프링-시큐리티(정수원 강사님) 중 인증 흐름 이해&nbsp; - Authentication flow

 

 

 

 

이번 시간에는 Authentication과 Authenticaion Architecture에 대해 알아보았다. 

 

결국 전체적인 틀은 키메라가 생각하기에 인증을 요청하고, 인증이 되어 있으면 성공이고, 안되어 있으면 다시 인증을 요청하도록 에러를 보내는 것으로 크게 틀이 짜여 있는 것 같다. 

 

어찌보면 너무 당연하지만, 그 내부에서 어떤 구조로 되어 있고, 어떤것들이 해당 역할을 하는지 이해하는 것이 관건인것 같다.