생각
기존 UserDetails 인터페이스의 인스턴스가 가진 필드 외에 email을 추가한 커스텀 UserDetails 클래스를 만들어 달라고 했다. 그 결과 아래와 같은 소스코드를 만들어 줬다.
일반 CustomUserDetails 클래스
1. CustomUserDetails 클래스
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class CustomUserDetails implements UserDetails {
private final String username; // 사용자 이름
private final String password; // 비밀번호
private final String email; // 이메일 필드 추가
private final boolean enabled; // 계정 활성화 여부
private final boolean accountNonExpired; // 계정 만료 여부
private final boolean accountNonLocked; // 계정 잠금 여부
private final boolean credentialsNonExpired; // 비밀번호 만료 여부
private final Collection<? extends GrantedAuthority> authorities; // 권한 목록
// 생성자
public CustomUserDetails(String username, String password, String email,
boolean enabled, boolean accountNonExpired,
boolean accountNonLocked, boolean credentialsNonExpired,
Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.email = email; // 이메일 초기화
this.enabled = enabled;
this.accountNonExpired = accountNonExpired;
this.accountNonLocked = accountNonLocked;
this.credentialsNonExpired = credentialsNonExpired;
this.authorities = authorities;
}
// UserDetails 인터페이스 메서드 구현
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
// 이메일 getter 추가
public String getEmail() {
return email;
}
}
2. UserDetailsService 수정
- loadUserByUsername 메서드에서 return문에 있는 CustomUserDetails를 수정해주면 된다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
// CustomUserDetails 객체 생성
return new CustomUserDetails(
user.getUsername(),
user.getPassword(),
user.getEmail(), // 이메일 추가
user.isEnabled(),
true, // 만료 여부 (필요에 따라 설정)
true, // 잠금 여부 (필요에 따라 설정)
true, // 비밀번호 만료 여부 (필요에 따라 설정)
authorities
);
}
Builder 패턴 사용한 CustomUserDetails 클래스
1. CustomUserDetails 클래스 (Builder 패턴 사용)
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class CustomUserDetails implements UserDetails {
private final String username;
private final String password;
private final String email;
private final boolean enabled;
private final boolean accountNonExpired;
private final boolean accountNonLocked;
private final boolean credentialsNonExpired;
private final Collection<? extends GrantedAuthority> authorities;
// private 생성자
private CustomUserDetails(Builder builder) {
this.username = builder.username;
this.password = builder.password;
this.email = builder.email;
this.enabled = builder.enabled;
this.accountNonExpired = builder.accountNonExpired;
this.accountNonLocked = builder.accountNonLocked;
this.credentialsNonExpired = builder.credentialsNonExpired;
this.authorities = builder.authorities;
}
// Builder 클래스
public static class Builder {
private String username;
private String password;
private String email;
private boolean enabled;
private boolean accountNonExpired;
private boolean accountNonLocked;
private boolean credentialsNonExpired;
private Collection<? extends GrantedAuthority> authorities;
public Builder username(String username) {
this.username = username;
return this;
}
public Builder password(String password) {
this.password = password;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder enabled(boolean enabled) {
this.enabled = enabled;
return this;
}
public Builder accountNonExpired(boolean accountNonExpired) {
this.accountNonExpired = accountNonExpired;
return this;
}
public Builder accountNonLocked(boolean accountNonLocked) {
this.accountNonLocked = accountNonLocked;
return this;
}
public Builder credentialsNonExpired(boolean credentialsNonExpired) {
this.credentialsNonExpired = credentialsNonExpired;
return this;
}
public Builder authorities(Collection<? extends GrantedAuthority> authorities) {
this.authorities = authorities;
return this;
}
public CustomUserDetails build() {
return new CustomUserDetails(this);
}
}
// UserDetails 인터페이스 메서드 구현
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
@Override
public boolean isAccountNonExpired() {
return accountNonExpired;
}
@Override
public boolean isAccountNonLocked() {
return accountNonLocked;
}
@Override
public boolean isCredentialsNonExpired() {
return credentialsNonExpired;
}
@Override
public boolean isEnabled() {
return enabled;
}
// 이메일 getter
public String getEmail() {
return email;
}
}
2. UserDetailsService 수정
- loadUserByUsername 메서드를 수정해서 Builder 패턴을 사용할 수 있다.
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
List<GrantedAuthority> authorities = user.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
// Builder 패턴을 사용하여 CustomUserDetails 객체 생성
return new CustomUserDetails.Builder()
.username(user.getUsername())
.password(user.getPassword())
.email(user.getEmail())
.enabled(user.isEnabled())
.accountNonExpired(true) // 필요에 따라 설정
.accountNonLocked(true) // 필요에 따라 설정
.credentialsNonExpired(true) // 필요에 따라 설정
.authorities(authorities)
.build();
}
빌더를 사용하고 안 하고의 차이
1. 가독성
- Builder 사용: 각 필드를 메서드 체인으로 설정할 수 있어서, 코드가 명확하고 읽기 쉽다.
- Builder 미사용: 모든 필드를 생성자에서 한 번에 전달해야 하므로, 길어지고 복잡해질 수 있다.
2. 유연성
- Builder 사용: 선택적인 필드를 쉽게 처리할 수 있다. 필요 없는 필드는 설정하지 않으면 된다. 기본값을 설정해두면 생략 가능하다.
- Builder 미사용: 모든 필드를 반드시 설정해야 하므로, 생성자 매개변수가 많아질 경우 관리가 힘들어질 수 있다. 모든 값을 명시적으로 설정해야한다.
3. 확장성
- Builder 사용: 새로운 필드를 추가할 때 Builder에만 추가하면 되므로, 기존 코드에 영향을 덜 끼친다.
- Builder 미사용: 기존 생성자에 필드를 추가하면 모든 호출 부분을 수정해야 할 수도 있다.
▼ Builder 사용
public static class Builder {
// ... 다른 필드
private boolean accountNonExpired = true; // 기본값 설정
private boolean accountNonLocked = true; // 기본값 설정
private boolean credentialsNonExpired = true; // 기본값 설정
// ... 빌더 메서드
}
// 사용 시
return new CustomUserDetails.Builder()
.username(user.getUsername())
.password(user.getPassword())
.email(user.getEmail())
.enabled(user.isEnabled())
.authorities(authorities)
.build();
▼ Builder 미사용
return new CustomUserDetails(
user.getUsername(),
user.getPassword(),
user.getEmail(),
user.isEnabled(),
true, // 계정 만료 여부
true, // 계정 잠금 여부
true, // 비밀번호 만료 여부
authorities
);
JPA를 활용한 UserEntity 클래스
1. UserEntity 클래스
import javax.persistence.*;
import java.util.List;
@Entity
@Table(name = "users") // 데이터베이스 테이블 이름
public class UserEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 고유 식별자
@Column(nullable = false, unique = true)
private String username; // 사용자 이름
@Column(nullable = false)
private String password; // 비밀번호
@Column(nullable = false)
private String email; // 이메일
@Column(nullable = false)
private boolean enabled; // 계정 활성화 여부
@OneToMany(fetch = FetchType.EAGER)
@JoinColumn(name = "user_id") // 외래 키 설정
private List<RoleEntity> roles; // 사용자 역할 목록
// Getters와 Setters 생략
}
2. RoleEntity 클래스
- 역할 정보를 나타내는 RoleEntity 클래스
import javax.persistence.*;
@Entity
@Table(name = "roles")
public class RoleEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; // 고유 식별자
@Column(nullable = false, unique = true)
private String name; // 역할 이름 (예: ROLE_USER, ROLE_ADMIN)
// Getters와 Setters 생략
}
3. UserRepository 인터페이스
- JPA를 사용하면 Repository 인터페이스를 만들어 데이터베이스 작업을 수행할 수 있다.
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<UserEntity, Long> {
UserEntity findByUsername(String username); // 사용자 이름으로 찾기
}
4. UserDetailsService 구현
- UserDetailsService를 구현할 때 UserRepository를 사용해 사용자 정보를 조회하는 방식으로 변경할 수 있다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository; // JPA Repository
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity userEntity = userRepository.findByUsername(username);
if (userEntity == null) {
throw new UsernameNotFoundException("User not found with username: " + username);
}
List<GrantedAuthority> authorities = userEntity.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
return new CustomUserDetails.Builder()
.username(userEntity.getUsername())
.password(userEntity.getPassword())
.email(userEntity.getEmail())
.enabled(userEntity.isEnabled())
.authorities(authorities)
.build();
}
}
요약
- JPA: 데이터베이스와의 매핑을 간편하게 해주는 프레임워크.
- Repository: 데이터베이스 작업을 수행할 수 있도록 도와주는 인터페이스.
- CustomUserDetailsService: UserRepository를 사용하여 사용자 정보를 조회하고, CustomUserDetails를 반환하는 서비스.
'Java > SpringBoot' 카테고리의 다른 글
.of() 메서드 (0) | 2024.11.06 |
---|---|
@RestController, @RequestMapping, ResponseEntity, @RequestBody, ResponseEntity.ok() (1) | 2024.11.05 |
UserDetailsService, UserDetails, GrantedAuthority, SimpleGrantedAuthority, stream (0) | 2024.10.28 |
@Configuration, @Bean, @EnableWebSecurity, SecurityFilterChain, HttpSecurity (0) | 2024.10.28 |
@Param (0) | 2024.10.28 |