[Spring Security] 인증(Authentication)

kwang-sub·2024년 12월 24일

아래는 시큐리티 프로세스를 다루고 있는 이미지이며 이번 글에서는 인증에 대해서 알아보도록 하자.

AuthenticationProvider

인증 공급자를 의미하며 인증 논리를 정의하는 역할을 한다.
인증 공급자는 Authentication이라는 인증 방식이라는 역할과 강하게 결합되어 있는데 유효한 인증인지 검증을 하는 역할을 한다.

Authentication는 자바 시큐리티의 Principal을 확장하고 있어 다른 프레임워크 호환성도 가지도록 했다. 아래는 Authentication에 대한 설명이다.

public interface Authentication extends Principal, Serializable {
	// 인증된 요청에 허가된 권한 컬렉션을 반환
	Collection<? extends GrantedAuthority> getAuthorities();
	// 인증 프로세스에 이용된 암호나 비밀을 반환
    Object getCredentials();
    // 인증된 사용자 정보를 잔환
	Object getDetails();
    
	Object getPrincipal();
    // 인증이 완료여부를 반환
	boolean isAuthenticated();
	void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;

}

AuthenticationProvider는 이러한 Authentication을 매개변수로 갖는 authenticate() 함수를 통해 인증 논리를 정의한다. 인증에 실패할 경우 AuthenticationException을 던진다.

public interface AuthenticationProvider {
	// 인증 논리를 정의
	Authentication authenticate(Authentication authentication) throws AuthenticationException;
	// 인증 가능 수단 여부 반환
	boolean supports(Class<?> authentication);
}

UserDetails

스프링 시큐리티에서 사용자를 의미한다. 우리가 애플리케이션에 사용자 정보를 따로 관리하고 있지만 시큐리티는 해당 정보가 사용자 정보인지 알 수 없다.

따라서 보통 DB 스키마에서 데이터를 가져와 시큐리티에서 사용할 사용자 정보와 논리적으로 동일한 의미인지를 정의하는 역할을 한다.

아래는 인터페이스며 각 항목들은 주석으로 설명을 작성해두었으며 default 메서드의 경우 유효한 사용자에 대한 별다른 요구사항이 없으면 true를 반환하도록 두면 된다.

public interface UserDetails extends Serializable {

	// 사용자에게 부여된 권한 그룹을 반환
	Collection<? extends GrantedAuthority> getAuthorities();

	// 사용자 패스워드를 반환
	String getPassword();
	
    // 사용자명(아이디와 같은 식별자)를 반환
	String getUsername();

	// 계정 만료하지 않았는지 여부 반환
	default boolean isAccountNonExpired() {
		return true;
	}

	// 계정 잠금되지 않았는지 여부 반환
	default boolean isAccountNonLocked() {
		return true;
	}

	// 계정 자격증명 만료되지 않았는지 여부
	default boolean isCredentialsNonExpired() {
		return true;
	}

	// 계정 활성화 여부
	default boolean isEnabled() {
		return true;
	}
}

UserDetailsService

사용자명으로 UserDetails를 반환하는 메서드를 가지며 인증에서 사용자명에 맞는 사용자를 찾고 세부정보를 제공하는 역할을 한다.

UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;

UserDetailsManager

UserDetailsService를 확장하고 있으며 다음과 같은 사용자 추가, 수정, 삭제 기능이 추가된다. 구현체로는 JdbcUserDetailsManager가 그나마 애플리케이션에서 사용할듯한데 users, authorities라는 테이블로 사용자를 관리하며 쿼리도 자동으로 작성해주지만(커스텀 가능한듯하다) 굳이 해당 인터페이스를 구현하면서까지는 사용할 필요가 없을거 같다.

PasswordEncoder

사용자의 비밀번호를 암호화하고 암호화된 비밀번호와 인증하는 암호가 같은지 확인하는 역할을 한다. 따라서 아래 메서드를 정의하고 있다.

public interface PasswordEncoder {

	String encode(CharSequence rawPassword);

	
	boolean matches(CharSequence rawPassword, String encodedPassword);
	
	default boolean upgradeEncoding(String encodedPassword) {
		return false;
	}

}

여기서 defalut 메서드 하나가 있는데 해당 메서드는 인코딩된 암호를 한번 더 인코딩할건지 여부를 선택하는 것이다.

이러한 인터페이스를 구현해 PasswordEncoder를 정의해도 되지만 이미 스프링에서 제공되는 인코더들이 있으니 아래 표에서 골라쓰고 필요한 경우 구현하도록 하자.

종류특징
NoOpPasswordEncoder암호를 인코딩하지 않고 일반 텍스트로 사용하는 방식
StandardPasswordEncoderSHA-256을 이용한 해시 방식으로 약한 암호화 방식
Pbkdf2PasswordEncoderPBKDF2를 이용한 방식
BCryptPasswordEncoderbcrypt 강력 해싱 함수로 암호화하는 방식
SCryptPasswordEncoderscrypt 해싱 함수로 암호화하는 방식

DelegatingPasswordEncoder

보안에 완벽이라는 것은 없다. 그래서 지금은 유용한 암호한 방식이지만 언제든 취약점이 발견될 수 있다. 이러한 경우 암호화 방식을 바꿔야하는데 보통 이를 디코딩하는 방식은 제공하지 않고 있다.

따라서 이전 암호화 방식과 새로운 암호화 방식을 혼합해서 쓰는 방식을 제공하는 방법이다.
해당 구현체는 여러 암호화 방식을 Map<String, PasswordEncoder> 방식으로 관리하는데 키가 되는 String을 데이터 보관시에 아래와 같이 관리하면 해당 인코더를 사용해 인증하게 된다.

{Key}$2a%105jan2bnoBeVdsd.df23141dsEsdbae

이는 암호화 방식 변경시 유일한 방법은 아니며 좋은 방법중에 하나이다.

profile
백엔드 개발일지

0개의 댓글