JAVA(SPRINGBOOT)

[SPRING SECURITY] 시큐리티 파헤치기 (3) - AUTHENTICATION

본듀 2023. 3. 22. 16:41

Servlet Authentication Archittecture

 

SecurityContextHolder 

누가 인증되었는지에 대한 정보가 저장되는 공간
SpringSecurity 인증의 핵심
SecurityContext를 포함
SpringSecurity는 SecurityContextHolder가 어떻게 채워지는지 신경쓰지않음
값이 저장되어있으면 현재 인증된 User로 사용된다.

// Setting SercurityContextHolder

SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication =
    new TestingAuthenticationToken("username", "password", "ROLE_USER");
context.setAuthentication(authentication);

SecurityContextHolder.setContext(context);



// Access Currently Authenticated User

SecurityContext context = SecurityContextHolder.getContext();
Authentication authentication = context.getAuthentication();
String username = authentication.getName();
Object principal = authentication.getPrincipal();
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();

기본적으로, SecurityContextHolder는 ThreadLocal을 사용하기때문에
인자로 주고받지 않아도 같은 스레드내에서 접근가능하다.

이러한 점은 현재 요청이 진행된 후 스레드 clear를 고려한다면 꽤나 안전하다.
FilterChainProxy는 SecurityContext가 항상 clear하도록 보장해준다.

대부분의 어플리케이션은 위의 기본값을 바꿀 필요가 없지만
JVM내의 모든 스레드에서 같은 SecurityContext를 사용하는 Swing client나
독립실행형 어플리케이션 등의 경우 아래의 두가지 방법으로 환경을 바꿀 수 있다.

1. 시스템 변수를 설정한다.
2. SecurityContextHolder의 static 메서드를 호출한다.

 

SecurityContext

SecurityContextHoler로부터 얻을 수 있음
Authentication을 포함

 

Authentication 

유저가 제공한 자격증명이나 SecurityContext로 부터 얻은 현재 유저를 저장

Authentication은 SpringSecurity내에서 두 가지 주된 목적이 있다.
1. 유저가 제공하는 자격증명을 제공하기 위해 AuthenticationManager의 인수가 된다.
2. 현재 인증된 유저의 나타낸다. (SecurityContext에서 얻을 수 있음)

principle : 유저 식별. username/password인증시 UserDetails의 인스턴스
credentilas: 보통 password. 유출되지 않도록 인증시 clear
authorities: GrantedAuthority 인스턴스는 유저가 부여받은 높은 수준의 권한

SpringSecurity는 
SecurityContextHolder내에 인증된 유저들에 대한 정보를 저장하고 인증 여부를 판단한다.

기본적으로, ThreadLocal로 SecurityContext라는 객체를 SecurityContextHolder에 저장하여
관리되며 Thread별로 전역적으로 접근할 수 있게한다.
상황에 맞게 저장 전략은 변경할 수 있다.


Authentication은 실질적으로 인증정보가 저장된 객체이다.
인증된 사용자, 자격증명, 권한들이 저장되어 있다.

스레드단위로 ContextHolder에 Context를 저장하여 관리하는 것은 이해되나,
왜 Authentication을 Context로 감싸 저장하는지 잘 이해가 되지 않는다.

이 부분은 이해되면 시큐리티 파헤치기 외에 정리하도록하자.

 

GrantedAuthority 

Authentication의 principle에 부여된 권한
Authentication.getAuthorities() 메서드를 통해 얻을 수 있음
username/password인증시 UserDtailsService에 의해 로드

특정 domain에 국한되지 않고 전체 어플리케이션의 권한
그렇기 때문에 54와 같은 숫자 형태로 나타내지 않음
권한의 종류가 많으면 memory가 빨리 부족해지고, 인증하는데 시간이 오래 걸릴 수 있음

 

AuthenticationManager 

SpringSecurity의 Filter가 어떻게 동작하는지 정의하는 API
반환된 Authentication은 Security Filter에 의해  SecurityContextHolder에 저장

SpringSecurity Filter를 통합하지 않는다면
직접 SecurityContextHolder에 저장하여 AuthenticationManager가 필요하지 않음

 

ProviderManager 

일반적인 AuthenticationManager의 구현체
AuthenticationProvider에게 위임

각각의 AuthenticationProvider는 인증의 성공여부를 나타낼 수 있고,
결정할 수 없다면 다음 AuthenticationProvider에게 결정할 수 있도록한다.

인증을 처리할 수 있는 AuthenticationProvider가 설정되어있지 않으면,
인증은 ProviderNotFoundException으로 실패

각각의 AuthenticaitonProvider은 username/password, SAML 등 특정 유형의 인증을 처리할 수 있다.
AuthenticationProvider의 역할을 나눔으로써 여러 유형의 인증을 처리할 수 있고,
AuthenticationManager 하나로 묶어서 표현할 수 있다.

기본적으로, ProviderManager는 인증 성공 후 반환된 Authentication의 민감한 정보를 지운다.
이는 HttpSession에 필요이상으로 정보가 유지되지 않도록 한다.

 

AuthenticationProvider 

특수한 타입의 인증을 처리하기 위해 ProviderManager가 사용

AuthenticationManager는
실질적인 인증처리를 하는 AuthenticationProvider의 묶음으로 보인다.

AuthenticationProvider에게 역할을 분담하여
역할을 확실히 구분짓고,
필요시 인증처리할 수 있는 타입을 유동적으로 변경하여
외부 요구 사항에 유연하게 대처할 수 있도록 만든 것으로 보인다.

 

Request Credentials with AuthenticationEntryPoint 

client로부터 자격증명을 요구하는 HTTP Response를 보내기위해 사용

client의 요청에 자격증명이 포함되어있다면 자격증명을 요구할 필요가 없지만,
포함되어 있지 않다면 로그인페이지로 redirect시킨다.

 

AbstractAuthenticationProcessingFilter 

인증을 위한 기본 필터

AbstractAuthenticationProcessingFilter가 Authentication을 생성한다.
생성된 Authentication은 AbstractAuthenticationProcessingFilter의 하위 클래스에 의존적이다.
username/password 인증시엔 UsernamePasswordAuthenticationFilter가 UsernamePasswordAuthenticationToken을 생성한다.

인증 실패시
SecurityContextHolder를 비우고
RememberMeServices.loginFail이 호출
AuthenticationFailureHandler가 호출

인증 성공시
SessionAuthenticationStrategy가 새 로그인의 알림 받음
Authentication이 SecurityContextHolder에 저장
(나중에 SecurityContextPersistenceFilter가 SecurityContext를 HttpSession에 저장)
RememberMeServices.loginSuccess가 호출
ApplicationEventPublisher가 InteractiveAuthenticationSuccessEvent 발행
AuthenticationSuccessHandler 호출

 

FormLogin

SpringSecurity는 username과 password를 읽기 위해 Form, Basic, Digest 세 가지 내장 매커니즘을 제공

이번에는 위의 세 가지 중 가장 많이 쓰이는 FormLogin만 살펴볼 예정이다.

 

1. 유저가 인증되지 않은 요청을한다.
2. FilterSecurityInterceptor가 요청을 거부하고 AccessDeniedException을 던진다.
3. 인증이 되지 않았기때문에 ExceptionTranslationFilter는 인증을 하기위해 설정된 
    AuthenticationEntryPoint와 함께 로그인 페이지로 redirect 시킨다.
4. 브라우저가 로그인페이지를 요청한다.
5. 로그인 페이지를 렌더링한다.

username과 password가 제출되면

1.username과 password가 제출되면
AbstractAuthenticationProcessingFilter을 상속받은 UsernamePasswordAuthenticationFilter가
Authentication 타입의 UsernamePasswordAuthenticationToken을 만든다.

2. Token은 인증받기 위해 AuthenticationManager를 통과한다.
그 후 과정은 위의 AbstractAuthenticationProcessingFilter의 처리과정과 같다.

SpringSecurity는 기본적으로 form login이 활성화되어있지만,
서블릿기반 설정이 제공되면, form기반 로그인은 명시적으로 제공되어야한다.

// Login Form Configuration

public SecurityFilterChain filterChain(HttpSecurity http) {
	http
        .formLogin(form -> form
             .loginPage("/login")
             .permitAll()
         );
}

 

HTML form 

HTML form으로 요청을 보낼 때는 다음을 준수해야한다.
1. POST "/login"으로 요청을 보내야한다.
2. CSRF Token을 포함해야한다.
3. username/password의 변수명은 각각 username/password가 되어야한다. (설정 변경 가능)

 

계속...

'JAVA(SPRINGBOOT)' 카테고리의 다른 글

[SPRING SECURITY] 시큐리티 파헤치기 (4) - AUTHENTICATION  (0) 2023.03.23
[SPRING SECURITY] 시큐리티 파헤치기 (1)  (0) 2023.03.16
[JPA] PAGEABLE  (0) 2023.03.13
[SPRINGBOOT] 좋아요 수 표현  (0) 2023.03.09
[JAVA] VECTOR  (0) 2023.01.26