1. 의존성 추가
스프링부트 3.x 프로젝트 생성 시 Spring Web, Spring Security 두 가지 의존성만을 추가한 상태이다.
build.gradle에는 다음과 같이 의존성이 설정되어 있을 것이다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-web'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
}
2. 엔드포인트 생성
그리고 다음과 같이 컨트롤러 엔드포인트를 하나 생성한다.
@RestController
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}
3. 로그인 페이지
서버 가동 후 localhost:8080으로 접속하면 다음과 같은 로그인 페이지로 연결된다.
의존성 추가만 했음에도 스프링 시큐리티의 자동 설정에 의하여 기본적인 보안 설정이 적용된 것이다.
이곳에서 로그인을 하려면 username에 user를 입력하고, password에는 콘솔 창에서 자동 생성된 비밀번호를 찾아서 붙여넣기 하면 된다.
이러한 형식으로 자동 생성된 비밀번호가 콘솔 창에 출력된다.
4. 스프링 시큐리티 코드로 확인하기
SecurityProperties
이 설정을 어디서 하고 있을까?
SecurityProperties라는 클래스를 찾아보면 내부에 User라는 클래스가 있다.
User의 속성 중 name은 "user"이며, password는 UUID임을 확인할 수 있다.
일반적인 애플리케이션의 로그인 플로우는 유저의 정보가 DB에 저장되어 있고, 입력값과 DB를 비교할 것이다.
하지만 현재 상황에서는 그러한 플로우가 없으니 위와 같은 정보를 메모리에 저장하는 초기화 과정이 이루어진다.
UserDetailsServiceAutoConfiguration
User의 getName()을 호출하는 곳은 UserDetailsServiceAutoConfiguration이라는 클래스의 다음 부분이다.
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
ObjectProvider<PasswordEncoder> passwordEncoder) {
SecurityProperties.User user = properties.getUser();
List<String> roles = user.getRoles();
return new InMemoryUserDetailsManager(User.withUsername(user.getName())
.password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
.roles(StringUtils.toStringArray(roles))
.build());
}
유저 객체를 관리(유저 객체를 생성해서 메모리에 저장)할 수 있는 매니저 클래스를 빈으로 등록하는 코드이다.
SpringBootWebSecurityConfiguration
위와 같은 설정을 해주는 설정 클래스이다.
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
class SpringBootWebSecurityConfiguration {
/**
* The default configuration for web security. It relies on Spring Security's
* content-negotiation strategy to determine what sort of authentication to use. If
* the user specifies their own {@link SecurityFilterChain} bean, this will back-off
* completely and the users should specify all the bits that they want to configure as
* part of the custom security configuration.
*/
@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();
}
이 설정 클래스의 defaultSecurityFilterChain 부분이 위의 설정을 가능하게 하는 부분이다.
이 코드는 SecurityFilterChain 타입의 Bean을 생성하고 있다.
그런데 이 부분이 실행되기 위해서는 조건이 필요한데, 그 조건은 @ConditionalOnDefaultWebSecurity 에 명시되어 있다.
ConditionalOnDefaultWebSecurity
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(DefaultWebSecurityCondition.class)
public @interface ConditionalOnDefaultWebSecurity {
}
@ConditionalOnDefaultWebSecurity 의 DefaultWebSecurityCondition 클래스에서 구체적인 조건을 확인할 수 있다.
DefaultWebSecurityCondition
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
static class Classes {
}
@ConditionalOnMissingBean({ SecurityFilterChain.class })
static class Beans {
}
}
그 조건은 첫째, SecurityFilterChain, HttpSecurity 클래스가 클래스 패스에 존재하는지?(참, 스프링 시큐리티 의존성을 추가했기 때문)
둘째, SecurityFilterChain 타입의 빈이 생성되어 있지 않은지?(참, 현재 아무런 SecurityFilterChain 타입 빈을 생성한 적 없기 때문)
두 가지 조건이 모두 참이어야 위와 같은 기본 보안 설정이 적용되는 것이다.
'Spring Security' 카테고리의 다른 글
[오류 해결] 스프링 시큐리티 로그인 실패 시 무한 재로그인 문제 해결 (1) | 2024.11.08 |
---|