Spring Security 메모 - 인증과정 커스터 마이징

timothy jeong·2022년 7월 17일
1

Spring Security

목록 보기
3/8

Spring Security User Details 과 커스터마이징

이전 장에서는 User 에 대한 정보를 InMemoryUserDetailsManager 에 저장했고, 그 저장하는 정보는 UserDetails 이었으며, 결국에는 그렇게 저장된 정보를

AuthenticationManagerBuilder 의 UserDetailService 에 보내주었다.

InMemoryUserDetailsManager, UserDetailService, UserDetails 는 무엇이고 서로의 관계는 어떻게 되는걸까?

UserDetils

이 인터페이스는 유저에 대한 정보를 갖고있으며, 이러한 정보는 앞으로 있을 모든 인증, 인가 과정에서 활용된다.
그러므로 자체적인 User 객체를 만들고 싶다면 이 인터페이스를 구현해서 새로운 객체를 정의해야 spring security 와 호환성이 좋다.
하지만 만약 기본적으로 제공되는 정보에 만족한다면 굳이 그렇게까지 할 필요는 없을 것이다.

UserDetailService

이 인터페이스는 오로지 지정된 데이터 베이스나, 어플리케이션 내부 메모리에서 유저 정보를 조회하는 역할을 수행한다. 따라서 당연히 UserDetails 인터페이스에 의존적이다.

구현해야하는 메서드는 loadUserByUserName(String userName) 인데, 이때 passwd 도 같이 DB 조회에 시용하지 않는 이유는, 그렇게 하면 DB 관리자가 비밀번호를 알수도 있고, passwd 가 DB 로그에 남기 때문이다. 이는 굉장히 위험하다.

UserDetailsManager

UserDetailsManager 는 유저를 생성, 수정, 삭제 하는 역할을 맡는다.
물론 UserDetailService 를 확장하기 때문에 loadUserByUserName 도 사용할 수 있다.

구현체들

그리고 이러한 기능을 모두 구현해놓은 것들이 최상의 세 가지이다. 이것들이 참 유명하다.

InMemoryUserDetailsManager

컨셉 증명등에 빠르게 사용해야하는 경우에만 유효함. Spring boot 내부 메모리에 저장하기 때문에 어플리케이션 프로세스가 종료되면 정보들이 모두 날아감.

Heap 영역에 존재하는 해쉬맵 구조에서 유저 정보를 관리함

JdbcUserDetailsManager

프로덕션 레벨에서 사용하기 아주 적합한 수준
몇가지 사항을 지켜줘야 하는데,
유저 정보를 저장하는 테이블은 users 가 되어야 하며, username, password, enabled 이 세 칼럼은 반드시 존재해야한다. 물론 커스터마이징 하는 방법이 있다.

그리고 group 이라는 개념이 존재하는데, linux 에서 user group 을 관리하는 것과 같은 느낌이다.

LdapUserDetailsManager

이건 뭐지? ㅋㅋㅋ

직접 만들기

하지만 만약, 구햔체들의 사용방법이 맘에 들지 않는다면 제공되는 인터페이스를 이용하여 구현체를 만드는것도 방법이다.

JdbcUserDetailsManager 기본 사용

가장 기본적으로 rds 에 프리티어 db 를 올리고 아래처럼 두개의 테이블을 만들어줘야한다.

# mysql

CREATE TABLE `users`(
 `id`         INT NOT NULL AUTO_INCREMENT,
 `username` varchar(45) NOT NULL ,
 `password`     varchar(45) NOT NULL ,
 `enabled`     varchar(45) NOT NULL ,

PRIMARY KEY (`id`)
);

CREATE TABLE `authorities`(
 `id`         INT NOT NULL AUTO_INCREMENT,
 `username` varchar(45) NOT NULL ,
 `authority`     varchar(45) NOT NULL ,

PRIMARY KEY (`id`)
);

그리고 application, properties 에 datasource 에 대한 정보를 명시한다.

spring.datasource.driver-class-name=org.mariadb.jdbc.Driver
spring.datasource.url=jdbc:mariadb://securitytest2.cizf3eukwuvm.ap-northeast-2.rds.amazonaws.com:3306/securitytest2?useUnicode=yes&characterEncoding=UTF-8

spring.datasource.username=oper1
spring.datasource.password=xPassWordxAdminx

WebSecurityConfigurerAdapter 를 확장한 config 객체에서 다음과 같은 설정이 필요하다.

@Bean
public UserDetailsService userDetailsService(DataSource dataSource) {
    return new JdbcUserDetailsManager((dataSource));
}

JdbcUserDetailsManager 커스터마이징

Users 테이블이 아니라 아래와 같이 새로운 테이블로 유저를 관리한다면 어떻게 해야할까?

CREATE TABLE `customer` (
 `id`         INT NOT NULL AUTO_INCREMENT,
 `email` varchar(45) NOT NULL ,
 `pwd`     varchar(45) NOT NULL ,
 `role`     varchar(45) NOT NULL ,
 
 PRIMARY KEY (`id`)
 );

JPA 를 이용해보자.
(1) Entity 객체를 만들고
(2) repository 를 정의한다.
(3) UserrDetails 을 확장한 클래스를 만든다.
(4) UserDetailService 를 확장한 클래스를 만든다.

이렇게 UserDetailService 를 직접 등록하고 Bean 에 올릴거라면 다른 기본 제공되는 UserDetailService 를 또 Bean 에 등록하고 있지는 않은지 체크가 필요하다.

위의 UserDetailsService Bean 이 활성화된 상태에서 (1) ~ (4) 단계 셋팅을 완료한 후 어플리케이션을 실행하면 새로운 유저를 추가하지 않는 이상 어떤 아이디와 비밀번호로도 로그인이 안될것이다.


// (1)------------------------------------------------
@Entity
@Table(name = "customer")
@Getter
@Setter
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;
    private String pwd;
    private String role;
}
// (2)------------------------------------------------


@Repository
interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByEmail(String email);
}

// (3)------------------------------------------------

/**
* isAccountNonExpired 등등은 아직 쓰지 않을거다, 다 true 로 반환해서 문제가 없도록 하자.
*/
public class SecurityCustomer implements UserDetails {
    
    private static final long serialVersionUID = -1111L;
    
    private final Customer customer;
    
    public SecurityCustomer(Customer customer) {
        this.customer = customer;
    }
    
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(customer.getRole()));
        return authorities;
    }

    @Override
    public String getPassword() {
        return customer.getPwd();
    }

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

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

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

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

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

// (4)------------------------------------------------

@Service
public class BankUserDetails implements UserDetailsService {

    private final CustomerRepository customerRepository;
    
    @Autowired
    public BankUserDetails(CustomerRepository repository) {
        this.customerRepository = repository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<Customer> customers = customerRepository.findByEmail(username);
        if (customers.size() == 0) {
            throw new UsernameNotFoundException("User details not found for the user : " + username);
        }
        return new SecurityCustomer(customers.get(0));
    }
}

그렇다면 유저를 추가하고 삭제하는 것은 어떻게 하면 될까?
간단하다, 따로 회원가입 페이지를 만들고 직접 로직을 만들어서 진행하면 된다.

profile
개발자

0개의 댓글