Project/Boilerplate

User implements UserDetails 문제

조용우 2025. 3. 6. 03:34

문제:

@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())));
    }
}