강의를 들으며 내가 알고 있는 내용을 점검하고,
새로 배운 내용을 정리하며,
궁금한 내용을 알아가며 학습해나가는 것을 목표로 합니다.
해당 필터에 요청이 도달할때까지 사용자가 인증되지 않았다면, 사용자를 null 대신 Anonymous 인증 타입으로 표현합니다.
그렇게 함으로써 사용자가 null 인지 확인하는것 보다 어떤 구체적인 타입으로 확인 할 수 있도록 합니다.
필터 체인에서 FilterSecurityInterceptor 바로 위에 위치하며, FilterSecurityInterceptor 실행 중 발생할 수 있는 예외를 잡고 처리 합니다.
💡 필터 체인 상에서 ExceptionTranslationFilter의 위치를 주의해서 볼 필요가 있다. ExceptionTranslationFilter는 필터 체인 실행 스택에서 자기 아래에 오는 필터들에서 발생하는 예외들에 대해서만 처리할 수 있다. 커스텀 필터를 추가해야 하는 경우 이 내용을 잘 기억하고, 커스텀 필터를 적당한 위치에 두어야 한다.
FilterSecurityInterceptor 실행 중 발생 가능한 AuthenticationException, AccessDeniedException 예외에 대한 처리를 담당합니다.
AuthenticationException 예외는 인증 관련 예외이며, AccessDecisionManager에 의해 접근 거부가 발생했을 때 접근 거부 페이지를 보여주거나 사용자를 로그인 페이지로 보냅니다.
암호화와 복호화에 같은 암호 키를 쓰는 알고리즘을 의미합니다.
대칭 키 암호는 공개 키 암호와 비교하여 계산 속도가 빠르다는 장점이 있습니다.
그러나 암호화를 하는 측과 복호화를 하는 측이 같은 암호 키를 공유해야 한다는 단점이 있습니다.
대표적인 알고리즘으로 AES, DES, SEED 등이 있습니다.
Rivet, Shamir, Adelman 세사람의 첫이름을 따 RSA 라고 하며, 공개키 암호 알고리즘 (사실상 표준 공개키 암호 알고리즘)
공개 키, 비밀 키가 한 쌍으로 존재하며 공개 키는 누구나 알 수 있지만 그에 대응하는 비밀 키는 키의 소유자만이 알 수 있어야 합니다.
공개 키 암호 방식은 크게 두 가지 종류로 나눌 수 있습니다.
일반적으로 공개 키 암호 방식은 대칭 키 암호화보다 느립니다.
그렇기 때문에 실제 데이터를 암호화하기 위한 대칭 키를 공개 키로 암호화하고 통신 상대에게 전달하는 방식으로 많이 쓰입니다.
Thread Per Request 모델은 톰캣과 같은 서블릿 컨테이너에서 사용해오고 있는 병렬처리 기법 중 하나 입니다.
동작하는 순서는 아래와 같습니다.
즉, WAS의 최대 동시 처리 HTTP 요청의 개수는 ThreadPool의 개수와 같습니다.
Thead 개수를 늘리면 동시 처리 개수는 늘어나지만, Thread Context 스위칭에 의한 오버헤드도 커지기 때문에 성능이 선형적으로 증가하지는 않습니다.
https://happyer16.tistory.com/entry/대용량-트래픽을-감당하기-위한-Spring-WebFlux-도입
최근 소개된 WebFlux 같은 기술은 Thread 개수를 작게 유지하며 HTTP 요청을 동시 처리 할 수 있도록 합니다.
최대한 적은 수의 스레드로 최대한 많은 요청을 처리하도록 합니다.
HTTP 요청은 하나 이상의 Thread에 바인딩되어 처리될 수 있습니다.
Thread 범위 변수, 동일 Thread 내에서는 언제든 ThreadLocal 변수에 접근할 수 있음을 의미합니다.
// TheadLocal 변수를 생성
ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
// ThreadLocal 변수에 값 쓰기
threadLocalValue.set(1);
// ThradLocal 변수에서 값 읽기
Integer result = threadLocalValue.get();
// ThreadLocal 변수 값 제거
threadLocal.remove();
즉, 동일 Thread내에서 실행되는 Controller, Service, Repository, 도메인 모델 어디에서든 명시적인 파라미터 전달 필요없이 ThreadLocal 변수에 접근할 수 있습니다.
ThreadPool과 함께 사용하는 경우 Thread가 ThreadPool에 반환되기 직전 ThreadLocal 변수 값을 반드시 제거 해야합니다.
그렇지 않을 경우 아래와 같은 상황이 발생하고, 미묘한 버그가 생겨날 수 있음
SecurityContextHolder는 SecurityContext 데이터를 쓰거나 읽을수 있는 API를 제공합니다.
(기본 구현은 ThreadLocal를 이용하고 있습니다.)
💡 At the heart of Spring Security’s authentication model is the SecurityContextHolder. It contains the SecurityContext.
public class SecurityContextHolder {
// ... 생략 ...
private static SecurityContextHolderStrategy strategy;
public static void clearContext() {
strategy.clearContext();
}
public static SecurityContext getContext() {
return strategy.getContext();
}
public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
// ... 생략 ...
}
/**
* SecurityContextHolderStrategy 전략패턴 인터페이스
*/
public interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}
/**
* SecurityContextHolderStrategy 인터페이스 ThreadLocal 구현체
*/
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();
@Override
public void clearContext() {
contextHolder.remove();
}
@Override
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}
@Override
public void setContext(SecurityContext context) {
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}
@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}
ThreadLocal을 기본 구현으로 사용한다는 것은 Thread Per Request 모델을 기본적으로 고려했음을 의미합니다. (물론 Spring Security는 Webflux를 지원하기도 합니다.)
FilterChainProxy 구현을 보면 finally 블록에서 SecurityContextHolder.clearContext() 메소드를 호출하는 확인할 수 있습니다.
이것은 HTTP 요청 처리가 완료되고 Thread가 ThreadPool에 반환되기전 ThreadLocal 변수 값을 제거하기 위합입니다.
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
boolean clearContext = request.getAttribute(FILTER_APPLIED) == null;
if (!clearContext) {
doFilterInternal(request, response, chain);
return;
}
try {
request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
doFilterInternal(request, response, chain);
}
catch (RequestRejectedException ex) {
this.requestRejectedHandler.handle((HttpServletRequest) request, (HttpServletResponse) response, ex);
}
finally {
SecurityContextHolder.clearContext();
request.removeAttribute(FILTER_APPLIED);
}
}
SecurityContextHolder 클래스를 통해 코드 어느 부분(Controller, Serivce, Repository)에서든 SecurityContext에 접근할 수 있습니다.
SecurityContext context = SecurityContextHolder.getContext();
// ... 생략 ...
SecurityContextHolder.setContext(context);
SecurityContext 자체는 어떤 특별한 기능을 제공하지 않으며, 당순히 org.springframework.security.core.Authentication 객체를 Wrapping 하고 있습니다.
public interface SecurityContext extends Serializable {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}
사용자를 표현하는 인증 토큰 인터페이스이며, 인증주체를 표현하는 Principal 그리고 사용자의 권한을 의미하는 GrantedAuthority 목록을 포함합니다.
인증이 완료되거나 혹은 인증되지 사용자를 모두를 포괄적으로 표현하며, 인증 여부를 확인할 수 있습니다.
사용자의 인증 완료 여부에 따라 Principal 값이 달라집니다.
인증(Authentication) — 사용자가 주장하는 본인이 맞는지 확인하는 절차를 의미합니다.
일반적으로 온라인상에서 수행되는 인증은 아이디/비밀번호를 입력하여 수행됩니다.
어플리케이션은 아이디에 해당하는 사용자를 확인하고, 입력한 비밀번호가 저장된 비밀번호와 일치하는지 확인합니다.
Spring Security 3.0 — Dmitry Noskov
HTTP GET 요청에 대해 디폴트 로그인 페이지를 생성해주는 필터입니다.
로그인 아이디/비밀번호/Remember-Me 파라미터명을 변경 가능합니다.
로그인 페이지 자체를 커스텀 구현 가능하며, 이 경우 해당 필터는 비활성화 됩니다.
http
// ... 생략 ...
.formLogin()
.loginPage("/mylogin")
.permitAll()
.and()
// ... 생략 ...
사용자 인증을 처리하기 위한 필터로 가장 대표적인 것으로 UsernamePasswordAuthenticationFilter 구현체가 있습니다.
사용자 인증을 위한 정보(credentials)를 취합하고, Authentication 객체를 생성합니다.
인증이 완료되지 않은 Authentication 객체는 AuthenticationManager 객체로 전달됩니다.
인증이 정상적으로 완료된다면 새롭게 만들어진 Authentication 객체를 반환합니다.
여기서 새롭게 만들어진 Authentication 객체는 인증이 완료된 상태이고, GrantedAuthority 목록을 포함하고 있습니다.
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
if (this.postOnly && !request.getMethod().equals("POST")) {
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
String username = obtainUsername(request);
username = (username != null) ? username : "";
username = username.trim();
String password = obtainPassword(request);
password = (password != null) ? password : "";
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
UsernamePasswordAuthenticationFilter 인증 흐름
AuthenticationManager 인터페이스는 사용자 인증을 위한 API를 제공합니다.
public interface AuthenticationManager {
Authentication authenticate(Authentication authentication) throws AuthenticationException;
}
기본 구현체로 org.springframework.security.authentication.ProviderManager 클래스가 있습니다.
이 구현체는 1개 이상의 AuthenticationProvider 인터페이스 구현체로 구성됩니다.
AuthenticationProvider 인터페이스 구현체가 실제 사용자 인증을 처리하게 됩니다.
1개 이상의 AuthenticationProvider 인터페이스 구현체 중 어떤 AuthenticationProvider가 실제 인증을 처리할지 결정할 수 있습니다.
주어진 Authentication 객체에 대해 supports(Class<?> authentication) 메소드가 true 를 반환하는 AuthenticationProvider 객체가 인증을 처리합니다.
예를 들어 UsernamePasswordAuthenticationToken 타입의 인증 요청은 DaoAuthenticationProvider가 처리합니다.