Netplix 구독형 멤버십 프로젝트로 배우는 SpringSecurity
[Ch 2. 스프링 시큐리티 기초] - 03. 기본 구성 재정의
강의를 바탕으로 실습 내용을 정리하였습니다.
목차
1. UserDetailsService 구성 요소 재정의
InMemoryUserDetailsManager 구현체를 이용하여 기본 구성을 재정의하는 방법에 대해 알아보자
- 스프링 빈으로 등록할 수 있음
- config 패키지를 생성하고 설정 클래스(SecurityConfig)를 생성
- @Configuration 어노테이션으로 클래스를 구성 클래스로 구분
- @Bean 어노테이션으로 반환되는 값을 스프링 컨텍스트에 반영
- 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
'Course > Spring Security' 카테고리의 다른 글
[netplix-security-a] AuthenticationProvider 구현 (0) | 2024.10.03 |
---|---|
[netplix-security-a] AOP를 활용하여 비밀번호 암호화하기 (1) | 2024.10.03 |
[netplix-security-a] PasswordEncoder 구현 (0) | 2024.10.03 |
[netplix-security-a] UserDetailsService 와 UserDetailsManager 구현 (1) | 2024.09.29 |
[netplix-security-a] UserDetails와 영속성 엔티티의 분리된 구현 (0) | 2024.09.29 |
댓글