| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- 페이징
- Spring Data JPA
- docker
- AWS
- @Transactional
- docker compose
- kafka
- 지연 로딩
- Dead Letter Queue
- dockerhub
- Routing Key
- Spring
- 쿠버네티스
- 컨테이너
- MSA
- Web
- DLQ
- JWT
- 스프링 부트
- redis
- JdbcTemplate
- DI
- mybatis
- securitycontextholderfilter
- @ComponentScan
- JPQL
- 서블릿 컨테이너
- JPA
- CORS
- Spring Container
- Today
- Total
look-forest
내부 구조 - 필터와 사용자/인증 정보 본문
서블릿 필터 → 스프링 빈으로 시큐리티 로직 위임
DelegatingFilterProxy와 FilterChainProxy
DelegatingFilterProxy
- 서블릿 필터 처리를 스프링에 들어있는 빈으로 위임하고 싶을 때 사용하는 서블릿 필터
- 스프링 빈을 찾아 요청을 넘겨주는 서블릿 필터. 스프링 시큐리티 의존성을 추가하면 자동 추가된다.
- FilterChainProxy에게 시큐리티 관련 처리를 위임한다.
- 타겟 빈 이름을 설정해야 하는데, 스프링 부트를 사용할 때는 자동으로 등록된다. (SecurityFilterAutoConfiguration)
FilterChainProxy
- DelagatingFilterProxy에 의해 호출되는 SecurityFilterChain을 들고 있는 빈
- 보통 “springSecurityFilterChain” 이라는 이름의 빈으로 등록된다.

스프링 시큐리티 Filter와 FilterChainProxy
FilterChainProxy
스프링 시큐리티의 여러 필터들은 FilterChainProxy가 호출한다.


SecurityFilterChain 호출 과정
1. 필터 체인을 여러 개 만들고 securityMatcher 설정을 해두면 (없으면 Default, 생성해도 Default라는 이름으로..)
@Configuration
@EnableWebSecurity(debug = true)
public class SecurityConfig{
@Bean
@Order(1)
public SecurityFilterChain securityFilterChain1(HttpSecurity http) throws Exception {
http
.securityMatcher("/**")
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()
);
return http.build();
}
@Bean
@Order(2)
public SecurityFilterChain securityFilterChain2(HttpSecurity http) throws Exception {
http
.securityMatcher("/**")
.authorizeHttpRequests(auth -> auth.anyRequest().authenticated()
);
return http.build();
}
}
2. FilterChainProxy에서 matchs()로 적절한 SecurityFilterChain을 선정한다
private List<Filter> getFilters(HttpServletRequest request){
for (SecurityFilterChain chain : this.filterChains) {
if (chain.matches(request)) {
return chain.getFilters();
}
}
return null;
}
3. SecurityFilterChain에 포함된 Filter들을 하나씩 실행한다.
public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException {
//필터를을 순회하며 하나씩 실행
this.currentPosition++;
Filter nextFilter = this.additionalFilters.get(this.currentPosition - 1);
nextFilter.doFilter(request, response, this);
}
필터의 전반 구조
필터 상속과 요청 전파
필터의 기반이 되는 필터 클래스를 만들어두고 해당 클래스를 상속 받아 각 특성에 맞게 구현되어 있다.
이로써 중복되는 코드를 줄이고 각각의 구현부가 자신이 가지는 책임에 대해서만 작업을 수행하면 된다.

가장 상단의 필터 클래스는 필터의 구조적인 역할만, 필터를 상속 받은 구현부는 구현부의 역할만 수행한다.
예를 들어 로그인 필터의 경우 로그인의 종류가 아주 많기 때문에 기본적인 로그인 틀을 구현1로 구현하고 세부적인 로그인 방식을 구현2로 구현하여 등록한다.

GenericFilterBean과 OncePerRequestFilter
모든 시큐리티 필터는 두 추상 클래스를 기반으로 구현되어 있다.
SecurityFilterChain에 담겨 있는 필터는 GenericFilterBean 기반으로 구현된 필터가 있고, GenericFilterBean을 상속한 OncePerRequestFilter 기반으로 구현된 필터도 존재한다.
- GenericFilterBean 추상 클래스는 자바 서블릿 필터 기반으로 구현되어 있으며,
자바 서블릿 영역에서 스프링 영역에 접근할 수 있도록 필터를 빈으로 등록한다.
내부적으로 동일한 필터를 여러 번 통과하더라도 통과한 수 만큼 내부 로직이 실행된다. - OncePerRequestFilter는 GenericFilterBean을 기반으로 작성된 추상 클래스로,
클라이언트의 한 번 요청에 대해 내부적으로 동일한 서블릿 필터를 여러번 거칠 경우 한 번 반응하도록 설계되어 있다.
즉, 내부적으로 동일한 필터를 여러 번 통과하더라도 첫 한 번만 내부 로직이 실행된다.
OncePerRequestFilter의 동작
- forward 시

- redirect 시

필터의 형식
- init() : 서블릿 컨테이너 실행 시 필터를 생성하고 초기화할 때 사용하는 메소드
- doFilter() : 요청에 대한 작업 수행 및 다음 필터를 호출하는 메소드
- GenericFilterBean : doFilter()
- OncePerRequestFilter : doFilterInternal()
- destory() : 서블릿 컨테이너 종료시 초기화하는 메소드
public class LogoutFilter extends GenericFilterBean {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
//처리할 로직
//System.out.println("before");
//다음 필터 호출
chain.doFilter(request, response);
//요청이 돌아서 다시 현재 필터를 통과할 때 처리할 로직
//System.out.println("after");
}
}
사용자/인증 정보
SecurityContextHolder와 Authentication
1. 애플리케이션에서 사용하는 사용자 정보를 Security에서 사용할 유저 정보로 변환할 필요가 있다.
2. 유저 정보를 공유해야 한다. 예를 들어, 인가 필터가 작업을 하려면 유저의 ROLE 정보가 필요한데, 앞단의 필터에서 유저에게 ROLE 값을 부여한 결과를 인가 필터까지 공유해서 확인할 수 있도록 해야 한다.
3. 멀티스레드 환경에서 사용자 별로 유저 정보를 따로 보관해야 한다.
SecurityContextHolder : SecurityContext들을 관리하는 메소드를 제공
- 하지만 실제로 등록, 초기화, 읽기와 같은 작업은 SecurityContextHolderStrategy 인터페이스를 활용해 위임한다.
- 그 중 기본 값이 ThreadLocalSecurityContextHolderStrategy로, *ThreadLocal을 사용해 SecurityContext를 보관한다.
더보기* 쓰레드 범위 변수. 즉, 쓰레드 수준의 데이터 저장소. 같은 쓰레드 내에서만 공유.
같은 쓰레드라면 해당 데이터를 메소드 매개변수로 넘겨줄 필요 x
접근 쓰레드별 SecurityContext 배분
톰캣 WAS는 멀티 쓰레드 방식으로 동작한다. 유저가 접속하면 유저에게 하나의 쓰레드를 할당한다.각각의 유저는 동시에 시큐리티 로그인 로직을 사용할 수 있다.
이때 SecurityContextHolder의 필드에 선언된 SecurityContext를 호출하게 된다면 쓰레드간 공유하는 메모리의 code 영역에 데이터가 있기 때문에 정보가 덮어지는 현상이 발생한다고 생각할 수 있는데,
threadLocal로 관리되기 때문에 쓰레드별 다른 구획을 나눠 제공한다.
(객체는 공유하지만 객체 내 로컬 변수는 스레드 영역의 스택에 저장됨) - 따라서 Authentication 객체는 애플리케이션 전반에서 사용이 가능하다.
SecurityContextHolder.getContext().getAuthentication().getPrincipal();
SecurityContext
- Authentication 보관 및 제공

Authentication
- Principal
- 사용자에 대한 정보.
- UserDetailsService에서 리턴한 그 객체 (UserDetails 구현체인 User) - GrantedAuthorities
- ROLE_USER, ROLE_ADMIN 등 Principal이 가지고 있는 권한
(ROLE_는 User.builder().roles("ADMIN")에 추가 시 붙음)
- 인증 이후 인가 및 권한 확인 시 이 정보 참조 - Credentials
- 증명 (비밀번호, 토큰) 정보. 인증 후에는 굳이 유지 x

UserDetails
- 애플리케이션이 가지고 있는 유저 정보와 스프링 시큐리티가 사용하는 Authentication 객체 사이의 어댑터
UserDetailsService
- 유저 정보를 UserDetails 타입으로 가져오는 DAO (DB든 인메모리든) 인터페이스
@RequiredArgsConstructor
@Service
public class AccountService implements UserDetailsService {
private final AccountRepository accountRepository;
private final PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountRepository.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException(username));
return User.builder()
.username(account.getUsername())
.password(account.getPassword())
.roles(account.getRole())
.build()
;
}
}
참고 자료 & 이미지 출처
https://www.devyummi.com/page?id=6695e062d31df967ae77c97b
스프링부트 시큐리티 (백기선 님)
https://docs.spring.io/spring-security/site/docs/5.1.5.RELEASE/reference/htmlsingle/#core-components
Spring Security Reference
The authenticator is also responsible for retrieving any required user attributes. This is because the permissions on the attributes may depend on the type of authentication being used. For example, if binding as the user, it may be necessary to read them
docs.spring.io
Spring Security 의 인증 알아보기
안녕하세요. 네이버페이 회원&인증BE 의 최용화입니다.
medium.com
'Spring > Spring Security (feat. JWT, OAuth2)' 카테고리의 다른 글
| 내부 구조 - Security Filters (0) | 2024.11.02 |
|---|---|
| 내부 구조 - Authorization (인가) (0) | 2024.11.02 |
| 내부 구조 - Authentication (인증) (1) | 2024.11.02 |
| 사용, 테스트 방법 (0) | 2024.10.30 |
| 스프링 시큐리티 전체 구조 개요 (0) | 2024.10.28 |