Spring/Spring Security

[Spring Security] 저장 메커니즘(Storage Mechanism)

공대키메라 2022. 7. 19. 22:28

지난 시간에 Form, Basic방식을 통한 Authentication(인증)을 알아보았다.

(궁금하면 여기 🤣🤣🤣 클릭!)

 

이번 시간에는 공식 사이트에서 알려주는 저장 메커니즘(Storage Mechanism)에 대해 알아볼 것이다.

 

참고한 사이트는 다음과 같다. 

 

출처:

https://docs.spring.io/spring-security/reference/servlet/authentication/passwords/storage.html

 

1. 저장 메커니즘(Storage Mechianism)

username과 password를 읽는 각각의 지원되는 메커니즘은 어떤 지원되는 메커니즘도 사용 가능하다. 

 

  • Simple Storage with In-Memory Authentication
  • Relational Databases with JDBC Authentication
  • Custom data stores with UserDetailsService
  • LDAP storage with LDAP Authentication

 

2. Storage Mechianism : In-Memory 인증(Authentication)

Spring Security의 InMemoryUserDetailManger는 메모리에 저장된 username/password 기반의 인증을 지원하기 위해 UserDetailService를 구현한다. InMemoryUserDetailManager는 UserDetailsManager 인터페이스를 구현해서 UserDetails의 관리를 제공한다. 

 

UserDetails 기반의 인증은 인증에서 username/password를 받기 위한 설정이 되어있을 때 Spring Security에서 사용된다.

 

UserDetails는 Spring Security에서 사용자의 정보를 담은 인터페이스다. 

UserDetailsService는 Spring Security에서 유저의 정보를 가져오는 인터페이스다.

 

Example 1. InMemoryUserDetailsManager Java Configuration

@Bean
public UserDetailsService users() {
	UserDetails user = User.builder()
		.username("user")
		.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
		.roles("USER")
		.build();
	UserDetails admin = User.builder()
		.username("admin")
		.password("{bcrypt}$2a$10$GRLdNijSQMUvl/au9ofL.eDwmoohzzS7.rmNSJZ.0FxO/BTk76klW")
		.roles("USER", "ADMIN")
		.build();
	return new InMemoryUserDetailsManager(user, admin);
}

InMemory 인증은 말 그래도 메모리에 우리가 저장을 해놔서 세팅해 놓는 것이다. 

 

 

위는 필자 키메라가 InMemoryUserDetailsManager.class 파일을 열어본것이다. 

private final로 user 를 Map으로 선언한 것을 볼 수 있다.

그리고 InMemoryUserDetailsManager를 생성할 때 넘겨주는 사용자 갯수에 따라서 등록이 되는 것을 확인할 수 있었다.

 

InMemoryUserDetailsManager.class 중 InMemoryUserDetailsManger method

    ...
    
    public InMemoryUserDetailsManager(Properties users) {
        Enumeration<?> names = users.propertyNames();
        UserAttributeEditor editor = new UserAttributeEditor();

        while(names.hasMoreElements()) {
            String name = (String)names.nextElement();
            editor.setAsText(users.getProperty(name));
            UserAttribute attr = (UserAttribute)editor.getValue();
            Assert.notNull(attr, () -> {
                return "The entry with username '" + name + "' could not be converted to an UserDetails";
            });
            this.createUser(this.createUserDetails(name, attr));
        }

    }
    
    ...

 

그러면 다시 글로 돌아가서...


위의 샘플은 안전한 형태로 비밀번호를 저장한다. 하지만, 시작하는 관점에서 많은 욕구를 남긴다. (뭔가 개선점이 많은 것 같다는 것을 표현한 것 같다. )

 

아래 샘플은 in memory 에 저장된 비밀번호를 확실히 보호하려고 User.withDefaultPasswordEncoder를 사용한다. 하지만, source code를 디컴파일링 해서 비밀번호를 얻어내는 것은 막지 못한다. 

 

이러한 이유로, user.withDefaultPasswordEncoder는 오직 시작할 때만 사용해야 하고, production에서 의도되면 안된다. 

(아니 시작할때만 사용하려면 굳이 사용할 필요가 있나...? 키메라는 잘 모르겠다 끼에에엑!)

 

3. Storage Mechianism : JDBC Authentication

Spring Security의 JdbcDaoImpl은 JDBC를 사용해서 가져오는 username/password 기반 인증을 지원하는 UserDetailService를 구현한다.  

 

JdbcUserDetailsManager는 JdbcDaoImpl을 상속하는데, UserDetailsManager 인터페이스를 통해 UserDetails의 관리를 제공한다. 

 

이 부분은 아무리 봐도 절대 안쓸거 같아서 패스하겠다. (언제든 공격 가능)

 

4. Storage Mechianism : UserDetails

UserDetails는 UserDetailService에 의해 반환된다. DaoAuthentiucationProdiver는 UserDetails를 검증하고 Authentication을 반환한다. Authentication은 설정된 UserDetailsService에 의해 반환된 UserDetails인 principal이다. 

 

5.Storage Mechianism : UserDetailsService

UserDetailService는 username, password, 그리고 다른 인증을 위한 속성을 가져오기 위해 DaoAuthenticationProvider에의해 사용된다.

 

Spring Security는 UserDetailsService의 in-memory와 JDBC 구현체를 제공한다. 

 

빈으로서 custom UserDetailService를 노출시켜서 custom 인증을 정의할 수 있다. 

 

Example 1. Custom UserDetailsService Bean

@Bean
CustomUserDetailsService customUserDetailsService() {
	return new CustomUserDetailsService();
}

 

많은 예제에서 보면 UserDetailsService를 보통 어플리케이션의 성격에 맞게 customizing을 많이 하는 것 같다. 

이거를 안쓰는 곳 본 적 있으면 누가 정 답을 알려줘!

 

6 .Storage Mechianism : PasswordEncoder

Spring Security의 servlet은 PasswordEncoder를 통합해서 안전하게 비밀번호를 저장하도록 지원한다. 

 

PasswordEncoder를 customizing하는건 PasswordEncoder bean을 사용해서 구현할 수 있다.

 

7. Storage Mechanism : DaoAuthenticationProvider

DaoAuthenticationProvider는 AuthenticationProvider의 구현체인데 UserDetailService와 PasswordEncoder로 username과 password를 인증하기 위해 사용한다. 

 

Spring Security내에서 어떻게 DaoAuthenticationProvider 에 대해 알아보자. 

 

https://docs.spring.io/spring-security/reference/_images/servlet/authentication/unpwd/daoauthenticationprovider.png

 

1. Username와 Password를 읽어서 authenticaion Filter가 UsernamePasswordAuthenticationToken을 ProviderManager의 의해 구현된 AuthenticationManager에게 전달한다. 

 

2. ProviderManager가 DaoAuthenticationProvider 종류의 AuthenticationProvider를 사용하기 위해 설정된다.

 

3. DaoAuthenticationProvider가 UserDetails와 UserDetailService를 바라본다. 

 

4. DaoAuthenticationProvider가 그리고 나서 이전 단계에서 반환된 UserDetail에 비밀번호를 인증하기 위해  PasswordEncoder를 사용한다. 

 

5. 인증(authentication)이 성공하면, 반환된 Authentication이 UsernamePasswordAuthenticationToken이고 설정된 UserDetailsService에 의해 반환된 UserDetails인 principal이다. 궁극적으로, 반환된 UsernamePasswordAuthenticaionToken은 인증 필터에 의해 SecurityContextHolder에 세팅된다. 


전체적으로 읽어보니 크게 어려운것은 아니다. 그냥 어느 경우에 어느것을 이용해서 흐름이 어떻게 흘러가는지를 정리해 놓은 글이다. 


아! 더 추가하자면 LDAP에 대한 내용은 빼고 정리를 했는데, 이유는 LDAP에 대해 정확히 이해가 되어야 하는데, 이것을 필자가 아직 모르기 때문이다. ㅠㅠ... 그것은 추후에 알아보고 정리하도록 하겠다.