[사이드프로젝트] 그저 그런 REST API로 괜찮은가? - 진정한 REST API 구현해보기 - AccountService 구현하기

gimseonjin616·2022년 3월 28일
0

Account 구현


Spring Security는 UserDetail이라는 객체를 통해서 권한 및 인증을 관리한다.

UserDetail 인터페이스는 다음과 같이 생겼으며 이를 상속받아서 Account class를 구현하여 이를 사용해서 인증 및 권한 관리를 한다.

세부 권한은 별도의 Enum 클래스로 구현하여 관리한다.

UserDetail 인터페이스에서 중요한 부분은 3군데다.

  • getAuthorities : 권한을 가져와서서 유효한 권한을 가지고 있는 지 체크한다.
  • getPassword : 유저의 패스워드 값을 가져와서 인증 관리를 한다.
  • getUsername : UserDetail은 ID 말고 Username이라는 네이밍을 사용한다. 따라서 Username이라는 컬럼을 사용하지 않으면 이 메소드를 통해서 return 값을 매핑해줘야 한다.
public interface UserDetails extends Serializable {
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();

    boolean isAccountNonExpired();

    boolean isAccountNonLocked();

    boolean isCredentialsNonExpired();

    boolean isEnabled();
}

Account.java

@Entity
@Getter @Setter @Builder
@NoArgsConstructor @AllArgsConstructor
@EqualsAndHashCode(of = "id")
public class Account implements UserDetails{
    @Id @GeneratedValue
    private Integer id;
    private String name;
    private String password;
    @ElementCollection(fetch = FetchType.EAGER)
    @Enumerated(value = EnumType.STRING)
    private Set<AccountRole> roles;

	// Security에서 사용하는 권한은 GrantedAuthority 객체로 관리되기 때문에 해당 객체로 매핑해준다.
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return roles.stream().map(r -> new SimpleGrantedAuthority("ROLE_" + r.name()))
                .collect(Collectors.toSet());
    }
	
    // 이번 프로젝트에서 User Id는 name이라는 컬럼으로 사용했기 때문에 getUsername을 수정해준다.
    // 그 외 getPassword는 컬럼 값이 같기 때문에 getter로 활용한다.
    @Override
    public String getUsername() {
        return name;
    }
	
    // 아래 항목들은 UserDetail에서 사용하는 값이나 현 프로젝트에선 사용하지 않아 기본값인 True로 return 한다.
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

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

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

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

AccountRole.java

// 권한은 크게 ADMIN과 USER로 한다.
public enum AccountRole {
    ADMIN, USER
}

AccountService 구현


유저 정보를 다루기 위한 AccountService를 구현해야한다.

Spring Security에서 사용하기 위해선 UserDetailSerivce를 상속해서 사용해야 한다.

UserDetailService는 다음과 같이 생겼다.

public interface UserDetailsService {
    UserDetails loadUserByUsername(String var1) throws UsernameNotFoundException;
}

여기서 가장 중요한 건(하나밖에 없지만) loadUserByUsername 메소드다. Spring Security에서 DB에 저장된 유저 정보를 가져와 사용자와 비교하기 위해서 DB 조회 후 UserDetail 객체를 반환해줘야 한다.

AccountService.java

@Service
public class AccountService implements UserDetailsService {

    @Autowired
    private AccountRepository accountRepository;

    @Autowired
    private PasswordEncoder passwordEncoder;

	// DB에서 Account 객체를 조회해와서 반환합니다.
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        return accountRepository.findByName(s)
                .orElseThrow(() -> new UsernameNotFoundException(s));
    }

    public Account create(Account account){
        account.setPassword(passwordEncoder.encode(account.getPassword()));
        return accountRepository.save(account);
    }
}

AccountSerive Test 구현


TDD 순서에 맞진 않지만 AccountServiceTest를 구현하여 제대로 AccountSerivce가 구현됐는 지 확인한다.

확인해야할 사항은 다음과 같다.

  • 유저가 DB에 존재할 시, Account 객체를 반환한다.
  • 유저가 DB에 존재하지 않을 시, UsernameNotFoundException 예외가 발생하는가
@SpringBootTest
@ActiveProfiles("test")
class AccountServiceTest {

    @Autowired
    AccountService accountService;
    
    public Account createAccount(){
        return Account.builder()
                .name("kimseonjin616")
                .password("password")
                .roles(Set.of(AccountRole.ADMIN, AccountRole.USER))
                .build();
    }

    @Test
    public void find_by_username_success(){
        Account account = createAccount();
        accountService.create(account);

        UserDetails userdetails = accountService.loadUserByUsername(account.getName());

        assertEquals(account.getUsername(), userdetails.getUsername());
        assertEquals(account.getPassword(), userdetails.getPassword());
    }

    @Test
    public void find_by_username_not_found(){
        String wrongUsername = "";

        UsernameNotFoundException exception =
                assertThrows(UsernameNotFoundException.class,
                        () -> accountService.loadUserByUsername(wrongUsername));

        assertEquals(exception.getCause(), new UsernameNotFoundException(wrongUsername).getCause());
        assertEquals(exception.getMessage(), new UsernameNotFoundException(wrongUsername).getMessage());
    }

}

profile
to be data engineer

0개의 댓글

관련 채용 정보