[Spring Security] 사용자 관리

컴공생의 코딩 일기·2023년 3월 14일
0

스프링 시큐리티

목록 보기
1/2
post-thumbnail

사용자 관리 정의

사용자 관리를 위해서는 UserDetailsServiceUserDetailsManager 인터페이스를 이용한다.

  • UserDetailsService : 사용자 이름으로 사용자를 검색하는 역할만 한다.
  • UserDetailsManager : 대부분의 애플리케이션에 필요한 사용자 추가, 수정, 삭제 작업을 추가한다.

UserDetailsManager (UserDetailsManager는 UserDetailsService 계약을 확장한다.)-> UserDetailsService (UserDetailsService는 UserDetails 계약을 이용한다.)-> UserDetails <-(UserDetails는 하나 이상의 권한을 가진다.)GrantedAuthority

사용자 기술하기

애플리케이션은 사용자가 누구인지에 따라 특정 기능을 호출할 수 있는지 여부를 결정한다.

UserDetails

  • UserDetails 계약은 스프링 시큐리티가 이해하는 방식으로 사용자를 나타낸다.
public interface UserDetails extends Serializable {
	
    // 사용자 자격 증명을 반환하는 메서드
	// 사용자 암호를 반환
    String getPassword();
    // 사용자 이름을 반환
	String getUsername();

	// 앱 사용자가 수행할 수 있는 작업을 GrantedAuthority 인스턴스의 컬렉션으로 반환
    // 사용자에게 부여된 그룹을 반환 
	Collection<? extends GrantedAuthority> getAuthorities();
	
	// 사용자 계정을 필요에 따라 활성화 또는 비활성화하는 메서드
    // 계정 만료 여부 - true: 만료 안됨
	boolean isAccountNonExpired();
    // 계정 장금 여부 - true: 장금 안됨
	boolean isAccountNonLocked();
    // 자격(비밀번호) 증명 만료 여부 - true: 만료 안됨
	boolean isCredentialsNonExpired();
    // 계정 비활성화 여부 - 활성화됨
	boolean isEnabled();

}

UserDetails 구현 방법

public class DummyUser implements UserDetails {

    private final String username;
    private final String password;

    public DummyUser(String username, String password) {
        this.username = username;
        this.password = password;
    }

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

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

    @Override
    public String getUsername() {
        return this.username;
    }

    // 모든 기능 활성화 -true
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

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

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

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

빌더를 이용해 UserDetails 형식의 인스턴스 만들기

org.springframework.security.core.userdtails 패키지의 User 클래스는 UserDetails 형식의 인스턴스를 간단하게 만드는 방법이며 이 클래스로 UserDetails의 변경이 불가능한 인스턴스를 만들 수 있다. 이 방법으로 사용자를 만들면 UserDetails 계약의 구현을 이용할 필요가 없다.

// User.withUsername(String username) -> User 클래스에 중첩된 빌더 클래스 UserBuilder 인스턴스를 반환 
UserDetails u = User.withUsername("bill")
            .password("1234")
            .authorities("read", "write")
            .accountExpired(false)
            .disabled(true)
            .build();

UserDetails의 유연한 사용 방법

  • 잘못된 예)
@Entity
public class User implements UserDetails {

    @Id
    private String username;
    private String password;
    private String authority;

    public User(String username, String password, String authority) {
        this.username = username;
        this.password = password;
        this.authority = authority;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(() -> authority);
    }

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

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

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

하나의 클래스에 Jpa 기능과 Security 기능을 사용하는 것을 올바르지 않다.(여러 책임을 혼합하는 것은 올바르지 않다.) 코드가 매우 복잡해진다.

  • 유연한 코드 예)
@Entity
public class User{

    @Id
    private String username;
    private String password;
    private String authority;

    public User(String username, String password, String authority) {
        this.username = username;
        this.password = password;
        this.authority = authority;
    }
    // .....
}
ublic class SecurityUser implements UserDetails {
    private final User user;

    public SecurityUser(User user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return getAuthorities();
    }

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

    @Override
    public String getUsername() {
        return getPassword();
    }
	
    // ...........

}

별도의 클래스를 정의해서 책임을 분리해야 코드이 복잡성을 줄일 수 있다.

GrantedAuthority 계약

  • 권한은 사용자가 애플리케이션에서 수행할 수 있는 작업을 나타낸다. 권한이 없으면 모든 사용자가 동등하다.

GrantedAuthority 인터페이스

GrantedAuthority 인터페이스는 사용자 세부 정보의 정의에 이용되며 사용자에게 허가된 이용 권리를 나타낸다. 사용자는 권한이 하나도 없거나 여러 권한을 가질 수 있지만 일반적으로 하나 이상의 권한을 가진다.

public interface GrantedAuthority extends Serializable {
	String getAuthority();
}

권한을 만들려면 나중에 권한 부여 규칙을 작성할 때 참조할 수 있게 해당 이용 권리의 이름만 찾으면 된다.

// 권한 이름을 String으로 반환하는 getAuthority() 메서드 구현
GrantedAuthority g1 = () -> "READ"
GrantedAuthority g2 = new SimpleGrantedAuthority("READ");

UserDetailsService 계약

  • Spring Security에서 유저의 정보를 가져오는 인터페이스이다.
  • Spring Security에서 유저의 정보를 불러오기 위해서 구현해야하는 인터페이스로 기본 오버라이드 메서드는 아래와 같다.
public interface UserDetailsService {
	UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

인증 구현은 loadUserByUsername(String username) 메서드를 호출해 주어진 사용자 이름을 가진 사용자의 정보를 얻는다. 사용자 이름이 존재하지 않으면 메서드가 UsernameNotFoundException을 던진다.

`UsernameNotFoundException` 은 `AuthenticationException`를 상속하고 `AuthenticationException`은 `RuntimeException`을 상속한다. 즉 `RuntimeException`을 발생시킨다.

UserDetailsService 구현

public class InMemoryUserDetailsService implements UserDetailsService {
	// UserDetailsService는 메모리 내 사용자의 목록을 관리
    private final List<UserDetails> user;

    public InMemoryUserDetailsService(List<UserDetails> user) {
        this.user = user;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return user.stream()
                .filter( // 사용자의 목록에서 요청된 사용자 이름과 일치하는 항목을 필터링
                        u->u.getUsername().equals(username)
                ).findFirst() // 일치하는 사용자가 있으면 반환
                .orElseThrow( // 이 사용자 이름이 존재하지 않으면 예외를 던진다.
                        ()->new UsernameNotFoundException("User not found")
                );
    }
}

UserDetailsManager

  • UserDetailsManager 인터페이스는 UserDetailsService 계약을 확장하고 메서드를 추가한다.
  • UserDetailsManager 인터페이스는 사용자 추가, 수정, 삭제 작업을 할 수 있게 편리하게 도와주는 인터페이스이다.
public interface UserDetailsManager extends UserDetailsService {

	void createUser(UserDetails user);
	void updateUser(UserDetails user);
	void deleteUser(String username);
	void changePassword(String oldPassword, String newPassword);
	boolean userExists(String username);

}
profile
더 좋은 개발자가 되기위한 과정

0개의 댓글