반응형
Note1. UserDetails
Spring Security에서 사용자 정보를 담기 위해 정의하는 인터페이스이다.
Domain Entity에 직접 구현하는 방식도 가능하지만 이는 엔터티가 Spring Security에 의존하게 된다는 단점이 있어 보통 CustomUserDetails, UserDetailsImpl과 같은 네이밍으로 커스텀 래퍼 클래스를 사용해 정의한다.
Lombok @RequiredArgsConsructor 어노테이션을 사용하면 생성자를 생략해줘도 상관없고, 사용하지 않는다면 User 객체를 주입받기 위한 별도의 생성자를 정의해 주어야 한다.
public class CustomUserDetails implements UserDetails {
...
private final User user; // Domain Entity
// Constructor
public CustomUserDetails(User user) {
this.user = user;
}
...
}
Note2. UserDetails Field
getAuthroties() - 사용자 권한 목록을 반환한다.
getPassword() - 사용자 비밀번호를 반환한다.
getUsername() - 사용자명을 반환한다.
isAccountNonExpired() - 계정 만료 기능을 활성화한다.
isAccountNonLocked() - 계정 잠금 기능을 활성화한다.
isCredentialsNonExpired() - 비밀번호 만료 기능을 활성화한다.
/**
* UserDetails 구현 클래스
* Spring Security가 사용하는 사용자 인증 정보를 제공
*/
public class CustomUserDetails implements UserDetails {
private final User user;
// Constructor
public CustomUserDetails(User user) {
this.user = user;
}
// getAuthorities - 사용자 권한 목록을 반환한다.
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthorityName()))
.collect(Collectors.toList());
}
// getPassword - 사용자 비밀번호를 반환한다.
@Override
public String getPassword() {
return user.getPassword();
}
// getUsername - 사용자명을 반환한다.
@Override
public String getUsername() {
return user.getUserId();
}
// isAccountNonExpired - 계정 만료 기능 (Default : true)
@Override
public boolean isAccountNonExpired() {
return true; // 현재는 계정 만료 기능 미사용
}
// isAccountNonLocked - 계정 잠금 기능
@Override
public boolean isAccountNonLocked() {
return true; // 현재는 계정 잠금 기능 미사용
}
// isCredentialsNonExpired - 비밀번호 만료 기능
@Override
public boolean isCredentialsNonExpired() {
return true; // 현재는 자격 증명 만료 기능 미사용
}
// isEnabled - 계정 활성화 기능
@Override
public boolean isEnabled() {
return true; // 현재는 계정 비활성화 기능 미사용
}
// Domain Entity 반환용 Getter
public User getUser() {
return user;
}
}
Note3. @AuthenticationPrincipal
@AuthenticationPrincipal 어노테이션을 사용하면 별도의 CustomUserDetails 객체 생성 없이 곧바로 주입받아 사용이 가능하다.
// Note. MyPageAPI - 현재 로그인한 사용자 정보를 JWT에서 추출한다.
@GetMapping("/me")
public ResponseEntity<?> getCurrentUser(@AuthenticationPrincipal CustomUserDetails customUserDetails) {
try {
// 인증되지 않은 경우
if (customUserDetails == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(Map.of("message", "인증이 필요합니다."));
}
// CustomUserDetails에서 User 엔터티 추출
User user = customUserDetails.getUser();
// DTO로 변환 (민감 정보 제외)
MyPageResponseDTO response = MyPageResponseDTO.builder()
.id(user.getId())
.userId(user.getUserId())
.name(user.getName())
.nickname(user.getNickname())
.email(user.getEmail())
.universityName(user.getUniversityName())
.major(user.getMajor())
.grade(user.getGrade())
.profileImage(user.getProfileImage())
.createdAt(user.getCreatedAt())
.updatedAt(user.getUpdatedAt())
.build();
return ResponseEntity.ok(response);
}
catch (Exception e) {
Map<String, String> error = new HashMap<>();
error.put("message", "사용자 정보 조회 실패: " + e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(error);
}
}반응형