wonslee / object-study

📔오브젝트 예제 코드를 따라 공부, 토론하는 스터디 그룹
0 stars 1 forks source link

Spring Security 에서 UserDetails를 회원 엔티티에 어떻게 적용할지? #61

Open kmw2378 opened 4 months ago

kmw2378 commented 4 months ago

Spring Security에서 UserDetails 인터페이스의 추상 메서드는 여러 인증과정에 사용됩니다. 따라서, 어플리케이션에서 사용하는 회원 엔티티는 반드시 UserDetails와 연관지어 사용해야 합니다. 회원 엔티티를 Member라 하면 MemberUserDetails의 코드를 재사용해야 되는 상황입니다.

1. 상속을 사용한 Member 엔티티 코드

@Getter
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@EntityListeners(AuditingEntityListener.class)
public class Member implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "provider_id", nullable = true)
    private Long providerId;

    @Column(updatable = false, nullable = false)
    private String email;   // 타입이 다르면 중복 가능

    @Column(name = "password", nullable = true)
    private String password;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.roles.stream()
                .map(r -> r.getValue().getType())
                .map(SimpleGrantedAuthority::new)
                .toList();
    }

    @Override
    public String getUsername() {
        return String.valueOf(id);
    }

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return usable;
    }

엔티티 내부 코드가 너무 복잡해져 직관성이 떨어진다는 큰 문제가 있었습니다. 더 나아가 SRP도 지켜지지 않는 느낌을 받았습니다. (특히, 인증과정에서 엔티티가 사용되는게 어색했습니다.) 따라서, 합성을 사용해 책임을 분리하자는 생각을 했습니다.

2. 합성을 이용

public class MemberDetails implements UserDetails {
    private final Member member;

    public MemberDetails(final Member member) {
        this.member = member;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority("ROLE_USER"));
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return member.getEmail();
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}

덕분에 Member 엔티티 내부 코드의 직관성이 높아졌으며 행동을 기준으로 객체를 분리하니 엔티티가 여러 곳에서 사용되는 것을 막을 수 있었습니다.