Netplix 구독형 멤버십 프로젝트로 배우는 SpringSecurity
[Ch 3. UserDetails와 사용자 관리] - 01. UserDetails 살펴보기
강의를 바탕으로 실습 내용을 정리하였습니다.
목차
이번 시간에는 UserDetails 인터페이스에 대해 조금 더 디테일한 레벨에서 알아보자
계정 만료, 계정 잠금, 자격 증명 만료, 계정 비활성화 기능을 통해 사용자를 애플리케이션 수준에서 제한할 수 있음
1. 간단한 UserDetails 구현해보기
UserDetails, GrantedAuthority 등을 이용하여 기본적인 UserDetails 를 구현해보자
요구사항
- 사용자의 이름은 ”jinny.lee”
- 비밀번호는 평문으로 “12345”
- 읽기 권한이 필요함 (READ)
user 라는 패키지를 생성하고 하위에 JinnyUser 클래스 생성
- UserDetails 인터페이스를 상속받도록 구현
Username 과 Password 구현
Username 과 Password 를 요구사항에 맞춰 입력하며 고정된 값을 반환하도록 함
Authorities 구현
읽기 (READ) 권한 추가
사용자 제한 메소드 구현
사용자를 제한하는 로직을 포함시키지 않을 것이기 때문에 별도의 코드 작성은 필요 없음
- default 메소드이기 때문에 이미 true 를 반환하고 있음
JinnyUser 에 대한 테스트 작성
간단한 단위 테스트를 작성하여 DannyUser 가 요구사항대로 구현되었는지 확인
package fast.campus.fcss01.user;
...
class JinnyUserTest {
@Test
void jinnyUserTest() {
// given & when
JinnyUser jinny = new JinnyUser();
// than
assertThat(jinny.getUsername()).isEqualTo("jinny.lee");
assertThat(jinny.getPassword()).isEqualTo("12345");
assertThat(jinny.getAuthorities().size()).isEqualTo(1); // size는 READ 권한 1개 있기 때문에 1
Optional<? extends GrantedAuthority> read = jinny.getAuthorities()
.stream()
.filter(authority -> authority.getAuthority().equals("READ"))
.findFirst();
read.ifPresent(each -> assertThat(each.getAuthority()).isEqualTo("READ"));
}
}
2. 좀 더 현실적인 UserDetails 활용법
방금까지 살펴본 방식은 JinnyUser 라는 클래스가 한 명의 사용자를 나타냄
- 여러 인스턴스를 만들더라도 같은 사용자를 의미함
- 실제 애플리케이션에서는 해당 방법을 사용할 수 없음
각기 다른 사용자를 표현할 수 있는 클래스 형태로 만들어야 함
- username 과 password 를 입력받을 수 있도록 별도 변수를 추가
- 생성자를 통해 username 과 password 를 입력받음
package fast.campus.fcss01.user;
...
public class SimpleUser implements UserDetails {
private final String username;
private final String password;
public SimpleUser(String username, String password) {
this.username = username;
this.password = password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(() -> "READ");
}
@Override
public String getPassword() {
return this.password
}
@Override
public String getUsername() {
return this.username;
}
}
UserDetails 생성을 위해 빌더 패턴 활용하기
스프링 시큐리티 라이브러리에서 제공하는 User 클래스에는 UserDetails 생성을 위해 빌더를 제공함
- 이 빌더를 활용하기 위해서는 사용자 이름과 암호가 필요함
- 정보 입력 후 .build() 를 통해 UserDetails 를 생성
다른 필드에 대한 빌더도 제공됨
User 엔티티와 UserDetails (1)
일반적으로 사용자 정보는 데이터베이스에서 관리되기 때문에 영속성 엔티티를 표현하는 클래스가 필요함
- 영속성 엔티티 클래스 뿐만 아니라 별도로 사용자를 표현하는 클래스도 필요함 (다른 시스템으로 전송하거나 내부적으로 활용할 목적으로)
사용자에 대한 영속성 엔티티는 다음과 같이 표현할 수 있음
- spring-data-jpa 에 대한 의존성을 추가
- @Entity 어노테이션 활용
- 엔티티임을 표현하기 위해 @Id 어노테이션 활용
dependencies {
...
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
...
}
package fast.campus.fcss01.user;
...
@Entity
@Getter
public class UserEntity {
@Id
private Long id;
private String username;
private String password;
private String authority;
}
User 엔티티와 UserDetails (2)
영속성 엔티티에 스프링 시큐리티의 사용자 세부 정보까지 동일한 클래스로 구현하면 어떻게 될까?
- UserDetails 에서 재정의해야 하는 getUsername 과 영속성 엔티티에서 @Getter 로 제공되는 getUsername 이 동일해지는 상황이 발생
- 만약 다른 로직이 적용되어야 하는 상황이 된다면 변수명을 다르게 하거나 로직이 상당히 복잡해질 수 있음
이런 코드 작성은 안티 패턴으로 지양해야 함
- 두 책임 (책임 1. 데이터베이스 관련 영속성 엔티티, 책임 2. 스프링 시큐리티의 사용자 정보)이 혼합되어 있기 때문
User 엔티티와 UserDetails (3)
해결을 위해서는 별도의 클래스를 구현해서 사용해야 함
- 영속성 엔티티를 표현하는 UserEnttiy
- 스프링 시큐리티의 사용자 정보를 표현하는 User
package fast.campus.fcss01.user;
...
@Entity
@Getter
public class **UserEntity** {
@Id
private Long id;
private String username;
private String password;
private String authority;
}
package fast.campus.fcss01.user;
...
public class **User** implements UserDetails {
private final UserEntity userEntity;
// 여기서 영속성 엔티티를 바로 입력하는 것은 좋지 않을수도 있음
public User(UserEntity userEntity) {
this.userEntity = userEntity;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of(userEntity::getAuthority);
}
@Override
public String getPassword() {
return userEntity.getPassword();
}
@Override
public String getUsername() {
return userEntity.getUsername();
}
}
핵심은 클래스를 분리하는 것
GitHub
https://github.com/lpromotion/fcss-01/commit/0bbdf08faee91d3aabb789d36f6f5cf616f30b69
'Course > Spring Security' 카테고리의 다른 글
[netplix-security-a] AuthenticationProvider 구현 (0) | 2024.10.03 |
---|---|
[netplix-security-a] AOP를 활용하여 비밀번호 암호화하기 (1) | 2024.10.03 |
[netplix-security-a] PasswordEncoder 구현 (0) | 2024.10.03 |
[netplix-security-a] UserDetailsService 와 UserDetailsManager 구현 (1) | 2024.09.29 |
[netplix-security-a] 기본 구성 재정의 (0) | 2024.09.29 |
댓글