스프링 시큐리티 : 아키텍쳐
SecurityContextHolder와 Authentication
- [reference](https://docs.spring.io/spring-security/site/docs/5.1.5.RELEASE/reference/htmlsingle/#core-comp onents)
- 구조 : SecurityContextHolder - SecurityContext - Authentication
-
SecurityContextHolder
- SecurityContext 제공
- 기본전략으로 ThreadLocal을 사용한다.
- Global, Inheritable, ThereadLocal 등 전략을 선택할 수 있다.
- ThreadLocal : 간단하게 말하면 한 Thread 내에서 Resource를 공유하는 저장소 (파라미터를 넘기지 않아도 객체에 접근이 가능)
- Servlet기반의 애플리케이션(Servlet컨테이너를 사용하는)는 Async한 기능이 아니라면 요청당 하느의 Thread를 사용한다. (Thread per Request)
SecurityContext
- Authentication 제공
Authentication
- pricipal과 GrantAuthority 제공
Principal
- "누구" 에 해당하는 정보
- UserDetailsService에서 리턴한 객체
- 객체는 UserDetails 타입
GrantAuthority
- "Role_USER", "ROLE_ADMIN" 등 pricipal이 가지고 있는 권한을 말한다.
- 인증 이후 인가 및 권한 확인 시, 해당정보를 참조한다.
- "ROLE_"은 SimpleGrantAuthority등 Authority 생성 시 붙여준다. (입력시에는 "USER", "ADMIN" 과같이 저장)
UserDetails
- 애플리케이션이 가지고 있는 유저정보와 스프링 시큐리티가 사용하는 Authentication객체 사이의 어댑터
UserDetailsService
- 유저정보를 UserDetails타입으로 가져오는 DAO(Data Access Object) 인터페이스
- 구현은 자유롭게
예제


AuthenticationManager 와 Authentication
- 스프링 시큐리티에서 인증은 AuthenticationManager가 한다.
Authentication authenticate(Authentication authentication) throws AuthenticationException;
- AuthenticationManager는 아래메소드 하나만을 가진다.
- 인자로 받은 Authentication : 사용자가 입력한 인증 정보로 만든 객체 (폼 인증의 경우)
- Principal : "Bong"
- Credentials : "123"
- 리턴 하는 Authentication : 인증된 객체를 Principal로 담은 객체
- Principal : UserDetailsService에서 리턴한 객체 (UserDetails)
- Credentials :
- GrantAuthorities : 권한
- AuthenticationException
- BadCredentials (잘못된 인증정보)
- Locked (잠김)
- Disabled (비활성)
- AuthenticationManager의 구현체
- 대부분 ProviderManager 사용
- ProviderManager
- 여러 AuthenticationProvider에 위임한다.
- 아무등록 안할 시, AnonymousAuthenticationProvider 하나를 가지고 있음
- UsernamePasswordAuthenticationToken(인자로 받은 authentication)은 AnonymousAuthenticationProvider에서 처리 불가능
- 부모 탐색 DaoAuthenticationProvider에서 처리가능
- UserDetailsService에 구현한 loadUserByUsername 메소드에서 UserDetails를 가져온다.
- UserDetails는 시큐리티가 Principal정보를 표현할 인터페이스
- 가져온 UserDetials를 check 후에 만든 result 가
SecurityContextHolder.getContext().getAuthentication()
에 담기는 객체
ThreadLocal
- Java.lang 패키지에서 제공하는 스레드 범위 변수
- 스레드 수준의 데이터 저장소
- 같은 스레드 내에서만 공유
- 같은 스레드라면 해당 데이터를 메소드 매개변수로 넘겨줄 필요가 없다.
- SecurityContextHolder의 기본 전략
package com.bong.demospringsecurityform.domain.Account;
public class AccountContext {
private static final ThreadLocal<Account> ACCOUNT_THREAD_LOCAL
= new ThreadLocal<>();
public static void setAccount(Account account) {
ACCOUNT_THREAD_LOCAL.set(account);
}
public static Account getAccount(Account account) {
return ACCOUNT_THREAD_LOCAL.get();
}
}
Authentication과 SecurityContextHolder
- SecurityContextHolder 에서 꺼낸 Authentication
- 모두 동일한 Authentication객체를 리턴하고 있다.
-
- UsernamePasswordAuthenticationFilter
- 폼 인증을 처리하는 시큐리티 필터
- authenticationManager.authenticate(authRequest) 를 통해 인증을 한다.
- 인증된 Authentication 객체를 SecurityContextHolder에 넣어주는 필터
- SecurityContextHolder.getContext().setAuthentication(authentication)
- SecurityContextPersistenceFilter
- SecurityContext를 HTTP session에 캐시(기본전략)하여 여러 요청에서 Authentication을 공유할 수 있도록 하는 필터
- SecurityContextRepository를 교체하여 세션을 HTTP session이 아닌 다른 곳에 저장하는것도 가능하다.
- 기본은 HttpSessionSecurityContextRepository
- 가져오는것만 하는것이 아니라, session에 저장하는것도 한다.
- 로그인 요청 -> UsernamePasswordAuthenticationFilter -> SecurityContextPersistenceFilter를 타서 세션에 저장
- 매번 요청 시, 저장된 유저정보가 있는지 확인한다.
스프링 시큐리티 Filter와 FilterChainProxy
필터가 어디서 어떻게 사용되는지 알아보자.
- FilterChainProxy를 통해 Filter목록들을 가져온다.
- url pattern이 매치가 되면 매치가 된 chain의 filter들을 모두 가져온다.
- 가져온 목록들을 순차적으로 실행한다.
- 모든필터들은 FilterChainProxy가 호출한다.
- 스프링 시큐리티가 제공하는 필터들
- WebAsyncManagerIntergrationFilter
- SecurityContextPersistenceFilter
- HeaderWriterFilter
- CsrfFilter
- LogoutFilter
- UsernamePasswordAuthenticationFilter
- DefaultLoginPageGeneratingFilter
- DefaultLogoutPageGeneratingFilter
- BasicAuthenticationFilter
- RequestCacheAwareFtiler
- SecurityContextHolderAwareReqeustFilter
- AnonymouseAuthenticationFilter
- SessionManagementFilter
- ExeptionTranslationFilter
- FilterSecurityInterceptor
- SecurityFIlterChain 만들어지고, 커스터마이징 되는곳은 SecurityConfig이다.
- 조건에 따라 Filter 갯수가 다르다.
- SecurityFilterChain을 여러개 설정할 수 있다. (여러가지 SecurityConfig를 만든다면..)
DelegatingFilterProxy와 FilterChainProxy
- 일발적인 서블릿 필터
- 어떤요청을 처리하는 앞뒤로 특정한 일을 할 수 있다.
- 이름 그대로 처리를 스프링에 들어있는 빈으로 위임하고 싶을 때 사용하는 서블릿 필터.
- 위임을 하기 위해서는 구체적인 명시가 필요한데, 타겟 빈 이름을 설정한다.
- 스프링 부트를 사용할 때는 자동으로 등록이 된다.(
SecurityFilterAutoConfiguration
에서..) - 스프링 부트 없이 시큐리티를 설정할 때는
AbstractSecurityWebApplicationInitializer
를 사용해서 등록한다.
Delegate : 위임
- 스프링 시큐리티 관점에서 보면 FilterChainProxy로 처리를 위임한다.
- 보통
springSecurityFilterChain
이라는 이름의 빈으로 등록된다.
- 보통

AccessDecisionManager
- 인가를 담당하는 매니저
- Access Control (Authorization) 을 결정내리는 인터페이스로 3가지 구현체를 기본 제공한다.
AffirmativeBased
: 여러 Voter중에 한명이라도 허용하면 허용 (기본전략)ConsensusBased
: 다수결UnanimousBased
: 만장일치
- AccessDecisionVoter
- 해당 Authentication이 특정한 Object에 접근할 때, 필요한 ConfigAttributes를 만족하는지 확인한다.
- WebExpressionVoter : 웹 시큐리티에서 사용하는 기본 구현체
- ROLE_Xxxx가 매치하는지 확인한다.
decide(...)
boolean supports(ConfigAttribute attribute)
- 해당
ConfigAttribute
를 지원하는지 여부를 확인한다. ConfigAttribute
의 예hasRole()
,permitAll()
등…
- 해당
커스터마이징
- AccessDecisionManager를 커스터마이징
- 보다 간단하게 설정하기 위해서는 ExpressionHandler만 커스터마이징하는것도 가능하다.
-
Filter Security Interceptor
- AccessDecisionManager를 사용하여 Access Control 또는 예외 처리 하는 필터.
- 대부분의 경우 FilterChainProxy에 제일 마지막 필터로 들어있다.
- 접근이 허용되지않은 voter의 경우 (attribute를 확인하여), exception 을 뱉는다.
- exception Handler등에 의해 이후 동작이 결정된다.
- AbstractSecurityInterceptor : FilterSecurityInterceptor의 부모 클래스
- accessDecisionManager를 사용하여 accessControl을 한다.
accessDecisionManager.decide(authenticated, object, attributes)
를 실행- 인가안된 사용자는 AccessDeniedException 발생하여 해당 에러를 처리하는 필터에서 처리한다.
ExceptionTranslationFilter
- 필터 체인에서 발생하는 AccessDeniedException과 AuthenticationException을 처리하는 필터
- AuthenticationException 발생 (인증예외)
- AuthenticationEntryPoint 실행 (인증처리기, 인증이될때까지 인증을 시도, 로그인페이지로 리다이렉트)
- AbstractSecurityInterceptor 하위 클래스(FilterSecurityInterceptor)에서 발생하는 예외만 처리한다.
- UsernamePasswordAuthenticationFilter에서 발생한 인증은 ExceptionTranslationFilter에서 처리하지 않는다.
- 해당 예외는 UsernamePasswordAuthenticationFilter안에서 직접 처리한다.
- Session에 exception 메세지를 담아둔다.
- LoginPageGenerating 하는 필터에서 뷰를 보여줄때 저장한 에러메세지와 같이 보여준다.
- AccessDeniedException 발생 (인가예외)
- 인증은 됬는데 인가 예외처리
- 익명사용자라면 AuthenticationEntryPoint 실행
- 익명사용자가 아닌 인증된 사용자라면 AccessDeniedHandler에게 위임
스프링 시큐리티 아키텍처 정리

- ServletContainer 안에 요청이 들어온다.
- DeligatingFilterProxy가 서플릿 필터로 등록 (boot는 자동으로 등록)
- FilterChainProxy에 위임한다. (springSecurityFilterChain이란 빈이름으로)
- 여러 필터들을 체인형태로 가지고 있다.
- WebSecurity라는것으로 만들어진다.(HttpSecurity도 같이 사용하여 만든다.)
- WebSecurityConfigurerAdapter
- WebSecurityConfiguration으로 만들어지면 이것으로 FilterChainProxy를 만드는것.
- DelegatingFilterChainProxy가 위임하는 FilterChain
- 이 필터들이 사용하는 주요한 객체들
- AuthenticationManager 인증
- 직접 구현하여 사용할 수 도 있지만, ProviderManager 구현체를 주로 사용한다.
- ProviderManager는 여러 AuthenticationProvider를 사용해서 인증을 처리한다.
- 그 중 하나가 DaoAuthenticationProvider 이고, DaoAuthenticationProvider 는 UserDetailsService라는 Dao 인터페이스를 사용해서 데이터에서 읽어온 유저정보를 사용하여 인증을 한다.
- 인증을 성공하면, SecurityContextHolder에 넣어놓고 어플리케이션 전반에 걸쳐 사용한다.
- SecurityContextHolder -> SecurityContext -> Authentication -> Principal, GrantAuthorities
- 세션에 저장할 수 도 있다.
- 이 정보는 SecurityContextPersistenceFilter에 의해 읽혀져 다시 사용할 수 도 있다.
- AccessDecisionManager 인가
- 인증 후에 FilterSecurityInterceptor에서 AccessDecisionManger를 사용해서 인가처리를 한다.(AccessControl)
- 특정 url, 특정 메소드에 접근할 수 있는 적절한 config attribute 즉 적절한 ROLE을 가지고 있는가를 확인한다.
- AffirmativeBased라는 전략을 기본으로 사용한다.
- 여러 DecisionVoter중에 하나라도 허용을 한다면 그 요청을 허용한다.
- 이 외에도 다수결, 만장일치 등의 전략이 있다.
- AffirmativeBased가 사용하는 voter 중에 WebExpressionVoter 하나만을 사용한다.
- WebExpressionVoter는 SecurityExpressionHanlder를 사용하여 Expression을 처리한다.
- ROLE 계층형 권한을 위해 커스터마이징 할 수 있다.