💡 오늘의 학습 목표
✔ Custom UserDetailsService를 사용하는 방법
일반적으로 Spring Security에서는 인증을 시도하는 주체를 User 라고 부른다.
Principal은 User의 더 구체적인 정보를 의미. (Spring Security에서의 Username의미)
- 샘플 애플리케이션에서 Member 엔티티 클래스가 로그인 인증 정보를 포함하는데 이 Member엔티티가 Spring Security의 User 정보를 포함함.
1. SecurityConfiguration의 설정 변경 및 추가
InMemory User를 위한 설정 제거
- (1)
frameOptions()
는 HTML 태그 중에서<frame>
이나<iframe>
,<object>
태그에서 페이지를 렌더링 할지의 여부를 결정하는 기능
Spring Security에서는 Clickjacking 공격을 막기위해 기본적으로 기능이 활성화 되어 있으며 디폴트 값은DENY
즉 위의 태그를 이용한 페이지 렌더링을 허용하지 않겠다는 의미- (1) 처럼
.frameOptions().sameOrigin()
을 호출하면 동일 출처로부터 들어오는 request만 페이지 렌더링 허용
개발환경에서는 H2 웹 콘솔 정상 사용을 위해 (1)과 같이 설정2. JavaConfiguration의 Bean 등록 변경
- (1)과 같이 데이터베이스에 User의 정보를 저장하기 위해 MemberService 인터페이스의 구현 클래스를 DBMemberService로 변경
- (1-1) MemberRepository 와 PasswordEncoder 객체를 DI 해준다.
3. DBMemberService 구현
User의 인증 정보를 데이터베이스에 저장하는 역할
Member 엔티티 클래스의 필드에 인증 정보를 담는 password 필드가 포함된다고 생각
- (1) 생성자를 통해 MemberRepository와 PasswordEncoder Bean 객체를 DI 받는다.
- (2) PasswordEncoder를 이용해 패스워드를 암호화
- (3) 암호화 된 패스워드를 password 필드에 다시 할당
⭐ 패스워드의 암호화
- 패스워드 같은 민감한 정보는 반드시 암호화 되어 저장되어야 한다.
패스워드는 복호화 할 이유가 없기 때문에 단방향 암호화 방식으로 암호화 되어야 한다.4. Custom UserDetailsService 구현
데이터베이스에서 조회한 User의 인증 정보를 기반으로 인증을 처리하는 클래스 구현⭐ UserDetailsService
- Spring Security에서 제공하는 컴포넌트 중 하나인
UserDetailsService
는 User 정보를 로드하는 핵심 인터페이스.
로드 = 인증에 필요한 User 정보를 어딘가에서 가지고 온다.
어딘가 = 메모리가 될 수도, DB 등 영구 저장소가 될 수도 있다.
UserDetailsManager
는UserDetailsService
를 상속하는 확장 인터페이스
- 이 클래스와 같은 Custom UserDetailsService를 구현하기 위해서는 (1) 처럼
UserDetailsService
인터페이스를 구현해야 한다.- 위 클래스는 데이터베이스에서 User를 조회하고, 조회한 User의 권한 (Role) 정보를 생성하기 위해서 (2) 처럼 MemberRepository 와 HelloAuthorityUtils 클래스를 DI 받는다.
UserDetailsService
인터페이스를 implemnts 하는 구현 클래스는 (3) 처럼loadUserByUsername(String username)
추상 메서드를 구현해야 한다.- (4)
HelloAuthorityUtils
를 이용해서 데이터베이스에서 조회한 회원 이메일 정보를 이용해 Role 기반의 권한 정보(GrantedAuthority) 컬렉션 생성.- 데이터베이스에서 조회한 인증 정보와 (4) 에서 생성한 권한 정보를 Spring Security에 제공해주어야 하며, (5) 에서
UserDetails
인터페이스 구현체인User
클래스의 객체를 통해서 제공한다.
- (5)와 같이 데이터베이스에서 조회한 User 클래스의 객체를 리턴하면 Spring Security가 이 정보를 이용해 인증 절차 수행.
⭐ 데이터베이스에서 User의 인증 정보만 Spring Security에게 넘겨주고, 인증 처리는 Spring Security가 대신해 준다.
- UserDetails
UserDetailsService에 의해 로드되어 인증을 위해 사용되는 핵심 User 정보를 표현하는 인터페이스.
UserDetails 인터페이스의 구현체는 Spring Security에서 보안 정보 제공을 목적으로 직접 사용x,Authentication
객체로 캡슐화 되어 제공✔ HelloAuthorityUtils
HelloUserDetailsService 에서 Role 기반의 User 권한을 생성하기 위해 사용
- (1) application.yml에 추가한 프로퍼티를 가져오는 표현식
@Value("${프로퍼티 경로}")
미리 정의한 관리자 권한을 가질 수 있는 이메일 주소를 불러오고 있음.
- application.yml 파일에 정의한 관리자용 이메일 주소는 회원 등록 시, 특정 이메일 주소에 관리자 권한을 부여할 수 있는지 여부를 결정하기 위해 사용
- (2) Spring Security에서 지원하는
AuthorityUtils
클래스를 이용해서 관리자용 권한 목록을List<GrantedAuthority>
객체로 미리 생성 ( 일반 사용자의 권한까지 추가 포함 )- (3) 일반 사용 권한 목록을 객체로 미리 생성
- (4) 파라미터로 전달 받은 이메일 주소가 yml 파일에서 가져온 관리자용 이메일 주소와 동일하다면 관리자용 권한인
List<GrantedAuthority> ADMIN_ROLES
를 리턴⭐ 실무에서는 회원 가입 시 관리자 등록을 위한 추가적인 인증 절차가 있다.
5. H2 웹 콘솔에서 등록한 회원 정보 확인 및 로그인 인증 테스트
- 애플리케이션 실행 후 회원가입으로 회원 등록 -> H2 콘솔 확인
- 비밀번호가 암호화되어 저장된 것을 볼 수 있다.
6. Custom UserDetails 구현
✔ 개선된 HelloUserDetailsService(V2)
기존 loadUserByUsername() 의 리턴값을 (1)과 같이 Custom UserDetails 클래스의 생성자로 findMember를 전달하도록 개선
기존 loadUserByUsername() 메서드 내부에서 User의 권한 정보를 생성하는
Collection<? extends GrantedAuthority> authorities = authorityUtils.createAuthorities(findMember);
코드는 (2) 의 클래스 내부로 포함됨.(2)
HelloUserDetails
클래스는UserDetails
인터페이스 구현하며Member
엔티티 클래스 상속🔍 데이터베이스에서 조회한 회원 정보를 Spring Security의 User 정보로 변환하는 과정과 User의 권한 정보를 생성하는 과정을 캡슐화 할 수있다.
또한,HelloUserDetails
클래스는 Member 엔티티클래스를 상속하고 있기 때문에 HelloUserDetails를 리턴받아 사용하는 측에서는 두 개 클래스 객체를 모두 캐스팅해서 사용 가능하다는 장점이 있다.(2-3)
HelloAuthorityUtils
의createAuthorities()
메서드로 User의 권한 정보 생성 (기존과 달리 HelloUserDetails 클래스 내부에서 사용되도록 캡슐화됨)(2-4) Spring Security에서 인식 가능한 username을 Member 클래스의 email 주소로 채운다.
getUsername()
의 리턴 값은 null일 수 없다.7. User의 Role을 DB에서 관리하기
현재 User의 권한 정보는 데이터베이스에서 조회한 User 정보를 기준으로 코드상에서 조건에 맞게 생성하고 있다 -> 권한 정보를 데이터베이스에서 관리하도록 코드 수정
- User의 권한 정보를 저장하기 위한 테이블 생성
- 회원 가입 시, User의 권한 정보(Role)를 데이터베이스에 저장하는 작업
- 로그인 인증 시, User의 권한 정보를 데이터베이스에서 조회하는 작업
✔ User의 권한 정보 테이블 생성
JPA를 이용하여 User와 User의 권한 정보 간의 연관 관계 맺기
- Member 엔티티 클래스에 추가
- (1) 과 같이 List, Set 같은 컬렉션 타입의 필드는
@ElementCollection
애너테이션 추가하면 User 권한 정보와 관련된 별도의 엔티티 클래스를 생성하지 않아도 간단하게 매핑 처리 가능✔ 회원 가입 시, User의 권한 정보(Role)를 데이터베이스에 저장
MEMBER_ROLES
테이블
- DBMemberService 클래스
- (1) 에서 회원의 권한 정보(
List<String>roles
) 생성한 뒤 member 객체에 넘겨주고 있다.
- HelloAuthorityUtils
- (1) 파라미터로 전달 된 이메일 주소가 application.yml 파일의 mail.address.admin 프로퍼티에 정의된 주소와 동일하면 관리자 Role 목록을 리턴하고 그 외는 일반 사용자 목록을 리턴
✔ 로그인 인증 시, User의 권한 정보를 데이터베이스에서 조회하는 작업
- 개선된 HelloUserDetailsService(V3)
- (1) HelloUserDetails가 상속하고 있는 Member(extend Member) 에 데이터베이스에서 조회한
List<String> roles
를 전달- (2) 다시 Member에 전달한 Role 정보를 authorityUtils.createAuthorities() 메서드의 파라미터로 전달해서 권한 목록 (
List<GrantedAuthority>
) 생성
- HelloAuthorityUtils
- 단순히 데이터베이스에서 가지고 온 Role 목록(
List<String>roles
)을 그대로 이용해서 권한 목록(authorities) 를 만든다.
- (2)와 같이
SimpleGrantedAuthority
객체를 생성할 때 생성자 파라미터로 넘겨 주는 값이ROLE_USER
와 같은 형태로 넘겨주어야 한다.