문제:
@PostMapping
public ResponseEntity<PostResponse> createPost(
@AuthenticationPrincipal User user,
@RequestBody CreatePostRequest request
) {
return ResponseEntity.ok(
PostResponse.from(
postService.create(
user.getId(),
request.getTitle(),
request.getContent()
)
)
);
}
게시글 생성 시 @AuthenticationPrincipal로 UserDetails를 가져와야 하는데, 현재 UserDetails는 User Entity 그 자체에 종속되어 있음
도메인 엔티티 객체는 특정 기술에 종속되지 않도록 개발하는 것이 Best practice.
해결:
JwtUserDetails를 (Custom UserDetails) 만들어 User 엔티티를 감싸도록 함
기존:
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id", updatable = false)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role;
@OneToMany(mappedBy = "user")
private List<Post> posts;
@OneToMany(mappedBy = "user")
private List<Comment> comments = new ArrayList<>();
@CreatedDate
@Column(name = "created_at")
private LocalDateTime createdAt;
@Builder
public User(String email, String username, String password, String name, Role role) {
this.email = email;
this.username = username;
this.password = password;
this.name = name;
this.role = role.getRole();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> collectors = new ArrayList<>();
collectors.add(this::getRole);
return collectors;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
리팩토링 후:
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id", updatable = false)
private Long id;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String password;
@Column(nullable = false)
private String role;
@OneToMany(mappedBy = "user")
private List<Post> posts;
@OneToMany(mappedBy = "user")
private List<Comment> comments = new ArrayList<>();
@CreatedDate
@Column(name = "created_at")
private LocalDateTime createdAt;
@Builder
public User(String email, String username, String password, String name, Role role) {
this.email = email;
this.username = username;
this.password = password;
this.name = name;
this.role = role.getRole();
}
}
JwtUserDetails.java
@Getter
public class JwtUserDetails implements UserDetails {
private final Long id;
private final String username;
private final String email;
private final String name;
private final String password;
private final String role;
public JwtUserDetails(User user) {
this.id = user.getId();
this.username = user.getUsername();
this.email = user.getEmail();
this.name = user.getName();
this.password = user.getPassword();
this.role = user.getRole();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(this::getRole);
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
JwtUserDetailsService.java
@RequiredArgsConstructor
@Service
@Transactional
public class JwtUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new JwtUserDetails(userRepository.findByUsername(username)
.orElseThrow(() -> new EntityNotFoundException(
UserError.NO_SUCH_USER.getMessage())));
}
}
'Project > Boilerplate' 카테고리의 다른 글
게시글 Pagination 적용 (2 / 2 - 인피니티 스크롤형 게시판) (0) | 2025.03.10 |
---|---|
게시글 Pagination 적용 (1 / 2 - 리스트형 게시판) (0) | 2025.03.10 |
Mock 객체 테스트 시 필드 주의점 (0) | 2025.03.06 |
Spring Boot response로 header가 추가되지 않는 현상 (0) | 2025.03.06 |
JWT 토큰 길이 문제 (0) | 2025.03.05 |