프로젝트 설정
-Spring boot 3.3.4 버전
- JDK 17
- Gradle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-security'
}
spring security 의존성 추가
Spring Boot Security 자동설정
spring security 의존성을 추가하면 관련 라이브러리가 다운로드 되고
그중
자동 설정 클래스가 추가된다.
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();
}
}
/**
* Adds the {@link EnableWebSecurity @EnableWebSecurity} annotation if Spring Security
* is on the classpath. This will make sure that the annotation is present with
* default security auto-configuration and also if the user adds custom security and
* forgets to add the annotation. If {@link EnableWebSecurity @EnableWebSecurity} has
* already been added or if a bean with name
* {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has been configured by the user, this
* will back-off.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {
}
}
해당 클래스로 인해 자동 설정의 의한 기본 보안 작동된다.
서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어진다
별도의 설정이나 코드를 작성하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동한다
- 기본적으로 모든 요청에 대하여 인증여부를 검증하고 인증이 승인되어야 자원에 접근이 가능하다
http.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
- 인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공한다
- 인증을 시도할 수 있는 로그인 페이지가 자동적으로 생성되어 렌더링 된다
http.formLogin(withDefaults());
http.httpBasic(withDefaults());
- 인증 승인이 이루어질 수 있도록 한 개의 계정이 기본적으로 제공된다
@ConfigurationProperties(prefix = "spring.security")
public class SecurityProperties {
....
public static class User {
/**
* Default user name.
*/
private String name = "user";
/**
* Password for the default user name.
*/
private String password = UUID.randomUUID().toString();
...
}
SecurityProperties 설정 클래스에서 생성
• username : user
• password : 랜덤 문자열
현재 이러한 설정가지고는 웹 서비스를 운영할 수 없다.
[ 계정이 여러개 필요할 수 있고, 계정마다 권한이 다를 수 있으므로 ]
그래서 어플리케이션에 맞게 커스텀한 보안설정을 진행할 것이다.
@RestController
public class IndexController {
@GetMapping("/")
public String index() {
return "index";
}
}
다음과 같은 테스트용 컨트롤러를 만들고
서버를 실행시켜보면
localhost:8080으로 request해도 /login으로 이동된다. -> 자동으로 어떤 request든 인증을 받도록 자동설정 되어있다
화면으로는 기본 설정으로 생긴 로그인 화면이 보일것이다.
로그인 성공후 로그인이 발생한 url로 되돌려줄때 기본경로라면 ?continue라는 부분이 붙는 경우가 존재한다.
다시한번 localhost:8080을 호출한다면
로그인 페이지없이 잘 보이는것을 확인가능하다.
이러한 설정을 자동으로 해주는 클래스인 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();
}
}
/**
* Adds the {@link EnableWebSecurity @EnableWebSecurity} annotation if Spring Security
* is on the classpath. This will make sure that the annotation is present with
* default security auto-configuration and also if the user adds custom security and
* forgets to add the annotation. If {@link EnableWebSecurity @EnableWebSecurity} has
* already been added or if a bean with name
* {@value BeanIds#SPRING_SECURITY_FILTER_CHAIN} has been configured by the user, this
* will back-off.
*/
@Configuration(proxyBeanMethods = false)
@ConditionalOnMissingBean(name = BeanIds.SPRING_SECURITY_FILTER_CHAIN)
@ConditionalOnClass(EnableWebSecurity.class)
@EnableWebSecurity
static class WebSecurityEnablerConfiguration {
}
}
@ConditionalOnWebApplication(type = Type.SERVLET)
이 조건이 맞아야 SpringBootWebSecurityConfiguration가 실행된다.
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
/**
* The required type of the web application.
* @return the required web application type
*/
Type type() default Type.ANY;
/**
* Available application types.
*/
enum Type {
/**
* Any web application will match.
*/
ANY,
/**
* Only servlet-based web application will match.
*/
SERVLET,
/**
* Only reactive-based web application will match.
*/
REACTIVE
}
}
이 코드에서
@Conditional(OnWebApplicationCondition.class)
@Conditinal은 컴포넌트의 Bean 등록여부에 조건을 달 수 있게하는 어노테이션이다.
OnWebApplicationCondition 클래스안에서 웹 어플리케이션이 맞는지 확인하는 로직이 들어있다.
그리고
@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();
}
}
이 static class 또한
@ConditionalOnDefaultWebSecurity
이런 조건이 존재하고
조건을 살펴보면
class DefaultWebSecurityCondition extends AllNestedConditions {
DefaultWebSecurityCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
static class Classes {
}
@ConditionalOnMissingBean({ SecurityFilterChain.class })
static class Beans {
}
}
@ConditionalOnClass({ SecurityFilterChain.class, HttpSecurity.class })
클래스패스내에 해당 클래스가 존재하는가? [ 의존성 추가로 가져온 라이브러리까지 포함]
-> 저 두 클래스는 security 의존성 추가시 생성되는 클래스 [ 사용자가 security를 사용할 마음이 있다라고 보는것 ]
@ConditionalOnMissingBean({ SecurityFilterChain.class })
SecurityFilterChain 빈이 아직 정의되지 않았을 때
-> SecurityFilterChain 빈이 없다는 것은 사용자가 커스텀 보안 설정을 하지 않았다는 의미이고,
이 경우 Spring Boot의 기본 보안 설정이 동작한다.
하지만 spring은 DefaultSecurityFilterChain라는 기본 시큐리티필터체인을 제공한다.
HttpSecurity에 다음과 같이 기본필터체인이 생성되는 메소드가 있다.
@Override
protected DefaultSecurityFilterChain performBuild() {
ExpressionUrlAuthorizationConfigurer<?> expressionConfigurer = getConfigurer(
ExpressionUrlAuthorizationConfigurer.class);
AuthorizeHttpRequestsConfigurer<?> httpConfigurer = getConfigurer(AuthorizeHttpRequestsConfigurer.class);
boolean oneConfigurerPresent = expressionConfigurer == null ^ httpConfigurer == null;
Assert.state((expressionConfigurer == null && httpConfigurer == null) || oneConfigurerPresent,
"authorizeHttpRequests cannot be used in conjunction with authorizeRequests. Please select just one.");
this.filters.sort(OrderComparator.INSTANCE);
List<Filter> sortedFilters = new ArrayList<>(this.filters.size());
for (Filter filter : this.filters) {
sortedFilters.add(((OrderedFilter) filter).filter);
}
return new DefaultSecurityFilterChain(this.requestMatcher, sortedFilters);
}
@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.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated());
authenticate : 증명하다 [ 인증 ]
authorize : 인가하다 [ 인가 ]
[모든 http request에 대해 인증을 해야 인가를 해준다는 설정]
http.formLogin(withDefaults());
- 사용자에게 웹 브라우저를 통해 접근할 수 있는 로그인 페이지를 제공합니다.
- 기본적으로 "/login" 경로에 로그인 폼을 생성합니다.
- 사용자 이름과 비밀번호를 입력할 수 있는 HTML 폼을 제공합니다.
- 주로 웹 애플리케이션의 사용자 인터페이스를 통한 인증에 사용됩니다.
Form Login은 세션 기반으로 동작하며, 로그인 후 세션을 유지합니다.
http.httpBasic(withDefaults());
HTTP Basic 인증은 주로 API 요청이나 프로그래밍 방식의 접근에 사용됩니다.
- user:password는 Base64로 인코딩되어 Authorization 헤더에 포함됩니다.
- 클라이언트는 이 인증 정보를 모든 요청에 포함시켜 서버에 전송합니다.
- 서버는 이 헤더를 해석하여 사용자를 인증합니다.
HTTP Basic은 매 요청마다 인증 정보를 전송하므로, HTTPS를 사용하여 보안을 강화해야 합니다.
Spring Security 기본설정은
두 방식을 함께 구현함으로써,
웹 애플리케이션은 일반 사용자와 API 클라이언트 모두에게 적절한 인증 방식을 제공할 수 있습니다.
SecurityBuilder / SecurityConfigurer
• SecurityBuilder는 빌더 클래스로서 웹 보안을 구성하는 빈 객체와 설정클래스들을 생성하는 역할을 하며
대표적으로 WebSecurity, HttpSecurity [ 구현체 ] 가 있다
• SecurityConfigurer 는 Http 요청과 관련된 보안처리를 담당하는 필터들을 생성하고 여러 초기화 설정에 관여한다
[ Spring Security는 인증,인가를 필터를 이용해서 처리하는 필터 기반 보안 프레임워크이다. ]
• SecurityBuilder는 SecurityConfigurer를 참조하고 있으며
인증 및 인가 초기화 작업은 SecurityConfigurer에 의해 진행된다.
[ 초기화 작업 => 필요한 스프링빈 생성 및 등록,
SecurityBuilder가 SecurityConfigurer를 사용해서 인증 및 인가 초기화 작업을 한다. ]
SecurityBuilder / SecurityConfigurer를 이용한 Spring Security 초기화 과정
@AutoConfiguration
1. 스프링 자동설정이 진행되면서 SecurityBuilder를 생성한다. [빌더 클래스 생성 ]
2. SecurityBuilder가 SecurityConfigurer를 생성한다. [설정 클래스 생성 ]
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
/**
* Initialize the {@link SecurityBuilder}. Here only shared state should be created
* and modified, but not properties on the {@link SecurityBuilder} used for building
* the object. This ensures that the {@link #configure(SecurityBuilder)} method uses
* the correct shared objects when building. Configurers should be applied here.
* @param builder
* @throws Exception
*/
void init(B builder) throws Exception;
/**
* Configure the {@link SecurityBuilder} by setting the necessary properties on the
* {@link SecurityBuilder}.
* @param builder
* @throws Exception
*/
void configure(B builder) throws Exception;
}
3. SecurityConfigurer가 SecurityBuilder를 매개변수로 받고 init, configure 메소드를 이용하여 초기화 작업을 진행한다.
[ 이 작업에서 필터도 생성한다. ]
다시한번 과정 확인
SecurityBuilder의 구현체인 HttpSecurity가 SecurityConfigurer의 구현체를 생성한다. [ 종류가 많다. ]
해당 설정 클래스의 init, configure 메소드를 이용하여 초기화 작업을 진행한다.
초기화 작업이 진행되는 중 필터도 생성된다.
[ Configurer 마다 필터가 존재하고 그 필터들을 다 생성한다. ]
[ 각 Configurer는 관련된 필터 생성 (예: FormLoginConfigurer는 UsernamePasswordAuthenticationFilter 생성) ]
[ 각 Configurer는 특정 보안 기능에 관련된 필터를 생성 ]
[ 생성된 필터들은 FilterChainProxy에 등록되어 보안 체인 구성 ]
HttpSecurityConfiguration 클래스
그 안에 HttpSecurity를 빈으로 등록하는 부분을 살펴보자.
@Bean(HTTPSECURITY_BEAN_NAME)
@Scope("prototype")
HttpSecurity httpSecurity() throws Exception {
LazyPasswordEncoder passwordEncoder = new LazyPasswordEncoder(this.context);
AuthenticationManagerBuilder authenticationBuilder = new DefaultPasswordEncoderAuthenticationManagerBuilder(
this.objectPostProcessor, passwordEncoder);
authenticationBuilder.parentAuthenticationManager(authenticationManager());
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
WebAsyncManagerIntegrationFilter webAsyncManagerIntegrationFilter = new WebAsyncManagerIntegrationFilter();
webAsyncManagerIntegrationFilter.setSecurityContextHolderStrategy(this.securityContextHolderStrategy);
// @formatter:off
http
.csrf(withDefaults())
.addFilter(webAsyncManagerIntegrationFilter)
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
applyCorsIfAvailable(http);
applyDefaultConfigurers(http);
return http;
}
스코프는 프로토타입
[Scope 관련 게시글 https://keeeeeepgoing.tistory.com/157]
HttpSecurity 초기화작업
http
.csrf(withDefaults())
.addFilter(webAsyncManagerIntegrationFilter)
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
자세한 내용은 다음에 배울것이다.
.csrf(withDefaults())
public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {
ApplicationContext context = getContext();
csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));
return HttpSecurity.this;
}
csrf() 메소드를 예시로 보면
new CsrfConfigurer<>(context))
CsrfConfigurer을 이용하여 설정해주는것이 보이는데
CsrfConfigurer를 타고 올라가보면 SecurityConfigurer를 상속받은 추상클래스를 구현하고있는것을 확인가능하다.
http
.csrf(withDefaults())
.addFilter(webAsyncManagerIntegrationFilter)
.exceptionHandling(withDefaults())
.headers(withDefaults())
.sessionManagement(withDefaults())
.securityContext(withDefaults())
.requestCache(withDefaults())
.anonymous(withDefaults())
.servletApi(withDefaults())
.apply(new DefaultLoginPageConfigurer<>());
http.logout(withDefaults());
// @formatter:on
applyCorsIfAvailable(http);
applyDefaultConfigurers(http);
return http;
이렇게 각각의 configurer를 통해 설정해주고 httpSecurity를 빈으로 등록해준다.
Debug를 통해 HttpSecurity를 살펴보면
많은 configurer를 통해 설정되어있는걸 확인가능하다.
formLogin또한 configurer로 설정하는 모습
[뭔가 설정을 한다하면 configurer를 이용한다.]
@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.build()가 되면 httpSecurity의 configurer안의 init,configure 메소드를 실행시켜 초기화 작업이 이루어진다.
'인프런 > 스프링 시큐리티 완전 정복 [6.x 개정판]' 카테고리의 다른 글
6) 인증 프로세스 - logout() ,RequestCache / SavedRequest (0) | 2024.10.11 |
---|---|
5) 인증프로세스 - BasicAuthenticationFilter , rememberMe() , RememberMeAuthenticationFilter, anonymous() (3) | 2024.10.10 |
4) 인증프로세스 - 폼 인증,폼 인증 필터, HTTP Basic 인증 (0) | 2024.10.08 |
3) 초기화 과정이해 - DelegatingFilterProxy / FilterChainProxy , 사용자 정의 보안 설정하기 (0) | 2024.10.05 |
2) 초기화 과정이해 - WebSecurity / HttpSecurity (3) | 2024.10.02 |
댓글