웹 보안
-
웹 계층의 스프링 보안은 서블릿 기반으로 한다.
-
클라이언트는 응용 프로그램에 요청을 보낸다.
-
컨테이너는 요청 URI의 경로를 기반으로 어떤 필터와 어떤 서블릿이 응용 프로그램에 적용되는지 결정한다.
-
서블릿은 한번의 요청을 필터체인을 순서대로 싷행하여 처리한다.
-
DelegatingFilterProxy & FilterChainProxy 생성 및 등록 Flow
- SecurityFilterAutoConfiguration
- 63 Line: DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
- springSecurityFilterChain 이름으로 DelegatingFilerProxy 를 생성
- DelegatingFilterProxy
- 165 Line: public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
- springSecurityFilterChain: DelegatingFilerProxy 형태로 Filter 생성
- 165 Line: public DelegatingFilterProxy(String targetBeanName, @Nullable WebApplicationContext wac) {
- WebSecurityConfiguration (스프링 영역)
- 94 Line: @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
- springSecurityFilterChain 명으로 Bean 등록
- 95 Line: public Filter springSecurityFilterChain() throws Exception {
- webSecurity가 FilterChainProxy을 springSecurityFilterChain이라는 이름으로 생성
- 94 Line: @Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
- WebSecurity
- 279 Line: protected Filter performBuild() throws Exception {
- 296 Line: FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
- FilterChainProxy를 Bean생성 및 등록
- 63 Line: DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(DEFAULT_FILTER_NAME);
- SecurityFilterAutoConfiguration
-
DelegatingFilterProxy & FilterChainProxy 요청 Flow
- DelegatingFilterProxy
- 250 Line: public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
- 위임할 Filter를 찾는다. (springSecurityFilterChain)
- 264 Line: delegateToUse = initDelegate(wac);
- 338 Line: Filter delegate = wac.getBean(targetBeanName, Filter.class);
- AnnotationConfigServletWebServerApplicationContext으로부터 getBean() 하여 springSecurityFilterChain라는 이름의 Bean을 가져옴
- 가져온 Bean은 FilterChainProxy
- 338 Line: Filter delegate = wac.getBean(targetBeanName, Filter.class);
- 271 Line: invokeDelegate(delegateToUse, request, response, filterChain);
- 요청을 위임
- 358 Line: delegate.doFilter(request, response, filterChain);
- FilterChainProxy를 호출
- 250 Line: public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain)
- FilterChainProxy
- 172 Line: public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- DelegatingFilterProxy의 request를 전달
- 보안 처리를 실시함
- 190 Line: private void doFilterInternal(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- securityFilter의 리스트를 가져와 보안 처리를 수행
- 198 Line: List filters = getFilters(fwRequest);
- securityFilter 리스트를 호출
- 214 Line: VirtualFilterChain vfc = new VirtualFilterChain(fwRequest, chain, filters);
- FilterChain 내부적으로 가상 VirtualFilterChain을 생성하여 내부적으로 실질적으로 request 처리를 수행함
- VirtualFilterChain
- 325 Line: Filter nextFilter = additionalFilters.get(currentPosition - 1);
- 설정된 securtyFilterChain을 관리 및 호출하여 request에 대한 보안처리를 수행함
- 요청을 완로하고나서 Servlet으로 이동
- 325 Line: Filter nextFilter = additionalFilters.get(currentPosition - 1);
- 172 Line: public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
- DelegatingFilterProxy
-
DelegatingFilterProxy 정리
-
필터는 서블릿 2.3 이상 버전부터 사용할 수 있다.
-
서블릿 필터란 사용자가 요청에 대한 자원에 접근 전 / 후에 대한 작업 처리가 가능하다.
-
대신 스프링 기술을 사용할 수 없음
-
스프링 시큐리티는 필터로 구성되어 처리를 하고 있다.
-
스프링내에 필터 개발 목적
- 필터에서 스프링 기술을 사용하기 위한 필요성이 생김
- 스프링 빈 객체를 생성하고 필터라는 타입으로 다루어짐
-
서블릿 기반으로 동작하는 스프링은 사용자의 요청을 서블릿 필터가 먼저 받게 된다.
-
Was에 Tomcat이 올라가게되면 서블릿 컨테이너에서 돌아가고 있는 필터가 사용자의 요청을 받게 된다.
-
서블릿 필터에서 스프링에서 생성한 필터 타입의 스프링 빈으로 요청 정보를 넘겨 처리할 수 있게하는 클래스가 DelegatingFilterProxy 이다.
-
DelegatingFilterProxy 는 서블릿 필터에 있는 필터이다.
-
DelegatingFilterProxy는 사용자의 요청을 Spring에서 관리하는 Filter (Bean) 에게 작업처리를 위임하는 역할을 하고 있다.
-
결론
- 모든 요청은 서블릿 필터가 받고, 스프링 기술을 사용하기 위해서 스프링 시큐리티라는 필터를 통해 보안처리 및 스프링 기술을 사용할수 있게 되었다.
-
springSecurityFilterChain이라는 이름으로 생성되고 이 Bean은 ApplicationContext가 관리
-
springSecurityFilterChain으로 생성되는 필터 Bean은 FilterChainProxy이다.
-
-
FilterChainProxy
- springSecurityFilterChain 내에 필터들을 관리하고 제어
- 필요에 따라 Filter의 개수가 늘어날 수 있음
- 사용자 정의에 따라 생성하여 필터체인에 추가 가능
- 필터의 순서가 중요
- 마지막 필터의 역할은 인증 및 인가 검증 처리
- 필터가 모두 정상 처리되어야 resource에 접근할 수 있다.
-
기본 필터 체인 0 = {WebAsyncManagerIntegrationFilter@7067} 1 = {SecurityContextPersistenceFilter@8025} 2 = {HeaderWriterFilter@8026} 3 = {CsrfFilter@8027} 4 = {LogoutFilter@8028} 5 = {UsernamePasswordAuthenticationFilter@8029} 6 = {DefaultLoginPageGeneratingFilter@8030} 7 = {DefaultLogoutPageGeneratingFilter@8031} 8 = {BasicAuthenticationFilter@8032} 9 = {RequestCacheAwareFilter@8033} 10 = {SecurityContextHolderAwareRequestFilter@8034} 11 = {AnonymousAuthenticationFilter@8035} 12 = {SessionManagementFilter@8036} 13 = {ExceptionTranslationFilter@8037} 14 = {FilterSecurityInterceptor@8014}
-
내가 확인해 본 내용
- 시큐리티 설정만 했을 때 적용되는 필터의 내용 ?
- 어플리케이션 실행 시 DelegatingFilterProxy 및 FilterChainProxy 가 등록되는 플로우
- 사용자 요청 시 처리되는 플로우
-
다중 설정이 필요한 이유 ?
- 요청 방식 또는 요청 URL 에 따라 다른 보안 방식이 필요하는 경우에 사용한다.
-
다중 설정 시 처리 방식
- requestMatcher 에 따라 다른 Filter 사용
-
다중 설정 시 설정 순서
- requestMatcher에 따라 Filter Flow가 다르기 때문에 상세한 requestMapping 부터 선언하도록 한다.
- 넓은 범위의 requestMapping부터 선언 시 세부적으로 설정해야 할 경로의 보안이 무시되는 이슈가 발생할 수 있음
-
하나의 프로젝트에 securityFilterChains라는 리스트에 securityFilterChain이라는 값으로 존재
@Order(0)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.
/* 3.1.1. 필터 초기화 및 다중 보안 설정 용 */
.antMatcher("/admin/**")
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
@Order(1)
@Configuration
public class SecuritySubConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
/* 3.1.1. 필터 초기화와 다중 보안 설정 */
http
.authorizeRequests()
.anyRequest().permitAll()
.and()
.formLogin();
}
}
- WebSecurity
- 296 Line: FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
- securityFilterChains 라는 값 안에 두가지 filters 값이 들어있음
- FilterChainProxy 생성 시 생성자로 넘김
- antMatchers의 resource 접근 값에 따라 필터 프로세스가 달라짐
- chain: 1 0 = {WebAsyncManagerIntegrationFilter@7506} 1 = {SecurityContextPersistenceFilter@7507} 2 = {HeaderWriterFilter@7508} 3 = {CsrfFilter@7509} 4 = {LogoutFilter@7510} 5 = {BasicAuthenticationFilter@7511} 6 = {RequestCacheAwareFilter@7512} 7 = {SecurityContextHolderAwareRequestFilter@7513} 8 = {AnonymousAuthenticationFilter@7514} 9 = {SessionManagementFilter@7515} 10 = {ExceptionTranslationFilter@7516} 11 = {FilterSecurityInterceptor@7517}
- chain: 2 0 = {WebAsyncManagerIntegrationFilter@7522} 1 = {SecurityContextPersistenceFilter@7523} 2 = {HeaderWriterFilter@7524} 3 = {CsrfFilter@7525} 4 = {LogoutFilter@7526} 5 = {UsernamePasswordAuthenticationFilter@7527} 6 = {DefaultLoginPageGeneratingFilter@7528} 7 = {DefaultLogoutPageGeneratingFilter@7529} 8 = {RequestCacheAwareFilter@7530} 9 = {SecurityContextHolderAwareRequestFilter@7531} 10 = {AnonymousAuthenticationFilter@7532} 11 = {SessionManagementFilter@7533} 12 = {ExceptionTranslationFilter@7534} 13 = {FilterSecurityInterceptor@7535}
- FilterChainProxy
- 198 Line: List filters = getFilters(fwRequest);
- 설정된 필터 리스트를 가져옴
- 226 Line: if (chain.matches(request)) {
- 요청정보(antMatcher)와 매핑이되는 해당 filterChain을 찾음
- 198 Line: List filters = getFilters(fwRequest);
- 296 Line: FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
- Authentication
- 사용자의 인증 정보를 저장하는 토큰 개념
- 인증 시 Id와 Password를 담고 인증 검증을 위해 전달되어 사용된다.
- 인증 후 최종 적으로 인증 결과(User, 권한정보)를 담고 SecurityContext에 저장되어 전역적으로 참조가 가능하다.
- 사용자의 인증 정보를 저장하는 토큰 개념
Authentication authentication = SecurityContextHolder.getContext().getAuthentication()
- 구조
- principal: 사용자 아이디 혹은 User 객체를 저장
- credential: 사용자 비밀번호 (인증 정보)
- authorities: 인증된 사용자의 권한 목록
- details: 인증 부가 정보
- Authenticated: 인증 여부
-
SecurityContext
- Authentication 객체가 저장되는 보관소로 필요시 언제든지 Authentication 객체를 꺼내어 쓸 수 있도록 제공되는 클래스
- ThreadLocal 에 저장되어 아무 곳에서나 참조가 가능하도록 설계됨
- 인증이 완료되면 HttpSession 에 저장되어 어플리케이션 전반에 걸쳐 전역적인 참조가 가능하다.
-
SecurityContextHolder
- SecurityContext 객체 저장 방식
- MODE_THREADLOCAL: 스레드당 SecurityContext 객체를 할당, 기본값
- MODE_INHERITABLETHREADLOCAL: 메인 스레드와 자식 스레드에 관하여 동일한 SecurityContext를 유지
- 기본적으로는 메인 스레드와 자식 스레드간 영역이 달라 공유하고 있지 않음
- MODE_GLOBAL: 응용 프로그램에서 단 하나의 SecurityContext 를 저장 (static 변수로 저장)
- SecurityContextHolder.clearContext() : SecurityContext 기존 정보 초기화
- SecurityContext 객체 저장 방식
-
시나리오 정리
- MainThread와 SubThread의 ThreadLocal의 영역이 독립적인 것을 확인하기 위한 시나리오
- SecurityContextHolder의 기본 전략 모드인 ThreadLocal과 InheritableThreadLocal 모드를 비교하여 Thread 간 SecurityContext가 관리되는 방식을 확인
- MainThread와 SubThread의 ThreadLocal의 영역이 독립적인 것을 확인하기 위한 시나리오
@GetMapping("/")
public String index(HttpSession session) {
// 1
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
// 2
SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
Authentication sessionContext = context.getAuthentication();
return "Hello World";
}
@GetMapping("/thread")
public String thread() {
new Thread(
new Runnable() {
@Override
public void run() {
/* ThreaLocal 모드에서는 인증 객체가 null, InheritableThreadLocal 모드의 경우 Thread간 인증 객체가 공유 가능 한 것을 확인 */
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
}
}
).start();
return "thread";
}
-
SecurityContextHolder
- 96 Line: private static void initialize() { ... }
- SecurityContext 객체를 저장하는 방식
-
MODE_THREADLOCAL
- 각 Thread간 ThreadLocal에 저장되어 있는 SecurityContext는 또 다른 Thread와 독립적으로 관리한다.
-
MODE_INHERITABLETHREADLOCAL
- MainThread내에 SubThread간 ThreadLocal이 공유되어 SecurityContextHolder에 접근하여 동일한 SecurityContext를 저장 및 호출할 수 있다.
-
MODE_GLOBAL : GlobalSecurityContextHolderStrategy
-
- xxxSecurityContextHolderStrategy 라는 각각의 전략 클래스명으로 SecurityContext 를 관리
- SecurityContext 객체를 저장하는 방식
- 96 Line: private static void initialize() { ... }
-
SecurityContextHolder 요약
-
SecurityContext 관리
- clearContext
- getContext
- setContext
- createEmptyContext
-
세 가지 SecurityContextStrategy의 구현체를 설정 가능
- MODE_THREADLOCAL : ThreadLocalSecurityContextHolderStrategy
- MODE_INHERITABLETHREADLOCAL : InheritableThreadLocalSecurityContextHolderStrategy
- MODE_GLOBAL : GlobalSecurityContextHolderStrategy
-
-
SecurityContext (interface)
-
SecurityContext 객체에 접근하는 방법 2가지
- SecurityContextHolder
- HttpSession
-
SecurityContextHolder에 있는 SecurityContext에 접근하는 방법
- Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
- HttpSession 에 있는 SecurityContext에 접근하는 방법
- SecurityContext context = (SecurityContext) session.getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY)
- Authentication authentication = context.getAuthentication();
-
-
SecurityContextImpl (class)
-
SecurityContext 요약
- Authentication 관리
- equals
- hashCode
- toString
- getAuthentication
- setAuthentication
- Authentication 관리
-
SecurityContext 객체의 생성, 저장, 조회
-
익명 사용자를 처리하는 방식
- 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder 에 저장
- AnonymousAuthenticationFilter에서 AnonymousAuthenticationToken 객체를 SecurityContext 에 저장
-
익명 사용자가 인증 시
- 새로운 SecurityContext 객체를 생성하여 SecurityContextHolder에 저장
- form인증 방식의 UsernamePasswordAuthenticationFilter 에서 인증 성공 후 SecurityContext 에 UsernamePasswordAuthenticationToken 객체를 SecurityContext에 저장
- 인증이 최종 완료되면 Session 에 SecurityContext을 저장
-
인증 후
- HttpSession에서 SecurityContext 내에 Authentication을 꺼내어 SecurityContextHolder에 저장
- SecurityContext 안에 Authentication 객체가 존재하면 계속 인증을 유지
-
최종 응답 시 공통
- SecurityContextHolder.clearContext()
- 매 요청마다 SecurityContextHolder의 SecurityContext를 비워준다.
- SecurityContextHolder.clearContext()
-
-
익명 사용자를 처리하는 Flow 정리
- SecurityContextPersistenceFilter
-
100 Line: SecurityContext contextBeforeChainExecution = repo.loadContext (holder);
- 사용자가 이전에 인증에 성공하여 securityContext를 session에 저장한 정보가 있는지 확인
- HttpSessionSecurityContextRepository
- 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
- request, response, session을 생성 session으로부터 SecurityContext의 존재여부를 확인 및 조회
- 120 Line: context = generateNewContext();
-
익명의 사용자는 인증한 이력이 없기 때문에 securityContext를 생성
-
224 Line: protected SecurityContext generateNewContext()
-
225 Line: SecurityContextHolder.createEmptyContext()
- SecurityContextHolder
- 161 Line: return strategy.createEmptyContext();
- InheritableThreadLocalSecurityContextHolderStrategy
- 60 Line: return new SecurityContextImpl();
- SecurityContext를 생성
- 54 Line: public void setContext(SecurityContext context);
- 56 Line: contextHolder.set(context);
- SecurityContext를 저장
- InheritableThreadLocalSecurityContextHolderStrategy
- 161 Line: return strategy.createEmptyContext();
- SecurityContextHolder
-
- 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
-
103 Line: SecurityContextHolder.setContext(contextBeforeChainExecution);
- 익명 사용자의 요청 정보로 인하여 생성된 SecurityContext를 SecurityContextHolder에 저장
-
105 Line: chain.doFilter(holder.getRequest(), holder.getResponse);
- SecurityContext를 생성 및 저장한 뒤 다음 필터로 이동
-
AnonymousAuthenticationFilter
- 95 Line: if(SecurityContextHolder.getContext().getAuthentication() == null) { ... }
- 익명 사용자의 접근으로 인하여 Authentication 값이 null 이기 때문에 익명 사용자용 인증처리 로직이 실행
- 114 Line: AnonymousAuthenticationToken auth = new AnonymousAuthenticationFilter(key, principal, authorities);
- 익명 객체 토큰을 생성
- 96 Line: SecurityContextHolder.getContext.setAuthentication(createAuthentication((HttpServletRequest) req));
- 바로 위에서 만든 익명 객체 토큰 값으로 AnonymousAuthentication을 생성하여 SecuriryContextHodler의 SecurityContext에 Authentication으로 저장
- 95 Line: if(SecurityContextHolder.getContext().getAuthentication() == null) { ... }
-
113 Line: SecurityContextHolder.clearContext();
- 익명 사용자의 경우 SecurityContext를 별도로 저장하지 않고 응답 하기전에 SecurityContextHolder 내에 SecurityContext를 삭제한다.
- 매 요청바다 SecurityContext를 생성, SecurityContextHolder에 저장하여 Filter간 인증 정보를 확인하기 때문에 계속 저장할 필요가 없음
-
114 Line: repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
-
134 Line: public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { ... }
-
- SecurityContextPersistenceFilter
-
익명 사용자를 처리하는 Flow
-
인증 요청 사용자를 처리 flow 정리
-
SecurityContextPersistenceFilter
-
100 Line: SecurityContext contextBeforeChainExecution = repo.loadContext (holder);
- 사용자가 이전에 인증에 성공하여 securityContext를 session에 저장한 정보가 있는지 확인
- HttpSessionSecurityContextRepository
- 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
- request, response, session을 생성 session으로부터 SecurityContext의 존재여부를 확인 및 조회
- 120 Line: context = generateNewContext();
- 처음 인증하는 사용자는 인증한 이력이 없기 때문에 securityContext를 생성
- 224 Line: protected SecurityContext generateNewContext()
- 225 Line: SecurityContextHolder.createEmptyContext()
- SecurityContextHolder
- 161 Line: return strategy.createEmptyContext();
- InheritableThreadLocalSecurityContextHolderStrategy
- 60 Line: return new SecurityContextImpl();
- SecurityContext를 생성
- 54 Line: public void setContext(SecurityContext context);
- 56 Line: contextHolder.set(context);
- SecurityContext를 저장
- InheritableThreadLocalSecurityContextHolderStrategy
- 161 Line: return strategy.createEmptyContext();
- SecurityContextHolder
- 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
-
103 Line: SecurityContextHolder.setContext(contextBeforeChainExecution); - 인증되지 않은 사용자의 요청 정보로 인하여 생성된 SecurityContext를 SecurityContextHolder에 저장
-
105 Line: chain.doFilter(holder.getRequest(), holder.getResponse);
- SecurityContext를 생성 및 저장한 뒤 다음 필터로 이동
-
... 인증 필터가 인증 처리를 한 뒤 인증 성공 후
-
AbstractAuthenticationProcessingFilter
- 307 Line: protected void successfulAuthentication( ... ) { ... }
- 인증 필터에서 인증 처리 후 인증 성공 시 처리되는 메서드
- 316 Line: SecurityContextHolder.getContext().setAuthentication(authRequest);
- 인증이 성공된 인증 객체를 SecurityContext에 저장
- 307 Line: protected void successfulAuthentication( ... ) { ... }
-
HttpSessionSecurityContextRepository
- 344 Line: protected void saveContext (SecurityContext context) { ... }
- 인증이 최종 완료되면 Session에 SecurityContext를 저장
- 349 Line: if(authentication == null || trustResolver.isAnonymous(authentication)) { ... }
- 익명 사용자가 아닌경우
- 368 Line: if(httpSession != null) { ... }
- 373 Line: httpSession.setAttribute(springSecurityContextKey, context);
- HttpSession 에 SecurityContext를 저장
- 344 Line: protected void saveContext (SecurityContext context) { ... }
-
113 Line: SecurityContextHolder.clearContext();
- 응답 하기전에 SecurityContextHolder 내에 SecurityContext를 삭제한다.
- 매 요청바다 SecurityContext를 생성, SecurityContextHolder에 저장하여 Filter간 인증 정보를 확인하기 때문에 계속 저장할 필요가 없음
-
114 Line: repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
-
134 Line: public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { ... }
- 정상 인증 처리된 Authentication은 SecurityContext에 저장되어 Session이 관리하게 되는 형태
-
-
-
인증 요청 사용자를 처리하는 Flow
-
정상 인증 처리된 사용자가 요청 flow 정리
- SecurityContextPersistenceFilter
-
100 Line: SecurityContext contextBeforeChainExecution = repo.loadContext (holder);
- 사용자가 이전에 인증에 성공하여 securityContext를 session에 저장한 정보가 있는지 확인
- HttpSessionSecurityContextRepository
- 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
- request, response, session을 생성 session으로부터 SecurityContext의 존재여부를 확인 및 조회
- 108 Line: public SecurityContext loadContext(httpRequestResponseHolder requestResponseHolder) { ... }
-
103 Line: SecurityContextHolder.setContext(contextBeforeChainExecution); - 인증된 사용자의 정보는 SecurityContext에 이미 존재하여 현재 필터에서 사용될 SecurityContextHolder에 저장되어 공유할 수 있도록 한다.
-
105 Line: chain.doFilter(holder.getRequest(), holder.getResponse);
- 공유할 SecurityContext를 Holder에 저장한 뒤 다음 필터로 이동
-
113 Line: SecurityContextHolder.clearContext();
- 응답 하기전에 SecurityContextHolder 내에 SecurityContext를 삭제한다.
- 매 요청바다 SecurityContext를 생성, SecurityContextHolder에 저장하여 Filter간 인증 정보를 확인하기 때문에 계속 저장할 필요가 없음
-
114 Line: repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
-
134 Line: public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) { ... }
-
- SecurityContextPersistenceFilter
-
인증 처리된 사용자의 요청 Flow
-
Authentication 인증 Flow Debug
- UsernamePasswordAuthenticationFilter
-
인증 필터
-
SecurityContext에 인증 객체(Authentication)을 저장
-
89 Line: UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
- 요청 정보를 담은 인증 토큰으로 생성, 인증 요청을 할 수 있는 파라미터 타입으로 변환
-
95 Line: return this.getAuthenticationManager().authenticate(authRequest);
- 인증 토큰을 AuthenticationManager에 전달하여 인증 요청
-
AuthenticationManager (Interface)
-
인증의 전반적인 관리
-
현재 요청의 인증에 대해서 알맞은 Provider를 찾아 인증 처리 위임
-
AuthenticationProvider (ProviderManager)
- UserDetails, authorities를 인증 토큰을 인증객체에 담아 반환
- 199 Line: result = provider.authenticate(authentication);
-
인증 요청에 맞는 AuthenticationProvider를 선택하여 인증처리를 실시함
-
AbstractUserDetailsAuthenticationProvider
- 인증 처리 메서드가 존재하는 DaoAuthenticationProvider의 상위 클래스
- 126 Line: public Authentication authenticate(Authentication authentication)
- 실제 인증 처리 수행이 되는 메서드
- 174 Line: user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
-
사용자를 검색
-
DaoAuthenticationProvider
-
103 Line: protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException { ... }
- 사용자 검색 요청하는 메서드
-
InMemoryUserDetailsManager
- 151 Line: public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { ... }
- username을 통해 메모리에 저장되어 있는 사용자 정보를 요청
- UserDetails 타입으로 반환
-
-
-
-
-
- UsernamePasswordAuthenticationFilter
-
Authentication 인증 Flow
- AuthenticationManager
-
인증 필터로부터 인증처리를 지시 받는 인터페이스
-
인증 처리를 할 수 있는 Provider를 찾아서 인증 처리를 위임하는 클래스
-
ParentProviderManager
-
ChildProviderManager
-
ProviderManager
- AnonymousAuthenticationProvider
- RememberMeAuthenticationProvider
-
- AbstractAuthenticationProcessingFilter
- Line 212: authResult = attemptAuthentication(request, response);
- 구현된 필터를 통해 인증 처리
- UsernamePasswordAuthenticationFilter
- Line 69: public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
- 인증 처리 로직
- Line 95: return this.getAuthenticationManager().authenticate(authRequest);
- 인증 토큰 전달
- ProviderManager
- Line 188: for (AuthenticationProvider provider : getProviders()) {
- 프로젝트 내에 설정되어 있는 Provider 중 현재 form인증 처리를 할 수 있는 DaoAuthenticationProvider를 선택
- Line 199 : result = provider.authenticate(authentication);
- 현재 넘어온 인증 객체를 처리할 수 있는 DaoAuthenticationProvider에게 전달
- AbstractUserDetailsAuthenticationProvider
- Line 128: Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
- Form인증를 할 수 있도록 검증 처리
- Line 144: user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
- 유저 검색
- DaoAuthenticationProvider
- Line 103: protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
- 사용자 조회
- Line 108: UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
- DB에 사용자가 존재하는지 확인
- InMemoryUserDetailsManager
- Line 151: public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- 전달받은 username에 대한 유저 정보를 조회
- Line 166: additionalAuthenticationChecks(user, (UsernamePasswordAuthenticationToken) authentication);
- 패스워드 검증
- DaoAuthenticationProvider
- Line 77: protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
- Line 197: return createSuccessAuthentication(principalToReturn, authentication, user);
- 인증 성공 시 인증 토큰을 만들어 인증된 객체를 AuthenticationManager에게 전달
- DaoAuthenticationProvider
- Line 128: protected Authentication createSuccessAuthentication(Object principal, Authentication authentication, UserDetails user) {
- 인증 객체 생성
- Line 128: Assert.isInstanceOf(UsernamePasswordAuthenticationToken.class, authentication,() -> messages.getMessage("AbstractUserDetailsAuthenticationProvider.onlySupports", "Only UsernamePasswordAuthenticationToken is supported"));
- 생성된 인증 객체를 인증 필터에게 전달
- Line 188: for (AuthenticationProvider provider : getProviders()) {
- Line 95: return this.getAuthenticationManager().authenticate(authRequest);
- 생성된 인증객체를 상위 클래스에서 성공 및 실패 처리할 수 있도록 전달
- Line 69: public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
- Line 240: successfulAuthentication(request, response, chain, authResult);
- 인증 처리 성공시 후처리
- Line 307: protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult)
- SecurityContext에 Authentication(인증객체) 설정
- Line 326: successHandler.onAuthenticationSuccess(request, response, authResult);
- SuccessHandler에서 인증 성공시 자원 접근 가능 처리
- Line 212: authResult = attemptAuthentication(request, response);
- 인증확인 > 인가여부확인(자격조건심사) > 자원접근
-
인가처리
- 인증된 사용자의 자원접근 허가여부 확인
-
스프링 시큐리티가 지원하는 권한 계층
- 웹 계층(request)
- URL 요청에 따른 메뉴 혹은 화면 단위의 레벨 보안
- 서비스 계층(invoke)
- 화면 단위가 아닌 메서드 같은 기능 단위의 레벨 보안
- 도메인 계층(Access Control List, 접근 제어 목록) ( write )
- 객체 단위의 레벨 보안
- 웹 계층(request)
-
인가 처리를 담당하는 Filter
- 마지막에 위치하는 필터로써 인증된 사용자에 대하여 특정 요청의 승인 / 거부 여부를 최종적으로 결정
- 인증 객체 없이 보호자원에 접근을 시도하는 경우 AuthenticationException 발생
- 인증 후 자원에 접근 가능한 권한이 존재하지 않은경우 AccessDeniedException 발생
- 권한 제어 방식 중 Http 자원의 보안을 처리하는 필터
- 권한 처리를 AccessDecisionManager 에게 맡김
-
Debug Point
- FilterChainProxy
- Line 334 : nextFilter.doFilter(request, response, this);
- 필터체인 리스트 확인 포인트
- 0 = {WebAsyncManagerIntegrationFilter@7562}
- 1 = {SecurityContextPersistenceFilter@8944}
- 2 = {HeaderWriterFilter@8945}
- 3 = {CsrfFilter@8946}
- 4 = {LogoutFilter@8947}
- 5 = {UsernamePasswordAuthenticationFilter@8948}
- 6 = {DefaultLoginPageGeneratingFilter@8949}
- 7 = {DefaultLogoutPageGeneratingFilter@8950}
- 8 = {RequestCacheAwareFilter@8951}
- 9 = {SecurityContextHolderAwareRequestFilter@8952}
- 10 = {AnonymousAuthenticationFilter@8953}
- 11 = {SessionManagementFilter@8954}
- 12 = {ExceptionTranslationFilter@8955}
- 13 = {FilterSecurityInterceptor@8956}
- Line 334 : nextFilter.doFilter(request, response, this);
- FilterChainProxy
-
FilterSecurityInterceptor
- 사용자의 요청 처리 순서
- 인증 객체의 존재여부 확인
- Null
- AuthenticationException
- ExceptionTranslationFilter
- AuthenticationException
- Not Null
- SecurityMetadataSource(resource의 자원 접근 권한 확인)
- 권한 정보 확인
- No(NULL인 경우 권한 심사 하지 않음)
- 자원 접근 허용
- Yes
- AccessDecisionManger
- 심의 요청
- AccessDecisionVoter
- 승인
- 자원 접근 허용
- 거부
- AccessDeniedException
- ExceptionTranslationFilter
- 심의 요청
- AccessDecisionManger
- No(NULL인 경우 권한 심사 하지 않음)
- 권한 정보 확인
- SecurityMetadataSource(resource의 자원 접근 권한 확인)
- Null
- 인증 객체의 존재여부 확인
- 사용자의 요청 처리 순서
-
code settings
http
/* /user resource는 USER 권한을 가진 사용자에 대해서 허용 */
.authorizeRequests().antMatchers("/user").hasRole("USER")
/* 그 외 요청에 대해서는 모든 사용자가 접근이 가능 */
.anyRequest().permitAll();
-
AbstractSecurityInterceptor
- MethodSecurityInterceptor (Interceptor)
- 메서드 보안 인가 처리
- AOP 기반으로 동작하는 Interceptor
- FilterSecurityInterceptor (Filter)
- AspectJMethodSecurityInterceptor
- MethodSecurityInterceptor (Interceptor)
-
debug
-
FilterChainProxy
-
Line 334: nextFilter.doFilter(request, response, this);
- 필터 리스트에 ExceptionTranslationFilter가 설정되어 있는 것을 확인
- ExceptionTranslationFilter
- Line 118: chain.doFilter(request, response);
- FilterSecurityInterceptor
- Line 89: FilterInvocation fi = new FilterInvocation(request, response, chain);
- 인가 처리를 담당하는 필터
- 부모 클래스에 전달
- Line 123: InterceptorStatusToken token = super.beforeInvocation(fi);
- 상위 클래스에 호출
- AbstractSecurityInterceptor
- Line 196: Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);
- 유저자원("/users")에 설정된 자원접근 권한정보("USER")를 조회
- Line 199: if (attributes == null || attributes.isEmpty()) {
- 유저의 자원정보를 확인
- 유저 자원에 설정된 권한 정보를 확인하여 인가처리 할 수 있도록 함
- Line 222: if (SecurityContextHolder.getContext().getAuthentication() == null) {
- 인증 객체가 NULL인지 아닌지 확인 (현재는 인증되지 않은 사용자로 접근: anonymousToken을 갖고 있는 사용자)
- 인증Filter를 비정상적으로 통화하여 접근한 사용자의 경우 인증 토큰이 존재 X ?
- Line 233: this.accessDecisionManager.decide(authenticated, object, attributes);
- 인가 처리를 AccessDecisionManager에게 위임
- AffirmativeBased
- Line 58: public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException {
- 실제 인가처리를 작업
- Line 63: int result = voter.vote(authentication, object, configAttributes);
- 구현 클래스에 권한 인가 위임
- WebExpressionVoter
- Line 36: int result = voter.vote(authentication, object, configAttributes);
- 현 접근자원에 대한 접근 권한을 갖고 있는지 판단
- Line 58: public void decide(Authentication authentication, Object object, Collection configAttributes) throws AccessDeniedException {
- Line 196: Collection attributes = this.obtainSecurityMetadataSource().getAttributes(object);
- Line 126: fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
- 요청 자원에 대한 접근
- Line 83: if (deny > 0) {
- Line 84: throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
- 접근 권한이 없는 경우 예외 처리
- Line 236: publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));
- 발생한 예외를 ExceptionTranslationFilter가 받아서 처리
- ExceptionTranslationFilter
- Line 166: private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception)
- AuthenticationException 또는 AccessDeniedException 예외를 처리
- Line 179: else if (exception instanceof AccessDeniedException) {
- 인가 예외로 인한 예외 처리 로직
- Line 184: sendStartAuthentication(...);
- Line 204: protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, AuthenticationException reason) throws ServletException, IOException {
- SecurityContext에 인증 객체 null 설정
- Line 212: authenticationEntryPoint.commence(request, response, reason);
- LoginUrlAuthenticationEntryPoint
- Line 133: public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
- Line 169: redirectStrategy.sendRedirect(request, response, redirectUrl);
- 로그인 페이지로 접근하여 인증 하도록 유도
- Line 166: private void handleSpringSecurityException(HttpServletRequest request, HttpServletResponse response, FilterChain chain, RuntimeException exception)
- Line 236: publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException));
- 요청 자원에 대한 접근
- Line 83: if (deny > 0) {
- Line 84: throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));
- 접근 권한이 없는 경우 예외 처리
- Line 89: FilterInvocation fi = new FilterInvocation(request, response, chain);
-
인증되지 않은 사용자의 접근
-
USER 권한을 가진 사용자의 접근
-
- FilterSecurityInterceptor
- 인가 처리
- AccessDecisionManager
-
decide
- 권한 심사 요청
- Authentication
- FilterInvocation
- ConfigAttributes
-
AccessDecisionVoter
- 권한 심사 정보 반환
- ACCESS_GRANTED
- ACCESS_DENIED
- ACCESS_ABSTAIN
-
ACCESS_DENIED
- ExceptionTranslationFilter
- 예외 처리
- ExceptionTranslationFilter
-
-
인증정보, 요청정보, 권한정보를 이용하여 사용자의 자원접근을 허용할 것인가를 최종 결정하는 주체
-
여러 개의 Voter들을 가질 수 있으며 Voter들로부터 접근허용, 거부, 보류에 해당하는 각각의 값을 리턴받아 판단 및 결정
-
최종 접근 거부 시 예외 발생
-
접근 결정의 세 가지 유형
- AffirmativeBased
- 여러 개의 voter 클래스 중 하나라도 접근 허가로 결론을 내면 접근 허가로 판단
- ConsensusBased
- 다수표(승인 및 거부)에 의해 최종 결정을 판단
- 동수인 경우 기본은 접근허가이나 allowIfEqualGrantedDeniedDecision을 false로 설정하는 경우 접근거부로 결정
- UnanimousBased
- 모든 voter가 만장일치로 접근을 승인해야하며 그렇지 않은 경우 접근을 거부
- AffirmativeBased
- 판단을 심사하는 것
- Voter가 권한 부여 과정에서 판단하는 자료
- Authentication - 인증 정보(User)
- FilterInvocation - 요청 정보(antMatcher("/user"))
- ConfigAttributes - 권한 정보(hasRole("USER"))
- 결정 방식
- ACCESS GRANTED: 권한 허용(1)
- ACCESS DENIED: 접근 거부(-1)
- ACCESS ABSTAIN: 접근 보류(0)
- voter가 해당 타입의 요청에 대해 결정을 내릴 수 없는 경우