자동 설정에 의한 기본 보안 작동
서버가 기동되면 SpringSecurity의 초기화 작업들이 이루어지고 보안 설정이 이루어진다.
별도의 설정이나 코드를 작성하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동한다(SpringBootWebSecurityConfiguration
).
인증여부를 검증하는 인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공한다.
인증을 시도할 수 있는 로그인 페이지가 스프링 시큐리티 내부적으로 자동으로 생성되어 렌더링 된다.
인증 승인이 이루어질 수 있도록 한개의 계정이 기본적으로 제공되는데 SecurityProperties
설정 클래스에서 생성된다.
@ConfigurationProperties(
prefix = "spring.security"
)
public class SecurityProperties {
public static final int BASIC_AUTH_ORDER = 2147483642;
public static final int IGNORED_ORDER = Integer.MIN_VALUE;
public static final int DEFAULT_FILTER_ORDER = -100;
private final Filter filter = new Filter();
private final User user = new User();
public SecurityProperties() {
}
public User getUser() {
return this.user;
}
public Filter getFilter() {
return this.filter;
}
public static class Filter {
private int order = -100;
private Set<DispatcherType> dispatcherTypes = EnumSet.allOf(DispatcherType.class);
public Filter() {
}
public int getOrder() {
return this.order;
}
public void setOrder(int order) {
this.order = order;
}
public Set<DispatcherType> getDispatcherTypes() {
return this.dispatcherTypes;
}
public void setDispatcherTypes(Set<DispatcherType> dispatcherTypes) {
this.dispatcherTypes = dispatcherTypes;
}
}
public static class User {
private String name = "user";
private String password = UUID.randomUUID().toString();
private List<String> roles = new ArrayList();
private boolean passwordGenerated = true;
public User() {
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
if (StringUtils.hasLength(password)) {
this.passwordGenerated = false;
this.password = password;
}
}
public List<String> getRoles() {
return this.roles;
}
public void setRoles(List<String> roles) {
this.roles = new ArrayList(roles);
}
public boolean isPasswordGenerated() {
return this.passwordGenerated;
}
}
}
해당 클래스를 보면 Inner클래스로 User와 Filter를 가지고 있다. User 필드를 보면 name은 user이고 password는 랜덤으로 설정되어있는 것을 알 수 있다.
그러면 초기에 설정된 user와 랜덤으로 생성된 비밀번호를 가지고
public class UserDetailsServiceAutoConfiguration {
private static final String NOOP_PASSWORD_PREFIX = "{noop}";
private static final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");
private static final Log logger = LogFactory.getLog(UserDetailsServiceAutoConfiguration.class);
public 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()});
}
로컬 메모리에 저장을 한다. 해당 InMemoryUserDetailsManager에 의해 생성된 User 객체를 가지고
초기에 Security가 자동으로 생성한 페이지에 로그인 한 후 원하는 자원에 접근할 수 있도록 해주는 것이다.
SecurityBuilder / SecurityConfigurer
SecurityBuilder : 빌더 클래스로서 웹 보안을 구성하는 빈 객체와 설정 클래스들을 생성하는 역할을 하며 대표적으로 WebSecurity, HttpSecurity가 있다.
SecurityConfigurer를 참조하고 있으며 인증 및 인가 초기화 작업은 SecurityConfiguter에 의해 진행된다.
SecurityConfigurer : Http 요청과 관련된 보안처리를 담당하는 필터들을 생성하고 여러 초기화 설정에 관여한다.
처음에 서버가 가동되면서 AutoConfiguration에 의해서 SecurityBuilder가 자동으로 생성된다. 그럼 SecurityBuilder가 build() 메서드를 통해 SecurityConfigurer를 생성하게 된다.
public interface SecurityBuilder<O> {
O build() throws Exception;
}
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
SecurityConfigurer는 총 2개의 메서드를 가지고 있는데 둘 다 초기화에 사용되는 메서드 들이다.
SecurityConfigurer를 구현하는 구현체 들은 총 48개나 된다.(Security6.x 기준)
해당 구현체들은 자기가 담당하는 역할에 맞게 보안 설정을 초기화하고 구성하며, 필터 등록은 일부 구현체들에서 이루어지는 작업입니다.
초기화 과정에서 자동설정에 의해 자동으로 등록되는 Filter들은 15개가 된다.
실제 등록된 Security의 Filter들은 HttpSecurity가 생성한 DefaultSecurityFilterChain이 관리한다.
즉 SecurityBuilder는 SpringSecurity에서 보안 구성을 단계별로 빌드하는 역할을 수행하며 build() 메서드를 통해 최종적으로 완성된 보안 객체를 생성한다. (HttpSecurity(Http 보안 요청과 관련된 보안을 빌드함))
SecurityConfigurer는 SecurityBuilder에 추가할 보안 설정을 정의하고 초기화하는 인터페이스이다.
• init(B builder): 보안 빌더의 초기 상태를 설정.
• configure(B builder): 초기화 후 보안 빌더에 세부 설정을 적용.
• 여러 구현체를 통해 세부적으로 인증, 인가, 세션 관리, CORS, CSRF, HTTP 기본 설정 등 다양한 보안 관련 기능을 설정할 수 있다.
WebSecurity / HttpSecurity(SecurityFilterChain)
HttpSecurityConfiguration은 스프링 부트에 의해서 자동으로 활성화 된다.그리고 HttpSecurity를 생성하고 초기화를 진행한다.
HttpSecurity는 보안에 필요한 각 설정 클래스(SecurityConfigurer의 구현체들)와 필터들을 생성하고(init 메서드와 configure 메서드를 호출) 최종적으로 SecurityFilterChain 빈을 생성한다.
즉 HttpSecurity는 위에서 보았듯이 총 48개의 SecurityConfigurer의 구현체들을 자동구성으로 초기화 한 다음 SecurityFilterChain을 생성한다는 것이다.
SecurityFilterChain
public interface SecurityFilterChain {
boolean matches(HttpServletRequest request);
List<Filter> getFilters();
}
SecuriyFilterChain은 단 두개의 메서드를 포함하고 있는데
matches : Client에 의해서 들어온 요청이 현재 SecurityFilterChain에 의해 처리되어야 하는지의 여부를 결정한다. true를 반환하면 현재 요청이 처리되어야 한다는 뜻이다.
getFilters : 현재 SecurityFilterChain에 포함된 Filter 객체의 리스트를 반환한다. 해당 메서드를 통해 현재 필터 체인에 포함된 필터들을 확인할 수 있다.
SecurityFilterChain의 구현체 DefaultSecurityFilterChain
public final class DefaultSecurityFilterChain implements SecurityFilterChain {
private static final Log logger = LogFactory.getLog(DefaultSecurityFilterChain.class);
private final RequestMatcher requestMatcher;
private final List<Filter> filters;
public DefaultSecurityFilterChain(RequestMatcher requestMatcher, Filter... filters) {
this(requestMatcher, Arrays.asList(filters));
}
//... 메인 로직
public boolean matches(HttpServletRequest request) {
return this.requestMatcher.matches(request);
}
클라이언트로부터 들어온 요청 리소스를 RequestMatcher를 통해 matches 작업을 수행하게 되고, 해당 자원에 대해서 필터가 등록되어있다면 필터에 의해 필터링 작업을 가지게 된다.
매칭이 되지 않는다면 chain.doFilter 메서드를 통해 다른 Filter에 의해서 작업을 거치게 된다.
WebSecurity 또한 스프링 부트의 자동설정으로 인해 생성된 WebSecurityConfiguration에 의해서 생성된다.
WebSecurity는 HttpSecurity SecurityFilterChain 빈을 생성하면 SecurityBuilder에 저장하는 역할을 한다.
Why
왜 WebSecurity는 SecurityBuilder에 저장을 하는가. 이는 WebSecurity가 build() 메서드를 실행하면 SecurityBuilder에서 SecurityFilterChain을 꺼내서 FilterChainProxy 생성자에게 전달을 한다.
예를 들어 관리자를 위한 보안 설정이 있다면
@Bean
public SecurityFilterChain adminSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN") // /admin/** 경로는 ADMIN 권한만 접근 가능
.anyRequest().authenticated() // 나머지 요청은 인증 필요
.and()
.formLogin()
.permitAll()
.and()
.logout()
.permitAll();
return http.build(); // `SecurityFilterChain` 객체를 반환
}
FilerChainProxy는 요청이 어떤 경로인지를 확인하고, 그 경로에 맞는 보안 규칙을 가진 SecurityFilterChain을 찾아서 실행한다.
• 예를 들어, /admin/** 경로에 대한 요청이 들어오면 FilterChainProxy는 관리자 관련 보안을 처리하는 SecurityFilterChain을 선택하여 실행.
선택하여 실행한다는 것은 커스터마이징이 가능하다는 것이다.
• 일반적인 요청에는 다른 SecurityFilterChain을 선택하고, 그 안의 필터들이 차례대로 실행.
즉 SecurityFilterChain은 여러 개의 보안 필터를 체인 형식으로 관리하고, FilterChainProxy는 요청에 맞는 SecurityFilterChain을 선택하여 해당 필터들을 실행한다는 것이다. 적절한 보안 필터가 적용될 수 있도록 프록시를 거친다.
'스프링 > Security6.x' 카테고리의 다른 글
[Security6.x] 인증 프로세스(formLogin) (0) | 2024.12.21 |
---|---|
[Security6.x] 사용자 정의 보안 기능 구현 (1) | 2024.12.13 |