스프링 시큐리티 의존성을 설치하기만해도 모든 요청에 인증이 요구된다.
spring-boot-starter-security 의존성이 추가되면
자동으로 SecurityAutoConfiguration
안 DefaultAuthenticationEventPublisher
가 Bean으로 등록된다.
아래는 시큐리티 자동 설정 클래스인 SecurityAutoConfiguration
의 내용이다.
@AutoConfiguration(
before = {UserDetailsServiceAutoConfiguration.class}
)
@ConditionalOnClass({DefaultAuthenticationEventPublisher.class})
@EnableConfigurationProperties({SecurityProperties.class})
@Import({SpringBootWebSecurityConfiguration.class, SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
public SecurityAutoConfiguration() {
}
@Bean
@ConditionalOnMissingBean({AuthenticationEventPublisher.class})
public DefaultAuthenticationEventPublisher authenticationEventPublisher(ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
위 퍼블리셔는
사용자가 로그인을 시도하거나 실패했을 때 발생하는 이벤트들
등 을 처리하여 로깅하거나, 특정 로직을 수행하는 리스너들에게 알리는 데 사용된다.
또한 @ConditionalOnMissingBean()
의 어노테이션은
인자로 들어온 빈 객체가 존재 하지 않으면 아래 설정을 따른다는 것이고
만약 인자로 있는 AuthenticationEventPublisher
를 우리가 직접 구현한다면 우리가 이벤트 처리등을 커스텀하게 처리 가능하다.
설정을 확장시킨 @Import
어노테이션에 존재하는 SpringBootWebSecurityConfiguration
을 살펴보자
@Import
어노테이션으로 줬기 때문에 SpringBootWebSecurityConfiguration
설정을 확장한다는 뜻이다.
기본설정들을 보자
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnWebApplication(
type = Type.SERVLET
)
class SpringBootWebSecurityConfiguration {
...
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnDefaultWebSecurity
static class SecurityFilterChainConfiguration {
SecurityFilterChainConfiguration() {
}
@Bean
@Order(2147483642)
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> {
((AuthorizeHttpRequestsConfigurer.AuthorizedUrl)requests.anyRequest()).authenticated();
});
http.formLogin(Customizer.withDefaults());
http.httpBasic(Customizer.withDefaults());
return (SecurityFilterChain)http.build();
}
}
}
정말길다…
여튼 가장 중요한 defaultSecurityFilterChain
메서드 를 살펴보자
requests.anyRequest()).authenticated()
이 코드 때문에 모든 요청에 인증이 걸리는것!
즉 브라우저에 로그인 폼이 나타나고 모든 요청이 인증이 필요하게 된것은 이 설정들 때문이다!
시큐리티 자동설정 중 하나인 UserDetailsServiceAutoConfiguration
설정파일을 살펴보자
@AutoConfiguration
@ConditionalOnClass({AuthenticationManager.class})
@Conditional({MissingAlternativeOrUserPropertiesConfigured.class})
@ConditionalOnBean({ObjectPostProcessor.class})
@ConditionalOnMissingBean(
value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, AuthenticationManagerResolver.class},
type = {"org.springframework.security.oauth2.jwt.JwtDecoder"}
)
public class UserDetailsServiceAutoConfiguration {
...
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties, ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(new UserDetails[]{User.withUsername(user.getName()).password(this.getOrDeducePassword(user, (PasswordEncoder)passwordEncoder.getIfAvailable())).roles(StringUtils.toStringArray(roles)).build()});
}
위 코드에서는 시큐리티가 인메모리에 새로운 User와 새로운 역할을 부여해서 InMemoryUserDetailsManager 에 등록하는 모습이다.
위에서 실행 때마다 랜덤한 비밀번호가 로그에 찍혔던 이유는 이 때문이다.
또한
@ConditionalOnMissingBean(
value = {AuthenticationManager.class, AuthenticationProvider.class, UserDetailsService.class, AuthenticationManagerResolver.class},
위 ConditionalOnMissingBean
어노테이션으로 인해 각 클래스들이 존재하지 않으면 아래 코드들이 적용되는 것이도
만약 UserDetailsService
을 상속받아 재정의한다면 커스텀 할수 있을 것이다.
또는 AuthenticationManager
, AuthenticationProvider
를 상속받아 재정의 한다면 인증 방식을 커스텀 할수 있을것이다.
ex) jwt 를 이용한 Rest API 구조에서
중요한 위 세가지 클래스를 살펴보자
일단 스프링 시큐리티의 아키텍처 구조를 보자
모든 요청은 필터 체인이 가로채 간다. 만약 Security를 추가한다면 자동으로 필터체인을 등록하는데 등록된 필터체인은 위에서 봤던
defaultSecurityFilterChain
필터체인이다.
AuthenticationManager는 여러개의 provider (공급자) 들을 등록 할 수 있는 코디네이터? 조정자? 정도로 생각하면된다.
AuthenticationProvider는 특정 유형의 인증을 처리한다.
AuthenticationProvider는 인터페이스이기 때문에 두가지 함수만 열려있다.
우리는 이 AuthenticationProvider 인터페이스를 implements 하여 커스텀 가능하다!
authenticate
요청에 대한 인증을 수행하는 함수supports
이 provider가 지정된 인증 유형을 지원하는지 확인하는 함수샘플 프로젝트에서 사용하고 있는 인터페이스의 중요한 구현 중 하나는 UserDetailsService에서 사용자 세부 정보를 검색하는 DaoAuthenticationProvider
이다.
UserDetailsService는 인터페이스로
대부분의 케이스에서 AuthenticationProvider는 데이터베이스에서 자격을 증명하고 유효성 검사를 한다고 한다.
따라서 Spring 에서는 자격 증명 후 유효성 검사 로직의 함수를 단일 함수로 제공하기로 하였다.
참고 자료
https://ict-nroo.tistory.com/118
https://www.toptal.com/spring/spring-security-tutorial
https://sjh9708.tistory.com/170
https://www.youtube.com/watch?v=KxqlJblhzfI
https://velog.io/@dh1010a/Spring-Spring-Security를-이용한-로그인-구현-스프링부트-3.X-버전-1