Security Filter 기능
03 Aug 2022패스트캠퍼스 Spring Security 강의 정리
Security Filter
- Spring security의 동작은 사실상 filter로 동작
- 각자 다른 기능을 가진 다양한 필터 존재
- 필터는 제외하거나 추가 가능
- 필터의 동작 순서 변경 가능
- 대표적인 필터
- SecurityContextPersistenceFilter
- BasicAuthenticationFilter
- UsernamePasswordAuthenticationFilter
- CsrfFilter
- RememberMeAuthenticationFilter
- AnonymousAuthenticationFilter
- FilterSecurityInterceptor
- ExceptionTranslationFilter
- 각 필터는
GenericFilterBean
을 상속하고 GenericFilterBean은Filter
를 구현함
public abstract class OncePerRequestFilter extends GenericFilterBean
public abstract class GenericFilterBean implements Filter
- 필터는 요청이나 응답에 작업을 수행
doFilter
메소드에서 필터링 작업을 하며 필터는 doFilter를 구현해야 함
public interface Filter {
public default void init(FilterConfig filterConfig) throws ServletException {}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException;
public default void destroy() {}
}
- FilterChainProxy
doFilterInternal
에서 적용된 필터의 개수와 동작 순서 확인 가능
- FilterOrderRegistration
- 필터 순서 정의
- 100번부터 시작해서 100씩 증가됨
- 100씩 증가되는 사이에 커스텀 필터 추가 가능
// spring security v5.7.2
final class FilterOrderRegistration {
private static final int INITIAL_ORDER = 100;
private static final int ORDER_STEP = 100;
private final Map<String, Integer> filterToOrder = new HashMap<>();
FilterOrderRegistration() {
Step order = new Step(INITIAL_ORDER, ORDER_STEP);
put(DisableEncodeUrlFilter.class, order.next());
put(ForceEagerSessionCreationFilter.class, order.next());
put(ChannelProcessingFilter.class, order.next());
order.next(); // gh-8105
...
}
SecurityContextPersistenceFilter
- SecurityContext를 찾아서 SecurityContextHolder에 넣어주는 역할
- 기본적으로 SecurityContext는 HttpSession에서 가져옴(HttpSessionSecurityContextRepository)
- 로그인 하지 않아도 JSESSIONID로 로그인 상태를 유지할 수 있도록 함
- SecurityContext가 없으면 새로 생성
- 5.7.2부터 deprecated 되었으며 SecurityContextHolderFilter 사용
public class SecurityContextPersistenceFilter extends GenericFilterBean {
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
...
// security context 불러오기
SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
try {
// security context holder에 security context 넣어주기
SecurityContextHolder.setContext(contextBeforeChainExecution);
if (contextBeforeChainExecution.getAuthentication() == null) {
logger.debug("Set SecurityContextHolder to empty SecurityContext");
}
else {
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
}
}
chain.doFilter(holder.getRequest(), holder.getResponse()); // 다음 필터 실행
} ...
}
BasicAuthenticationFilter
- 로그인 과정을 거치지 않아도 요청 가능
- 로그인 데이터를 Base64로 인코딩해서 모든 요청에 포함해서 보내서 인증
- username:password 데이터 인코딩
- Authorization 헤더에
Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
- 세션이 필요없고 요청할 때마다 인증 과정(stateless)
- 보안에 취약하므로 https 사용
- 사용하지 않을 때는
http.httpBasic().disable()
비활성화 처리
UsernamePasswordAuthenticationFilter
- form 데이터로 username, password 기반의 인증
- 흐름
- UsernamePasswordAuthenticationFilter 통해 인증 시도
- ProviderManager(AuthenticationManager) 인증 정보 제공
- AbstractUserDetailsAuthenticationProvider 사용자 계정의 상태나 비밀번호 일치 여부 등 확인
- DaoAuthenticationProvider 사용자 정보 제공
- UserDetailsService 사용자 정보 제공하는 service
// UsernamePasswordAuthenticationFilter
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
...
// this.getAuthenticationManager() -> ProviderManager
return this.getAuthenticationManager().authenticate(authRequest);
}
}
// ProviderManager
public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
try {
// provider -> AbstractUserDetailsAuthenticationProvider
result = provider.authenticate(authentication);
if (result != null) {
copyDetails(authentication, result);
break;
}
}
...
}
}
// AbstractUserDetailsAuthenticationProvider
public abstract class AbstractUserDetailsAuthenticationProvider
implements AuthenticationProvider, InitializingBean, MessageSourceAware {
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
...
try {
// retrieveUser -> DaoAuthenticationProvider
user = retrieveUser(username, (UsernamePasswordAuthenticationToken) authentication);
}
...
}
}
// DaoAuthenticationProvider
public class DaoAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {
protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication)
throws AuthenticationException {
prepareTimingAttackProtection();
try {
// this.getUserDetailsService() -> UserDetailsService
UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
...
}
}
}
CsrfFilter
- CSRF 공격을 방어하는 필터
- 사이트 간 요청 위조
- 사용자 의지와 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 하는 공격
- CSRF 토큰으로 요청 확인
- 정상적인 페이지는 CSRF 토큰이 없거나 잘못된 토큰을 가짐
- 자동으로 활성화되어 있는 필터
- 명시적으로 실행하기 위해
http.csrf()
- 비활성화할 때는
http.csrf().disable()
RememberMeAuthenticationFilter
- 일반 세션보다 더 긴 시간 로그인 사실을 기억하도록 함
- 세션의 기본 만료 시간은 30분
- RememberMeAuthenticationFilter 기본 만료 시간은 2주
- RememberMe 토큰으로 인증
- 서버가 꺼지지 않으면 브라우저를 껐다 켜도 세션이 유지됨
- 활성화
http.rememberMe()
AnonymousAuthenticationFilter
- 인증 안 된 사용자가 요청할 때 익명 유저로 만들어 authentication에 넣어주는 필터
- 다른 필터에서 익명 유저인지 확인 가능
- AnonymousAuthenticationToken
- 활성화
http.anonymous()
principal("anonymousUser")
로 principal 변경 가능
FilterSecurityInterceptor
- 앞의 필터를 통해 넘어온 authentication을 기반으로 최종 인가 판단
- 보통 필터 중에서 뒤쪽 순서
- 인증을 가져오고 문제가 있으면 AuthenticationException 발생
- 문제가 없으면 해당 인증으로 인가 판단
- 인가가 거절되면 AccessDeniedExcetion 발생
- 승인되면 정상적으로 필터 종료
- 흐름
- FilterSecurityInterceptor.doFilter()
- AbstractSecurityInterceptor.beforeInvocation()
- AbstractSecurityInterceptor.authenticateIfRequired()
- 인증에 문제 있을 경우 AuthenticationException
- AbstractSecurityInterceptor.attemptAuthorization()
- 인가에 문제 있을 경우 AccessDeniedExcetion
ExceptionTranslationFilter
- 예외를 처리해주는 필터
- AuthenticationException
- AccessDeniedException
- handleSpringSecurityException에서 처리
- handleAuthenticationException() & handleAccessDeniedException() 분기 처리