본문 바로가기

Spring

[Spring Security] 스프링 시큐리티에 대한 흐름 이해

Spring Security는 Java 기반의 애플리케이션에서 보안 인증과 권한 부여를 처리하기 위한 프레임워크이다. 스프링 시큐리티는 다양한 인증 메커니즘을 지원하고, 다양한 방식으로 사용자 인증을 구현할 수 있다. 이번 블로그를 통해 스프링 시큐리티의 동작원리에 대해 이해하고, 대표적인 특징에 대해 알아보려고 한다.


1. 스프링 시큐리티의 구조

출처 : https://chathurangat.wordpress.com/2017/08/23/spring-security-authentication-architecture/

1. Http Request 수신

클라이언트로부터 로그인정보와 함께 인증 요청을 하게 되면 AuthenticationFilter에서 인증 및 권한 부여의 목적으로 일련의 필터를 거치게 된다.

 

로그인이 이미 되어있다면!!

이미 Authentication 객체가 Http Session에 저장되어 있기 때문에 AuthenticationManager의 authenticate() 메서드를 호출하지 않고 SecurityContextHolder에서 Authenticatin 객체를 가져와 인증 후 곧바로 클라이언트의 요청을 수행한다.

 

▶ 시큐리티에서 제공하는 필터(유저 인증 관련)

  • UsernamePasswordAuthenticationFilter : 사용자의 이름과 비밀번호를 사용한 기본 폼 기반 인증을 처리하는 필터로, 사용자가 로그인을 하게 되면 이 필터가 사용자를 인증하고 세션에 사용자 정보를 저장한다.
  • DefaultLoginPageGeneratingFilter : 기본 로그인 페이지를 생성하고 제공하는 역할
  • DefaultLogoutPageGeneratingFilter : 기본 로그아웃 페이지를 생성하고 제공하는 역할
  • RememberMeAuthenticationFilter : 사용자가 이전에 로그인한 정보를 기반으로 자동 로그인을 지원하는 역할
  • RequestCacheAwareFilter : 인증되지 않은 사용자가 접근하려는 리소스에 대한 요청을 캐시 하고, 사용자가 로그인한 후 이전 요청으로 돌아갈 수 있게끔 도와주는 필터
  • OAuth2LoginAuthenticationFilter : OAuth2.0을 통한 로그인을 처리하는 필터. 외부 로그인 서비스(카카오톡, 네이버, 구글 등)를 지원한다.
  • JwtAuthenticationFilter : JWT(JSON Web Token)를 사용한 인증을 처리하는 필터. 클라이언트가 JWT 토큰을 제공하면 필터가 해당 토큰으로 사용자를 인증한다.
  • CasAuthenticationFilter : Central Authentication Service(CAS)를 통해 인증하는 필터로, 싱글 로그인(SSO, Single Sign-On) 환경에서 사용된다. (SSO : 여러개의 사이트에서 한 번의 로그인으로 여러 가지 다른 사이트들을 자동적으로 접속하여 이용할 수 있도록 하는 서비스)

2. 사용자 자격증명을 기반으로 한 AuthenticationToken 생성

유저의 인증 요청에서 AuthenticationFilter에 의해 유저의 이름과 패스워드를 추출한다. 추출된 정보를 기반으로 인증 토큰(UsernamePasswordAuthenticationToken)이 생성된다. 생성된 토큰은 AuthenticationManager의 authenticate() 메서드를 통해 ProviderManager로 넘어가게 된다.

3. AuthenticationManager에게 인증 토큰 위임

UsernamePasswordAuthenticationToken 개체 생성 후 AuthenticationManger의 인증 메서드를 호출하는 데 사용된다. AuthenticationManager 인터페이스이고 실제 구현은 ProviderManager이기에 해당 객체에 인증 토큰을 전달한다.

 

public class ProviderManager implements AuthenticationManager, MessageSourceAware, InitializingBean {
	// ...
	private List<AuthenticationProvider> providers = Collections.emptyList();
	// ...
	@Override
	public Authentication authenticate(Authentication authentication) throws AuthenticationException {
		Class<? extends Authentication> toTest = authentication.getClass();
		AuthenticationException lastException = null;
		AuthenticationException parentException = null;
		Authentication result = null;
		Authentication parentResult = null;
		int currentPosition = 0;
		int size = this.providers.size();
		for (AuthenticationProvider provider : getProviders()) {
			if (!provider.supports(toTest)) {
				continue;
			}
			if (logger.isTraceEnabled()) {
				logger.trace(LogMessage.format("Authenticating request with %s (%d/%d)",
						provider.getClass().getSimpleName(), ++currentPosition, size));
			}
			try {
				result = provider.authenticate(authentication);
				if (result != null) {
					copyDetails(authentication, result);
					break;
				}
			}
			catch (AccountStatusException | InternalAuthenticationServiceException ex) {
				prepareException(ex, authentication);
				throw ex;
			}
			catch (AuthenticationException ex) {
				lastException = ex;
			}
		}
		if (result == null && this.parent != null) {
			try {
				parentResult = this.parent.authenticate(authentication);
				result = parentResult;
			}
			catch (ProviderNotFoundException ex) {
			}
			catch (AuthenticationException ex) {
				parentException = ex;
				lastException = ex;
			}
		}

	}
}

위 클래스는 AuthenticationMananger의 구현체인 ProviderManager 클래스이다. 클래스의 변수를 보면 AuthenticationProvider를 리스트로 관리하고 있다. 그리고 authenticate() 메서드에서 for문으로 리스트를 순환하며 provider 클래스의 supports() 메서드를 호출하며 인증 메커니즘을 수행하도록 한다.

4. Authentication Provider 목록으로  사용자 인증 처리

  • DaoAuthenticationProvider : 유저 이름과 비밀번호를 사용한 인증 처리
  • JwtAuthenticationProvider : JWT를 사용한 인증 처리

AuthenticationProvider(인증 제공자)의 구현체는 특정 인증 메커니즘에 대한 인증을 수행하며(다중 인증 메커니즘 지원), 사용자의 자격 증명을 확인하고, 사용자를 인증한다. 인증에 성공하면 인증된  Authentication 객체를 반환한다.

※ 개발자가 커스텀하여 AuthenticationProvider를 구현하여 고유한 인증로직을 구현할 수도 있음.

5. UserDetailsService 에 Authentication 넘기기

UserDetailsService는 유저의 정보를 가져오는 인터페이스로 해당 인터페이스를 구현하기 위해서는  DB로부터 유저의 정보를 불러와 UserDetails로 반환하는 loadUserByUsername 메서드를 구현해야 한다.

  • 사용자 이름을 기반으로 사용자의 세부 정보를 검색할 수 있다.
메서드 리턴 타입 설명
loadUserByUsername UserDetails DB로 부터 유저의 정보를 불러와서 UserDetails로 리턴

6. UserDetailsService로부터 UserDetails 검색

UserDetails 인터페이스는 사용자 정보를 캡슐화하고 사용자의 인증 및 권한 정보를 Spring Security와 함께 사용할 수 있도록 설계되었다. DB에서 사용자의 정보를 토대로 검색하고, 해당 데이터를 UserDetails 객체에 매핑시켜 반환한다.

 

UserDetails의 주요 메서드 및 속성

메서드 리턴타입 설명
getAuthorities() Collection<? extends GrantedAuthority> 사용자에게 부여된 권한 목록을 반환
getPassword() String 사용자의 암호화된 비밀번호 반환
getUsername() String 사용자의 이름 반환
isAccountNonExpired() boolean 사용자의 계정이 만료되었는지의 여부
isAccountNonLocked() boolean 사용자의 계정이 잠겨있는지 여부
isCredentialsNonExpired() boolean 사용자의  자격증명(비밀번호)이 만료되었는지 여부
isEnabled() boolean 사용자의 계정이 활성화되었는지 여부

7. AuthenticationProviders에게 UserDetails 객체 전달

AuthenticationProviders는 UserDetails를 받아 유저 정보를 비교한다.

8. 성공 시 인증개체 반환 OR 실패 시 인증 예외 발생

인증 완료 시 Principal(주체), Credentials(자격 증명), Authorities(권한), Authenticated(인증 여부)를 설정한 Authentication 객체를 반환하고, 인증에 실패하면 상황에 맞는 예외를 발생시킨다.

 

Authentication 내부 구조

▶ Principal(주체) : 사용자 아이디 혹은 User 객체

  • getPrincipal() 메서드를 통해 사용자를 나타내는 주체(Principal)를 얻을 수 있다.
  • 주체는 보통 사용자의 식별정보(사용자 이름, ID 등)를 나타내며, UserDetails 객체로 나타낼 수 있다.

Credentials(자격 증명) : 사용자 비밀번호

  • getCredentials() 메서드를 통해 사용자의 자격 증명(비밀번호, 토큰)을 얻을 수 있다.
  • 자격 증명은 사용자를 인증하기 위한 정보이다.

Authorities(권한) : 인증된 사용자의 권한 목록

  • getAuthorities() 메서드를 통해 사용자에게 부여된 권한목록을 얻을 수 있다.
  • 권한은 사용자가 수행할 수 있는 작업이나 접근할 수 있는 리소스를 나타낸다.

Authenticated(인증여부)

  • isAuthenticated() 메서드는 사용자가 인증되었는지 여부를 나타내며, true, 또는 false 값을 반환한다.
  • 사용자가 인증되면 true, 그렇지 않으면 false를 반환한다.

 9. 인증 완료

AuthenticationManager는 Authentication 객체를 관련 AuthenticationFilter로 반환한다.

10. SecurityContext에 Authentication 객체 저장

SecurityContextHolder는 세션 영역에 있는 SecurityContext에 Authentication 객체를 저장한다.

 

Security Context

  • Authentication 객체가 직접 저장되는 저장소.
  • 필요시 언제든 SecurityContext에서 Authentication 객체를 가져올 수 있다. 
  • 동일 스레드 내에서 전역적으로 접근 가능하도록 설계(SecurityContextHolder Strategy에 따라 다름 )

정리

  1. 클라이언트가 로그인 정보(username, password)와 함께 인증 요청
  2. AuthenticationFilter(ex. UsernamePasswordAuthenticationFilter)에서 AuthenticationManager에 의해 attemptAuthentication() 메서드 호출
  3. UsernamePasswordAuthenticationToken 생성 후 인증 메커니즘 진행
  4. 인증 성공
  5. Authentication 객체에 Principal, Credentials, Authorities, Details, Authenticated 값을 초기화 후 반환
  6. SecuritySession을 만들고 SecurityContext에 저장
  7. 이후의 인증과정은 SecurityContextHolder 내부의 SecurityContext에 접근하여 확인
    • Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

참고하면 좋은 사이트

 

SSO(Single Sign-On)이란?

안녕하세요. 오늘은 SSO(Single Sign-On)에 대해서 알아보면서 개념 및 원리를 간략하게 정리해보려는 포스팅을 하려고 합니다. 1. SSO란 무엇인가? Single Sign-On의 약자로 여러 개의 사이트에서 한번의

toma0912.tistory.com

 

[스프링] Spring Security 인증은 어떻게 이루어질까?

예전 포스팅에서 security 관련 예제를 다룬적 있다. 당시에는 내부 프로세스를 모르고 예제를 보면서 UserDetails, UserDetailsService, Authentication만 커스텀해서 인증을 다뤘다. 이번 토이프로젝트에서 OAu

cjw-awdsd.tistory.com

 

Springboot JWT 로그인 - (3) UserDetails, UserDetailsService 이해

UserDetails, UserDetailsService를 왜 사용해야하는 것일까? 먼저 스프링 시큐리티의 동작을 이해해보자. 시큐리티는 "~/login" 주소로 요청이 오면 가로채서 로그인을 진행한다. 로그인 진행이 완료되면

ws-pace.tistory.com

 

스프링 시큐리티 기본편 정리

스프링 시큐리티 기본편 정리

velog.io