[Spring Security6] 주요 구성요소
스프링 시큐리티는 SpringBootWebSecurityConfiguration 파일을 제공해 스프링 부트가 자동으로 제공하는 보안 설정을 구성한다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {
@Bean
@Order(SecurityProperties.BASIC_AUTH_ORDER)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
return http.build();
}
}
시큐리티의 기본 보안 설정을 정의한다.
모든 HTTP 요청에 인증을 요구하고, 폼 기반 로그인과 HTTP Basic 인증을 활성화하고 기본 설정을 사용한다.
라이브러리만 다운로드하고 아무 설정도 진행하지 않으면 위의 설정이 애플리케이션에 적용된다.
커스텀 보안 설정을 위해 SecurityFilterChain 빈을 반환하는 @Configuration 클래스를 작성해야 한다.
@Configuration
public class ProjectSecurityConfig {
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests.requestMatchers("/myAccount", "/myBalance", "/myLoans", "/myCards").authenticated()
.requestMatchers("/notices", "/contact").permitAll())
.formLogin(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
return http.build();
}
}
HttpSecurity 객체를 구성하는 방법으로는 람다 방식과 메서드 체이닝 방식이 있다.
메서드 체이닝 방식은 .and() 메서드를 사용해 설정의 끝을 표시하고 다양한 설정을 연결하는 방식이다.
.and() 메서드가 HttpSecurity 인스턴스를 반환해 스트림을 사용하는 것 처럼 객체를 구성할 수 있다.
최신 버전부터는 예시처럼 람다식을 사용한다.
보안 설정을 람다 함수로 그룹화해서 각 설정이 독립적이라는 점을 표현한다.
위의 예시는 특정 요청에 대해서는 인증을 요구하고, 특정 요청에 대해서는 모두 허용하는 예시이다.
스프링 시큐리티를 사용하는 애플리케이션에는 인증을 위해 로그인을 요구한다.
데이터베이스를 연결하지 않은 경우 로그인을 수행하는 방법으로는 세 가지가 있다.
1.
아이디는 user / 비밀번호는 콘솔에 출력되는 값을 사용한다.
2.
설정 파일에 spring.security.user.name 과 spring.security.user.password 값을 지정해 아이디와 비밀번호로 사용한다.
3.
InMemoryUserDetailsManager 를 반환하는 메서드를 스프링 빈으로 등록한다.
@Bean
public InMemoryUserDetailsManager userDetailsService() {
UserDetails admin = User.withDefaultPasswordEncoder()
.username("admin")
.password("12345")
.authorities("admin")
.build();
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("12345")
.authorities("read")
.build();
return new InMemoryUserDetailsManager(admin, user);
실제 운영환경에서는 이렇게 사용하지는 않는다. (PasswordEncoder를 사용해 비밀번호를 저장해야 한다)
해당 메서드를 빈으로 등록해두면 스프링 시큐리티가 빈을 읽고 사용한다.
스프링 시큐리티는 사용자의 정보를 로드하기 위한 인터페이스로 UserDetailService 인터페이스를 제공한다.
loadUserByUsername 메서드 하나만 있는 인터페이스로 해당 인터페이스를 상속받는 인터페이스로는 UserDetailsManager가 있다. (보안을 위해 비밀번호 관련 메서드는 제공하지 않는다)
UserDetailsManager 인터페이스는 서비스의 확정 버전으로 사용자 정보의 CRUD 연산을 제공한다.
그 하위 클래스들은 시큐리티가 제공하는 구현체의 예시이다.
웬만하면 하위 클래스들은 하나만 정해서 스프링 빈으로 등록한 후 사용하는 편이다.
시큐리티가 제공하는 Manager를 그대로 써도 되고 필요한 경우 Manager를 커스텀해서 사용해도 된다.
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
스프링 시큐리티에서 사용자 관련 정보는 UserDetails 인터페이스를 통해 나타내고, 스프링 시큐리티가 제공하는 구현체로는 User 클래스가 있다.
사용자의 비밀번호, 권한, 계정 만료 여부 등을 확인할 수 있는 기능을 기본으로 제공하며 역시 기능이 부족하다면 확장해서 사용하는것도 가능하다.
한편 Authentication 인터페이스는 현재 로그인 한 사용자에 대한 정보를 나타낸다.
해당 객체는 AuthenticationManager를 통해 인증된 후 SecurityContext에 저장되며 인증 기반 기능 구현에 사용된다.
UserDetails와 Authentication 인터페이스를 구분해서 이해하자.
데이터베이스 테이블과 시큐리티를 연동할 때는 빈을 등록하는 방식보다는 UserDetailsService 인터페이스를 구현하는 클래스를 만드는 방식으로 진행한다.
@Service
public class MyProjectUserDetails implements UserDetailsService {
private final CustomerRepository customerRepository;
public MyProjectUserDetails(CustomerRepository customerRepository) {
this.customerRepository = customerRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String userName, password = null;
List<GrantedAuthority> authorities = null;
List<Customer> customer = customerRepository.findByEmail(username);
if (customer.size() == 0) {
throw new UsernameNotFoundException("User details not found for the user : " + username);
} else{
userName = customer.get(0).getEmail();
password = customer.get(0).getPwd();
authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(customer.get(0).getRole()));
}
return new User(username,password,authorities);
}
}
UserDetails 객체를 반환하는 loadUserByUsername 메서드를 오버라이드해서 스프링 시큐리티가 특정 데이터베이스 테이블을 기준으로 회원을 검색하도록 설정한다.
스프링 시큐리티는 UserDetailsService 를 구현하는 클래스를 찾고 해당 클래스를 기준으로 설정을 진행한다.
로그인 시 loadUserByUsername 메서드를 사용해 UserDetails 객체를 생성하고 반환하니 loadUserByUsername 메서드를 오버라이드 해서 로그인 시 사용될 비즈니스 로직을 구현하자.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security6] Authorization과 Filter (0) | 2023.08.09 |
---|---|
[Spring Security6] CORS, CSRF (0) | 2023.08.08 |
[Spring Security6] 사용자 인증 (0) | 2023.08.06 |
[Spring Security6] Password Encoder (0) | 2023.08.05 |
[Spring Security6] 내부 흐름 (0) | 2023.07.30 |
댓글
이 글 공유하기
다른 글
-
[Spring Security6] CORS, CSRF
[Spring Security6] CORS, CSRF
2023.08.08 -
[Spring Security6] 사용자 인증
[Spring Security6] 사용자 인증
2023.08.06 -
[Spring Security6] Password Encoder
[Spring Security6] Password Encoder
2023.08.05 -
[Spring Security6] 내부 흐름
[Spring Security6] 내부 흐름
2023.07.30