View

Spring Security - Architectrue에서 설명했던 아키텍쳐는 서블릿에서 스프링 시큐리티가 어떻게 Servlet 구조에서 인증, 인가를 하기위해 어떻게 동작하는지 살펴보았다. 클라이언트 요청이 일련의 필터체인을 거치면서 각 체인에 필요한 단계들을 거치는데 이러한 단계 뿐만 아니라 실제 애플리케이션 로직에서 사용할 공통적인 정보들을 공유하기 위해 ThreadLocal에 일련의 정보들을 보관하고 관리하는데 SecurityContextHolder이다.

위 그림의 SecurityContextHolder를 보면 여러 내부 하위 정보를 감싸는 형태로 구성되어 있는데, 실제 인증, 권한 정보를 저장하는 Principal, Credentials, Authorities 객체를 Authentication이라는 하나의 객체가 감싸고 있고 이를 SecurityContext가 감싸고 있다. SecurityContext가 스프링 시큐리티에서 다루는 실질적인 정보를 담고있는 모음?이고 SecurityContextHodler는 이 정보를 관리하는 역할이다. 예를들어 하나의 쓰레드에서만 공유가능한 형태로 관리할 것인지, 애플리케이션 전체에서 하나의 SecurityContext만 사용할 것인지 따위이다.

예를들어, 웹 애플리케이션 같은 서버-클라이언트 애플리케이션이 아니라 swing등을 사용한 일반 프로그램의 경우 개별 쓰레드가 별도의 인증정보를 담고있는 형태는 아니기 때문에 SecurityContextHodler에서 SecurityContext의 범위를 조정해줄 수 있는 것이다.

쓰레드 로컬에 저장된 정보는 하나의 요청 당 하나의 쓰레드를 배정하는 컨테이너 특성에 따라 하나의 쓰레드가 통과하는 모든 애플리케이션 로직에서 해당 정보를 참조할 수 있게 되는데 예를들어 스프링 시큐리티를 통해 인증된 사용자가 서비스를 요청할 경우에 모든 서비스 코드에서 쓰레드 로컬에 저장된 사용자의 정보를 아래와 같이 받아올 수 있으며 생성 또한 가능하다.

쓰레드 로컬에 저장된 정보는 다른 쓰레드에서 접근이 불가능하므로 안전하고, 만약 해당 쓰레드를 재사용할 경우 정보 노출을 막기 위해 FilterChainProxy가 정보를 모두 삭제한다.

// 정보 받아오기
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);


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

AuthenticationManager, ProviderManager

앞에서 나왔던 SecurityContextAuthentication 객체를 포함하고 있고 Authentication 객체는 Principal, Credentials, Authorizes 객체를 포함하고 있다.

  • principal : 실제 유저 정보(아이디, 비밀번호)를 가지고 있음
  • credentials : 비밀번호 정보 등 보안정보를 가지고 있음
  • authorities : 인가에 필요한 권한 정보를 담고 있음

authentication은 인터페이스이며 각 인증 방식에 따라 다앙한 구현체가 있다. 예를들어 아이디/비밀번호 인증방식을 사용할 경우 Authentication 인터페이스는 UsernamePasswordAuthenticationToken 구현체로 구현된다.

credentials에 담긴 정보는 필터를 거치면서 지워진다. 실제로 필터에서 해당 정보를 보면 비밀번호 정보가 있는데 서비스 레이어에서 보면 null로 되어 있다.

이렇게 합쳐진 3개의 정보를 가진 AuthenticationAuthenticationManager의 input이기도 한데, AuthenticationManager가 전달받은 Authentication이 유효한지, 안한지에 대해 판단하는 역할을 수행하게 된다. 보통 스프링에 구현되어있는, 일반적으로 사용되는 AuthenticationManager의 구현체는 ProviderManager인데 ProviderManager는 하나 이상의 AuthenticationProvider를 가지고 들어온 요청이 유효한지 안한지 확인하는데 우리가 일반적으로 말하는 인증 방식을 해석하는 곳이 AuthenticationProviders에 해당한다고 보면된다.

예를들어 id, password 인증 방식의 경우 id, password 인증 방식을 구현한 AuthenticationProviderProviderManager에 제공되어서, 요청이 들어오면 해당 요청에 대한 Authentication 객체가 만들어지고, AuthenticationManager이 현재 보유중인 AuthenticationProvider들을 체크해가며 인증이 유효한지 안한지 체크해서 그 결과를 반환하게 된다.

AuthenticationProvider가 하나 이상인 이유는 애플리케이션이 여러개의 인증 방식을 제공할 수 있는 경우이다. 예를들어 웹 애플리케이션의 경우 form을 통한 id/password 인증 방식도 제공하면서, 토큰 방식도 제공하고 싶다고 한다면 id/password를 위한 프로파이더, 토큰을 위한 프로바이더를 등록하면 되는것이다. ProviderManager는 이러한 동작을 수행하기 위해, 인증이 실패할 경우 바로 익셉션을 발생 시키는 것이 아니라 현재 사용가능한 모든 프로바이더들을 interate하면서 인증을 검증한다. ProviderManager는 하나의 AuthenticationProvider가 실패했다고 끝내는 것이 아니라, 그 다음 AuthenticationProvider를 체크하도록 설계되어 있으며 인증 작업이 끝나면 Authentication을 반환하는데, 이 반환 값의 credential(비밀번호 등) 정보를 지우는 역할도 수행한다.

credential 정보를 지우는 동작 여부는 ProvidoerManager가 제공하는 eraseCredentialsAfterAuthentication의 값을 조정함으로써 제어할수 있다.

AbstractAuthenticationProcessingFilter

인증을 수행하기 위해 스프링 시큐리티에서 제공하는 AuthenticationManager, ProviderManager를 살펴보았는데 이제 이것들이 스프링 시큐리티의 필터 체인과 어떻게 동작할까라는 물음의 답은 필터이다. 이러한 일련의 과정들은 AbstractAuthenticationProcessingFilter 인터페이스를 구현한 필터 구현체에서 사용된다. AbstractAuthenticationProcessingFilter를 보면 AuthenticationManager와 연관된 메소드들을 확인할 수 있다.

Reference

Share Link
reply