개인 프로젝트에서는 직접 Spring Security를 사용해 사용자를 인증하여 로그인하고, 사용자의 권한을 확인해 특정 페이지에 접근하는 것을 제한하는 기능을 구현하였다. 당시에는 기능 구현에 초점을 두었다면 이제는 기본적인 내용부터 다시 정리해보고자 한다.
Spring Security란
Spring Security란 스프링 개반의 애플리케이션의 보안을 담당하는 스프링 하위 프레임워크로, 크게 인증과 인가라는 개념을 기반으로 동작한다.
주요 개념
인증(Authentication) : 입력 받은 정보와 사용자 정보를 비교해 신원을 확인하는 것을 의미한다. 대표적인 인증 방식으로 로그인, 공인인증서 확인 등이 있다.
인가(Authorization) : 인증을 통해 사용자를 식별한 후 사용자가 가진 권한을 확인해서 페이지나 서비스에 대한 접근을 허용하는 것으로 인가는 한 애플리케이션에서 무수히 많이 발생될 수 있다.
권한(Role) : 스프링 시큐리티에서는 권한을 Role 기반으로 관리한다. (권한명 역시 'ROLE_USER', 'ROLE_ADMIN', 'ROLE_ANONYMOUS'의 형태로 사용된다.)
로그인 과정
비로그인 상태에서 접근 주체가 특정 권한이 필요한 서비스를 요청하면 Spring Security는 Spring Security Context에서 Authentication객체를 찾는다. Authentication 객체가 없을 경우 스프링은 접근 주체에게 로그인 페이지를 제공해준다.
접근 주체는 아이디와 비밀번호와 같은 로그인 정보를 입력하여 로그인을 시도할 수 있고, 이 정보를 기반으로 Spring Security에서 사용자의 인증이 이루어지고나면 Spring Security Context에 Authentication 객체(사용자의 인증 정보를 담음)를 생성해 저장되어 세션이 만료되기 전에는 계속해서 해당 객체를 통해 사용자의 권한을 확인하고 권한을 인가할 수 있다.
스프링 시큐리티 동작 과정
1. AuthenticationFilter
Request가 들어오면 사용자 자격 증명 정보를 기반으로 인증 토큰을 생성한다. AuthenticationFilter에서는 인증 토큰에서 사용자의 이름과 비밀번호를 추출하고 이를 이용해 UsernamePasswordAuthenticationToken 객체 생성한다. 이 토큰을 인자로 전달하여 인터페이스인 AuthenticationManager의 authenticate() 호출한다.
*토큰 생성시 입력된 아이디와 비밀번호는 UserDetail 객체의 Principal(접근 주체), Credential(비밀 번호)가 된다.
2. AuthenticationManager(AuthenticationProvider)
AuthenticationManager의 실제 구현체인 AuthenticationProvider에서 authenticate()는 토큰에서 사용자 이름(Principal)과 비밀번호(Credential)을 추출해 UserDetailsService를 통해 사용자 세부 정보를 검색하는 기능을 수행한다.
3. UserDetailsService
UserDetailsService에서는 loadUserByUsername()의 인자로 사용자 이름(username)을 전달해 UserDetails(User 인터페이스 구현)를 검색한다.
4. AuthenticationManager(AuthenticationProvider)
UserDetailsService를 통해 불러온 사용자 정보가 성공적으로 인증되면 완전한(부여된 권한 목록, 사용자 자격 증명, authenticated =true) UsernamePasswordAuthenticationToken 객체를 생성해 반환하고, 그렇지 못할 경우에는 AuthenticationException이 발생한다.
5. AuthenticationFilter
AuthenticationFilter는 향후 필터 사용을 위해 획득 한 Authentication 객체를 SecurityContext에 저장한다.
UsernamePasswordAuthenticationToken(참고)
- Authentication을 implements한 AbstractAuthenticationToken의 하위 클래스
- username이 Principal의 역할을 하고, password가 Credential의 역할 수행
- 첫번째 생성자는 인증 전의 객체를 생성하고, 두번째 생성자는 인증이 완료된 객체를 생성
필터 체인(참고)
웹 계층에서의 Spring Security는 springSecurityFilterChain이라는 이름의 하나의 필터로 보이지만 내부적으로는 여러 개의 필터가 순서대로 연결된 필터 체인의 형태로 동작한다.
클라이언트로부터 요청이 들어올 경우 DispatcherServlet으로 요청이 전달되기 전에 필터들을 거치게 된다. 이 필터들 중 DelegatingFilterProxyRegistrationBean이라는 필터를 만나게 되고, 이 필터는 Spring Security가 만든 DelegatingFilterProxy 클래스를 "springSecurityFilterChain"라는 이름의 빈(Bean)으로 등록해주는 역할을 한다. 실제로 DelegatingFilterProxy가 처리를 위임하는 클래스는 FilterChainProxy이다.
스프링과 스프링 시큐리티를 연결해주는 역할을 한다고 여겨진다.
필터 종류
springSecurityFilterChain은 스프링 시큐리티가 자동으로 생성한 필터들(AuthenticationFilter)을 순회하며 필터링을 실시한다. 필터의 동작 순서는 다음과 같다. *필터들은 리스트(List)형태로 존재하며, 그 순서가 일정하다. 필터 중에는 무조건 로드되는 필터도 있지만, 설정에 따라 로드되는 필터도 있다.
1. WebAsyncManagerIntegrationFilter
SpringSecurityContextHolder는 ThreadLocal 기반 (하나의 스레드에서 SecurityContext를 공유하는 방식)으로 동작하는데, 비동기(Async)와 관련된 기능을 쓸 때에도 SecurityContext를 사용할 수 있도록 만들어준다.
2. SecurityContextPersistenceFilter
SecurityContext(Authentication 객체 보관 인터페이스)가 없으면 만들어준다.
3. HeaderWriterFilter
응답(Response)에 Security와 관련된 헤더 값을 설정해준다.
4. CsrfFilter
CSRF 공격을 방어한다.
5. LogoutFilter
설정된 로그아웃 URL로 오는 요청을 감시하며, 해당 유저를 로그아웃시킨다.
6. UsernamePasswordAuthenticationFilter
Form기반 인증을 처리하는 필터. AuthenticationManager를 통한 인증 실행
- 인증 성공시 SecurityContext에 Authentication 객체 저장, AuthenticationSuccessHandler 실행
- 인증 실패시 AuthenticationFailureHandler 실행
7. ConcurrentSessionFilter
인증된 사용자와 관련된 모든 세션을 추적한다. 매 요청마다 SessionRegistry에서 세션 정보를 검색해 세션 만료 여부를 체크하고, 최대 세션 허용 개수를 초과할 경우 세션을 만료 시킨다.
8. BasicAuthenticationFilter(참고)
HttpBasic 인증을 처리한다.
HttpBasic
- Http Spec 중 Header에 username, password 를 실어 보내면 브라우저 혹은 서버가 그 값을 읽어 인증하는 방식이다.
- 보통 브라우저 기반 요청이 클라이언트의 요청을 처리할때 자주 사용한다.
- 요청헤더를 통해 username, password를 보냈기때문에 요청헤더에서 부터 인증정보를 읽어와 인증을 시도하며, 폼인증 방식과 달리 stateless 하기때문에 매 요청마다 인증을 시도한다.
- 보안에 상당히 취약하기 때문에 반드시 HTTPS를 사용할 것을 권장한다.
- 요청이 하나라도 Snipping 당한다면 인증정보가 노출된다.
9. RequestCacheAwareFilter
인증 후 원래 Request 정보로 재구성하는 기능을 수행한다. 예를 들어 비로그인 상태로 가입한 사용자만 접근 가능한 페이지로 접근하려 할 경우에 로그인 페이지로 먼저 이동시키고, 로그인 완료 후 원래 접근하던 페이지(가입한 사용자만 접근 가능한 페이지)로 이동시켜 주는 역할을 한다.
10. SecurityContextHolderAwareRequestFilter
Spring Security 에서 시큐리티 관련 서블릿 3 API 를 구현해준다.
11. RememberMeAuthenticationFilter
세션이 사라지거나 만료가 되더라도 쿠키 또는 DB를 사용하여 저장된 토큰 기반으로 인증을 처리한다. 현재 Remember-me 셋팅을 해둔 상태이기 때문에 실행되고 있다.
12. AnonymousAuthenticationFilter
이 필터에 도달할 때까지 접근 주체의 정보가 인증되지 않았다면(Authentication 객체가 없을 경우) 인증 토큰에 사용자가 익명 사용자(Anonymous)로 나타난다. SpringSecurityContext에 Authentication이 있다면 그냥 통과한다.
13. SessionManagementFilter
세션 변조 방지 전략 설정, 유효하지 않은 세션을 Redirect 시킬 URL 설정, 동시성 제어 설정, 세션 생성 전략 설정 등의 기능을 수행한다.
14. ExceptionTranslationFilter
보호된 요청을 처리하는 중에 발생할 수 있는 예외를 위임하거나 전달하는 역할을 한다.
15. FilterSecurityInterceptor
AccessDecisionManager로 권한부여 처리를 위임함으로써 접근 제어 결정을 쉽게 해준다.
16. OAuth2ClientAuthenticationProcessingFilter
이 필터는 기본적으로 생성되는 필터 리스트에는 존재하지 않으나, 개발자가 OAuth 2.0 인증을 이용하고 할 때에는 스프링 시큐리티가 생성해준다. 기본적으로는 UsernamePasswordAuthenticationFilter에서 사용자 인증이 이루어지지만, OAuth 2.0 인증은 실시되지 않아 OAuth 2.0 인증을 수행할 때는 이 필터에서 인증되지 않은 채 다음 필터로 넘어간다. 이럴 때는 이 필터에서 OAuth 2.0 인증이 이루어진다.
17. DefaultLoginPageGeneratingFilter & DefaultLogoutPageGeneratingFilter
기본적으로 스프링 시큐리티에서 만들어주는 로그인 페이지, 로그아웃 페이지를 제공해준다.
필터 등록 방법
스프링부트
@EnableAutoConfiguration을 통해 SecurityFilterAutoConfiguration 클래스를 로드할 수 있다.
스프링
web.xml에 다음과 같이 필터를 추가할 수 있다.
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>