스프링 시큐리티 - 아키텍처 정리
공부중인 스프링 시큐리티의 로직 케이스 및 아키텍처에 대해 정리해보았다.
스프링 시큐리티 아키텍처
개요
-
스프링 시큐리티는 주요한 기능들이 파이프라인과 같은 필터 패턴으로 구현되어 있다.
-
이러한 필터들은 체인으로 묶여있다.
- 서블릿 필터는 스프링에서 정의된 빈을 주입해서 사용할 수 없다. 따라서 특정 이름의 빈을 찾아 해당 빈에게 요청을 위임
DelegatingFilterProxy
가springSecurityFilterChain
에게 요청을 위임DelegatingFilterProxy
는 실제 보안처리를 하지 않음
- 각각의 필터들을 거치며 자신이 작업가능한 케이스인 경우 로직을 수행한다.
생성된 스프링 시큐리티 필터 목록
보안 설정 커스터마이징
WebSecurityConfigurerAdapter
클래스의configure()
메소드를 오버라이딩 하여 보안 설정 커스터마이징이 가능하다.
@Configuration
@EnableWebSecurity
class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
;
}
}
@Order
를 이용하여 다중 시큐리티 설정 또한 가능하다. (순서 중요)
@Configuration
@EnableWebSecurity
@Order(1)
class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.formLogin()
;
}
}
@Configuration
@Order(0)
class AdminSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
;
}
}
각 필터 설명
이미지 출처 : Inflearn - 정수원님 스프링 시큐리티 강의
- SecurityContextPersistenceFilter
사용자의 인증 요청을 받게되면 loadContext
를 통해 해당 사용자의 이전 인증 정보가 있는지 검사, 없는 경우 SecurityContext
생성하여 SecurityContextHolder
에 저장
이때 SecurityContext
는 사용자의 인증 정보 Authentication
객체를 담고있다.
- LogoutFilter
로그아웃을 처리하는 필터
LogoutHandler
에서 로그아웃 로직 수행
- UsernamePasswordAuthenticationFilter
Form 인증 방식의 인증 요청을 담당하는 필터
SecurityContext
에 있는 Authentication
의 username, password 값을 확인하여 인증을 수행
실제 로직 수행은 AuthenticationManager
에게 인증 수행 요청을 하면 해당 클래스는 AuthenticationProvider
에게 실제 인증 처리를 위임한다.
- ConcurrentSessionFilter
사용자가 가지고 있는 session에 대해 session.isExpired()
판별하여 세션이 만료된 경우 로그아웃 처리
- RememberMeAuthenticationFilter
사용자의 이전 인증에서 RememberMe
를 체크한 경우 새로운 인증 수행 필요 X
- AnonymousAuthenticationFilter
인증 정보를 가지고 있지 않은 익명 사용자에게 인증 토큰 AnonymousAuthenticationToken
발급
- SessionManagementFilter
session 설정에 따라 새롭게 생성된 세션의 등록 여부를 처리하는 필터
인증 시도 차단 또는 이전 세션 만료 설정 처리
- ExceptionTranslationFilter
인증, 인가 처리에서 발생된 예외들을 처리해주는 필터
- FilterSecurityInterceptor
인가 처리를 해주는 필터
인증 객체 존재 여부, 해당 권한 포함 여부
로직 케이스 예시
- 웹 서비스 접속 루트
@RestController
public class SecurityController {
@GetMapping("/")
public String rootPage() {
return "root";
}
@GetMapping("/admin")
public String adminPage() {
return "admin";
}
- security 설정
@Configuration
@EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
;
}
}
인증 예시
- 처음 Form 인증 시도하는 경우
- 사용자가 서버에 접근 (localhost:8080/)
DelegatingFilterProxy
->springSecurityFilterChain
에게 요청 위임SecurityContextPersistenceFilter
->SecurityContext
생성, 사용자의Authentication
객체 담아서SecurityContextHolder
로 시큐리티 컨텍스트를 감싼뒤 저장UsernamePasswordAuthenticationFilter
->SecurityContextHolder.getContext().getAuthentication()
통해 사용자의 인증 객체를 얻어서AuthenticationManager
에게 인증 요청AuthenticationManager
는AuthenticationProvider
에게 실제 인증 처리 위임AuthenticationProvider
로 부터 리턴 받은 인증 성공 여부 반환SessionManagementFilter
-> 인증 성공한 사용자의 Session 등록
- 이미 인증이 수행된 동일한 계정으로 인증 시도하는 경우
- 사용자가 서버에 접근 (localhost:8080/)
- 위와 같이 Form 인증을 동일한 계정으로 수행
SessionManagementFilter
->ConcurrentSession
존재하는 경우 아래와 같은 두가지 전략으로 처리- (1)
SessionAuthenticationException
: 새로운 인증 시도 차단 - (2)
session.expireNow()
: 이전 사용자 세션 만료
- (1)
- session.expireNow() 를 통해 이전 사용자 세션이 만료된 경우
- 사용자가 서버에 접근 (localhost:8080/)
ConcurrentSessionFilter
->session.isExpired
를 통해 사용자의 세션이 만료된 것을 확인- 해당 사용자 로그아웃 처리
- response 예외 발생
인가 예시
- 인증 객체 존재하지 않는 경우
- 사용자가 서버에 접근 (localhost:8080/)
- 앞선 필터들에서 인증 처리 실패 ->
Authentication == null
FilterSecurityInterceptor
-> 인증 여부 확인 ->AuthenticationException
발생
- 요구되는 권한을 가지고 있지 않은 경우
- 사용자가 어드민 리소스에 접근 (localhost:8080/admin)
- 앞선 필터들에서 인증 처리 성공
FilterSecurityInterceptor
->AccessDecisionManager
에게 인가 처리 위임AccessDecisionManager
->AccessDecisionVoter
객체들에게 인가 여부 판단 요청- Voter 들에게서 리턴 받은 값을 통해 설정된 전략으로 인가 여부를 판단
- (1) AffirmativeBased: 하나의 Voter라도 허가 한 경우 인가 승인
- (2) ConsensusBased: 다수표로 판단
- (3) UnanimousBased: 모든 Voter가 허가해야 인가 승인
- Admin 권한이 없으므로
AccessDeniedException
발생
댓글남기기