본문 바로가기
Course/Spring Security

[netplix-security-a] 기본 구성 재정의

by Lpromotion 2024. 9. 29.
Netplix 구독형 멤버십 프로젝트로 배우는 SpringSecurity
[Ch 2. 스프링 시큐리티 기초] - 03. 기본 구성 재정의
강의를 바탕으로 실습 내용을 정리하였습니다.

목차
 

1. UserDetailsService 구성 요소 재정의

InMemoryUserDetailsManager 구현체를 이용하여 기본 구성을 재정의하는 방법에 대해 알아보자

  • 스프링 빈으로 등록할 수 있음
  1. config 패키지를 생성하고 설정 클래스(SecurityConfig)를 생성
  2. @Configuration 어노테이션으로 클래스를 구성 클래스로 구분
  3. @Bean 어노테이션으로 반환되는 값을 스프링 컨텍스트에 반영
  4. InMemoryUserDetailsManager 를 UserDetailsService 로 반영
package fast.campus.fcss01.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

@Configuration // 클래스를 구성 클래스로 구분
public class SecurityConfig {

    @Bean // 반환되는 값을 스프링 컨텍스트에 반영
    public UserDetailsService userDetailsService() {
        // InMemoryUserDetailsManager를 UserDetailsService로 반영
        return new InMemoryUserDetailsManager();
    }
}

 

더 이상 노출되지 않는 비밀번호

전 슬라이드와 같이 설정 후 로컬에서 애플리케이션을 실행시켜보면 더 이상 비밀번호가 노출되지 않음

  • 별도의 설정이 없을 때는 스프링 시큐리티 기본 비밀번호로 사용할 수 있는 UUID 가 노출되었음

  • 기본으로 구성된 UserDetailsService 구현체 대신 스프링 컨텍스트에 반영된 UserDetailsService, 즉 InMemoryUserDetailsManager 를 이용함
  • 하지만 사용자와 PasswordEncoder 를 적용하지 않았기 때문에 REST 엔드포인트에 접근이 불가능함

 

테스트 사용자 등록

UserDetailsService 코드가 수정됨

  • UserDetails 추가
  • InMemoryUserDetailsManager.createUser 호출

참고. User 클래스는 스프링 시큐리티에서 제공하는 객체로 사용자를 나타냄

...
@Configuration // 클래스를 구성 클래스로 구분
public class SecurityConfig {

    @Bean // 반환되는 값을 스프링 컨텍스트에 반영
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        UserDetails user1 = User.builder().username("user1").password("password").build();
        inMemoryUserDetailsManager.createUser(user1);

        // InMemoryUserDetailsManager를 UserDetailsService로 반영
        return inMemoryUserDetailsManager;
    }
}

 

로컬 환경 테스트

포스트맨에서 (id: danny, pw: password) 호출하면 어떻게 될까?

  • 여전히 401 Unauthorized 에러 발생

 

로컬 애플리케이션 에러 로그가 발생함
”You have entered a password with no PasswordEncoder…”

REST 엔드포인트를 호출할 때 스프링 시큐리티가 암호를 관리하는 방법을 모르기 때문에 오류가 발생함

 

2. PasswordEncoder 재정의

에러에서 알 수 있듯 PasswordEncoder 도 다시 정의를 해야 함

  • 기본 UserDetailsService 를 이용하면 자체적으로 기본 PasswordEncoder 를 설정해줌
  • 하지만 커스텀하게 UserDetailsService 를 설정하게 되면 PasswordEncoder 역시 직접 재설정해줘야 함

따라서 UserDetailsService 를 스프링 빈으로 재정의했던 것처럼 PasswordEncoder 도 별도의 스프링 빈으로 재정의하면 해결할 수 있음

 

PasswordEncoder 로 NoOpPasswordEncoder 를 활용함

  • PasswordEncoder 에 대한 구현체로 NoOpPasswordEncoder 를 등록
  • 적용 후에는 성공적으로 REST 엔드포인트가 호출됨
...
@Configuration // 클래스를 구성 클래스로 구분
public class SecurityConfig {

    @Bean // 반환되는 값을 스프링 컨텍스트에 반영
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        UserDetails user1 = User.builder().username("user1").password("password").build();
        inMemoryUserDetailsManager.createUser(user1);

        // InMemoryUserDetailsManager를 UserDetailsService로 반영
        return new InMemoryUserDetailsManager();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        // PasswordEncoder 로 NoOpPasswordEncoder를 활용
        return NoOpPasswordEncoder.getInstance();
    }
}

 

3. 엔드 포인트에 따른 접근 권한 부여 재정의

엔드 포인트에 따라 접근하도록 허용하거나 제한할 수 있음

  • SecurityFilterChain, WebSecurityCustomizer 에 대한 스프링 빈을 재정의할 수 있음
  • SecurityConfig 설정 클래스 내 설정하면 됨
...

@Configuration // 클래스를 구성 클래스로 구분
public class SecurityConfig {

    ...

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeHttpRequests((auth) -> auth.anyRequest().authenticated()) // 모든 요청에 대한 인증을 요구
                .httpBasic(withDefaults()); // HTTP Basic 인증
        return httpSecurity.build();
    }
}

 

permitAll() 으로 변경하면 별도의 자격 증명 없이 REST 엔드포인트를 호출할 수 있음 (로그인 없이)

.authorizeHttpRequests((auth) -> auth.anyRequest().**permitAll**()) // 로그인 없이 api 접근 가능

ID, PW 설정 없이도 “Hello, Spring Security” 응답을 받음

 

4. AuthenticationProvider 구현 재정의

AuthenticationProvider 는 인증공급자에 해당하는 부분으로 인증 논리를 구현하고 사용자 관리와 비밀번호 관리를 각각 UserDetailsService 와 PasswordEncoder 에 위임함

 

CustomAuthenticationProvider

AuthenticationProvider 를 상속받아 구현함

  • CustomAuthenticationProvider 는 @Component 어노테이션으로 스프링 빈으로 등록함
  • config 패키지 아래에 생성
  • 인증 로직을 구현하기 위해 if 문을 추가 (id 값이 “user1” 이고 pw 값이 “password” 일 때)
package fast.campus.fcss01.config;

import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;

import java.util.List;

@Component // 스프링 빈으로 등록
public class CustomAuthenticationProvider implements AuthenticationProvider {
    // AuthenticationProvider 를 상속받아 구현
    // 인증 논리를 추가하는 메소드

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = String.valueOf(authentication.getCredentials());

        if("user1".equals(username) && "password".equals(password)) {
            return new UsernamePasswordAuthenticationToken(username, password, List.of());
        }

        throw new RuntimeException("auth error");
    }

    // Authentication 형식의 구현을 추가하는 메소드
    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}

 

 

GitHub

https://github.com/lpromotion/fcss-01/commit/97ce172cd21629d15ba0b832193a86082a3f0294

반응형

댓글