주의
이 책은 Spring Security 5 버전을 기준으로 작성되었으므로, Spring Boot 3.X 버전에서는 일부 클래스가 더 이상 사용되지(deprecated) 않을 수 있습니다.
1. 기본 구성이란?
부연 설명
- AuthenticationFilter는 인증 요청을 AuthenticationManager에 위임하고 응답을 바탕으로 SecurityContext를 구성
- AuthenticationManager는 AuthenticationProvider를 이용해 인증을 처리
- AuthenticationProvider는 인증 논리를 구현
- AuthenticationProvider는 사용자 관리 책임을 구현하는 UserDetailsService를 인증 논리에 이용
- AuthenticationProvider는 암호 관리를 구현하는 PasswordEncoder를 인증 논리에 이용
- SecurityContext는 인증 프로세스 후 인증 데이터를 유지함
UserDetailsService
- 사용자에 관한 세부 정보는 스프링 시큐리티로 UserDetailsService 계약을 구현하는 객체가 관리함
- UserDetailsService 구현체는 애플리케이션의 내부 메모리에 기본 자격 증명을 등록하는 일만 수행
- 기본 자격 증명에서 사용자 이름은 'user'
- 기본 암호는 UUID 형식이며 암호는 스프링 컨텍스트가 로드될 때 자동으로 생성되며 현재 애플리케이션은 암호를 볼 수 있도록 콘솔에 출력
- 위 기본 구현은 개념 증명의 역할을 수행하며 스프링 시큐리티 종속성이 잘 적용되는 것을 확인하는 역할
- 운영 단계 애플리케이션에서 기본 구현을 그대로 사용하는 것은 지양해야 함
PasswordEncoder
- PasswordEncoder는 두 가지 일을 수행
- 암호를 인코딩
- 암호가 기존 인코딩과 일치하는지 확인
- UserDetailsService 객체와 마찬가지로, PasswordEncoder 객체도 Basic 인증 흐름에 꼭 필요함
- 가장 단순한 구현에서는 암호를 평문으로 관리하고 인코딩하지 않음
- 현재로서는 PasswordEncoder가 기본 UserDetailsService와 함께 존재한다고 인지하는 것으로 충분
- UserDetailsService의 기본 구현을 대체할 때는 PasswordEncoder 또한 지정해줘야 함
AuthenticationProvider
- AuthenticationProvider는 인증 논리를 정의하고 사용자와 암호의 관리를 위임함
- AuthenticationProvider의 기본 구현은 UserDetailsService와 PasswordEncoder에 제공된 기본 구현을 이용
2. 기본 구성 재정의
- 기본 구성 요소를 재정의하는 옵션을 알아야 하는 이유는 맞춤형 구현을 연결하고 애플리케이션에 맞게 보안을 적용하는 방법이기 때문
UserDetailsService 구성 요소 재정의
- 직접 구현을 만들거나 스프링 시큐리티에 있는 구현을 이용하는 두 가지 옵션이 있음
- 이번 절에서는 스프링 시큐리티에 있는 InMemoryUserDetailsManager라는 구현을 이용
- 해당 구현은 메모리에 자격 증명을 저장해서 스프링 시큐리티가 요청을 인증할 때 이용할 수 있게 함
- UserDetailsService 형식의 인스턴스를 정의하기 위해 다음과 같은 작업이 필요함
- 자격 증명 (사용자명 및 암호)이 있는 사용자를 하나 이상 생성
- 사용자를 UserDetailsService에서 관리하도록 추가
- 주어진 암호를 UserDetailsService가 저장하고 관리하는 암호를 이용해 검증하는 PasswordEncoder 형식의 빈을 정의
- 먼저 InMemoryUserDetailsManager의 인스턴스를 대상으로 인증하는 데 이용할 수 있는 자격 증명 집합을 선언하고 추가
- 해당 절에서는 미리 정의된 빌더를 이용해 UserDetails 형식의 객체를 생성
- 인스턴스를 생성할 때는 사용자 이름과 암호, 그리고 하나 이상의 권한을 지정해야 함
- 권한 (Authority)은 해당 사용자에게 허용된 작업이며 예제에서는 권한을 이용하지 않으므로 일단 아무 문자열이나 지정하면 됨
- 기본 UserDetailsService를 이용하면 PasswordEncoder도 자동 구성되지만, UserDetailsService를 재정의하면 PasswordEncoder도 다시 선언해야 함
엔드포인트 권한 부여 구성 재정의
- 기본 구성에서 모든 엔드포인트는 애플리케이션에서 관리하는 유효한 사용자가 있다고 가정
- 기본적으로 HTTP Basic 인증을 권한 부여 방법으로 이용하지만 해당 구성은 손쉽게 재정의 가능
- HTTP Basic 인증은 대부분의 애플리케이션 아키텍처에 적합하지 않음
- 애플리케이션의 모든 엔드포인트를 보호할 필요는 없으며 보안이 필요한 엔드포인트에 다른 권한 부여 규칙을 선택해야 할 수도 있음
- 이러한 변경을 위해 WebSecurityConfigurerAdpater 클래스를 확장하는 것부터 시작 가능
다른 방법으로 구성 설정
- 스프링 시큐리티의 구성을 작성할 때 혼동되는 점은 여러 가지 방법으로 같은 구성을 만들 수 있다는 것
- 해당 절에서는 UserDetailsService와 PasswordEncoder를 구성하는 다른 방법을 다룸
- 구성 클래스에서 두 객체를 빈으로 정의하는 대신 configure(AuthenticationManagerBuilder auth) 메서드로 설정 가능
- 아래 예제 코드에 나온 것처럼 WebSecurityConfigurerAdapter 클래스의 해당 메서드를 재정의해서 AuthenticationManagerBuilder 형식의 매개변수로 UserDetailsService와 PasswordEncoder를 설정할 수 있음
- 컨텍스트에 빈을 추가하는 옵션을 이용하면 필요할 가능성 있는 다른 클래스에 값을 주입할 수 있다는 장점이 있음
- 반면, 다른 클래스에 값을 주입할 필요가 없을 때는 configure(AuthenticationManagerBuilder auth) 메서드를 추천
- 단, 구성을 혼합하면 헷갈릴 수 있어 권장하지 않음
- 아래처럼 구성을 혼합하면 UserDetailsService 및 PasswordEncoder의 연결이 어디인지 알기가 쉽지 않음
AuthenticationProvider 구현 재정의
- AuthenticationProvider는 인증 논리를 구현하고 사용자 관리와 암호 관리를 각각 UserDetailsService 및 PasswordEncoder에 위임함
- 스프링 시큐리티 아키텍처에 반영된 책임은 유지하는 것이 좋으며 해당 아키텍처는 세분화된 책임과 느슨하게 결합되어 있음
- 스프링 시큐리티가 유연하고 애플리케이션에 통합하기 쉬운 이유는 바로 해당 설계 덕분
- 그러나 유연성을 활용하는 방법에 따라 해당 설계 또한 바꿀 수 있지만 이러한 접근법은 솔루션을 복잡하게 만들 수 있으므로 주의 필요
- i.g. UserDetailsService나 PasswordEncoder가 더는 필요 없도록 기본 AuthenticationProvider를 재정의할 수 있음
프로젝트에 여러 구성 클래스 이용
- 앞서 구현한 여러 에제에서는 하나의 구성 클래스만 사용했지만 구성 클래스도 책임을 분리하는 것을 권장
- 분리가 필요한 이유는 구성이 복잡해지기 때문
- 프로젝트를 이해하기 쉽게 만들기 위해 둘 이상의 구성 클래스를 만드는 것이 좋음
- 항상 한 클래스가 하나의 책임을 맡도록 하는 것이 바람직함
- 아래 예제에서는 권한 부여 구성에서 사용자 관리 구성을 분리할 수 있음
- UserManagementConfig 클래스는 사용자 관리를 담당하는 UserDetailsService 및 PasswordEncoder의 두 빈만 포함
- WebAuthorizationConfig 클래스는 WebSecurityConfigurerAdapter를 확장하고 configure(HttpSecurity http) 메서드를 재정의
참고
스프링 시큐리티 인 액션
반응형