[Spring Security] SecurityBuilder์ SecurityConfigurer
๐ ๊ฐ์
Spring Security
๋ ์ธ์ฆ/์ธ๊ฐ ๋ฉ์ปค๋์ฆ์ filter chain
์ ๊ธฐ๋ฐ์ผ๋ก ์ฒ๋ฆฌํ๋ค.
SecurityBuilder
, SecurityConfigurer
๋ ํํฐ ์ฒด์ธ๊ณผ ๊ด๋ จ๋ ๋น๋ค์ ๋น๋ ํจํด์ ์ฌ์ฉํ์ฌ ์์ฑํ๋ค.
๐ SecurityBuilder, SecurityConfigurer
1
2
3
4
5
public interface SecurityBuilder<O> {
O build() throws Exception;
}
SecurityBuilder
๋ ์น ๋ณด์์ ๊ตฌ์ฑํ๋ ๋ฐ ํ์ํ ๋น ๊ฐ์ฒด์ ์ค์ ํด๋์ค๋ค์ ์์ฑํ๋ ๋น๋์ด๋ค. ์ ๋ค๋ฆญ ์ธํฐํ์ด์ค์ด๋ฉฐ, ๋น๋ ํจํด์ผ๋ก ๋ณด์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
๋ํ์ ์ธ ๊ตฌํ์ฒด๊ฐ ์๋ค. WebSecurity
๋ FilterChainProxy
๋ฅผ ์์ฑํ๋ค. ์ ์ฒด ์น ๋ณด์ ํํฐ ์ฒด์ธ์ ๊ด๋ฆฌํ๋ ์ต์์ ํ๋ก์์ด๋ค. ignoring
๊ณผ ๊ฐ์ด ํน์ ์์ฒญ์ ๋ณด์ ํํฐ์์ ์์ ์ ์ธํ๋ ์ ์ญ์ ์ธ ์ค์ ์ ํ ๋ ์ฌ์ฉํ๋ค.
๋ ๋ค๋ฅธ ๊ตฌํ์ฒด์ธ HttpSecurity
๋ SecurityFilterChain
์ ์์ฑํ๋ฉฐ, HTTP ์์ฒญ์ ๋ํ ์ธ๋ถ์ ์ธ ๋ณด์ ๊ท์น(์ธ์ฆ/์ธ๊ฐ, ์ธ์
, CSRF ๋ฐฉ์ด ๋ฑ)์ ์ค์ ํ๋ค.
1
2
3
4
5
6
public interface SecurityConfigurer<O, B extends SecurityBuilder<O>> {
void init(B builder) throws Exception;
void configure(B builder) throws Exception;
}
SecurityConfigurer
๋ SecurityBuilder
์ ๊ตฌ์ฒด์ ์ธ ๋ณด์ ์ค์ ์ ์ถ๊ฐํ๊ณ ์ปค์คํฐ๋ง์ด์งํ๋ค. formLogin
, csrf
์ ๊ฐ์ ์ธ๋ถ์ ์ธ ์ค์ ์ด ๊ฐ๋ฅํ๋ฉฐ ์ค์ ํํฐ๋ฅผ ์์ฑํ๊ณ ์ด๊ธฐํํ๋ค.
init
๋ฉ์๋๋ configure
์ ์ ํ์ํ ๊ณต์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ฑฐ๋ ์ํ๋ฅผ ์ด๊ธฐํํ๋ ๋ฐ ์ฌ์ฉ๋๋ค.
configurer
๋ฉ์๋๋ init
์ด ๋ชจ๋ ๋๋ ํ ํธ์ถ๋๋ฉฐ SecurityBuilder
์ ํ์ํ ์์ฑ์ ์ค์ ํ๊ณ ์ค์ ํํฐ๋ฅผ ์์ฑํ์ฌ ํํฐ ์ฒด์ธ์ ์ถ๊ฐํ๋ ๋ฑ ๊ตฌ์ฒด์ ์ธ ๋ณด์ ๊ตฌ์ฑ์ ์ ์ฉํ๋ค.
.formLogin()
, .csrf()
์ ๊ฐ์ ๋ฉ์๋๋ ๋ด๋ถ์ ์ผ๋ก FormLoginConfigurer
๋ CsrfConfigurer
์ ๊ฐ์ SecurityConfigurer
์ ๊ตฌํ์ฒด๋ฅผ ์์ฑํ๊ณ SecurityBuilder
์ ์ถ๊ฐํ๋ค.
1
2
3
4
5
6
7
8
@Override
public final O build() throws Exception {
if (this.building.compareAndSet(false, true)) {
this.object = doBuild();
return this.object;
}
throw new AlreadyBuiltException("This object has already been built");
}
build
๋ฉ์๋๋ ๋ด๋ถ์ ์ผ๋ก doBuild
๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉฐ,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected final O doBuild() throws Exception {
synchronized (this.configurers) {
this.buildState = BuildState.INITIALIZING;
beforeInit();
init();
this.buildState = BuildState.CONFIGURING;
beforeConfigure();
configure();
this.buildState = BuildState.BUILDING;
O result = performBuild();
this.buildState = BuildState.BUILT;
return result;
}
}
doBuild
๋ฉ์๋๋ ๋ด๋ถ์์ init
๊ณผ configure
๋ฉ์๋๋ฅผ ํธ์ถํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@SuppressWarnings("unchecked")
private void init() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.init((B) this);
}
for (SecurityConfigurer<O, B> configurer : this.configurersAddedInInitializing) {
configurer.init((B) this);
}
}
@SuppressWarnings("unchecked")
private void configure() throws Exception {
Collection<SecurityConfigurer<O, B>> configurers = getConfigurers();
for (SecurityConfigurer<O, B> configurer : configurers) {
configurer.configure((B) this);
}
}
init
์ configure
๋ฉ์๋๋ ๊ฐ๊ฐ ๋ด๋ถ์ ์ผ๋ก SecurityConfigurer
์ init
๊ณผ configure
๋ฉ์๋๋ฅผ ํธ์ถํ๋ค.
1
2
3
4
public <C extends SecurityConfigurer<O, B>> C apply(C configurer) throws Exception {
add(configurer);
return configurer;
}
AbstractConfiguredSecurityBuilder
์๋ apply
๋ฉ์๋๊ฐ ์๋๋ฐ, SecurityConfigurer
๋ฅผ SecurityBuilder
๊ตฌํ์ฒด์ ์ถ๊ฐํ๋ค.
SecurityBuilder
์ ๋ํ์ ์ธ ๊ตฌํ์ฒด๋ก WebSecurity
์ HttpSecurity
๊ฐ ์์๋ค.
WebSecurity
์ build
๋ฉ์๋๋ FilterChainProxy
๋ฅผ ๋ฆฌํดํ๋ฉฐ, HttpSecurity
์ build
๋ฉ์๋๋ SecurityFilterChain
์ ๋ฆฌํดํ๋ค.
1
2
3
4
5
6
public class FilterChainProxy extends GenericFilterBean {
// ...
private List<SecurityFilterChain> filterChains;
}
FilterChainProxy
๋ ๋ด๋ถ์ ์ผ๋ก SecurityFilterChain
์ ๊ฐ์ง๊ณ ์๋ค. ์์ฒญ์ด ๋ค์ด์ค๋ฉด SecurityFilterChain
์ ํตํด ์ธ์ฆ์ ์ํํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Autowired(required = false)
public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> objectPostProcessor,
ConfigurableListableBeanFactory beanFactory) throws Exception {
this.webSecurity = objectPostProcessor.postProcess(new WebSecurity(objectPostProcessor));
if (this.debugEnabled != null) {
this.webSecurity.debug(this.debugEnabled);
}
List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers = new AutowiredWebSecurityConfigurersIgnoreParents(
beanFactory)
.getWebSecurityConfigurers();
webSecurityConfigurers.sort(AnnotationAwareOrderComparator.INSTANCE);
Integer previousOrder = null;
Object previousConfig = null;
for (SecurityConfigurer<Filter, WebSecurity> config : webSecurityConfigurers) {
Integer order = AnnotationAwareOrderComparator.lookupOrder(config);
if (previousOrder != null && previousOrder.equals(order)) {
throw new IllegalStateException("@Order on WebSecurityConfigurers must be unique. Order of " + order
+ " was already used on " + previousConfig + ", so it cannot be used on " + config + " too.");
}
previousOrder = order;
previousConfig = config;
}
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
this.webSecurity.apply(webSecurityConfigurer);
}
this.webSecurityConfigurers = webSecurityConfigurers;
}
WebSecurityConfiguration
๋น์ด ์์ฑ๋๊ณ ์ด๊ธฐํ๋ ๋, setFilterChainProxySecurityConfigurer
๊ฐ ํธ์ถ๋๋ค. ๋ชจ๋ SecurityConfigurer
๋ฅผ apply
๋ฉ์๋๋ฅผ ํตํด WebSecurity
์ ์ถ๊ฐํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@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
applyDefaultConfigurers(http);
return http;
}
WebSecurityConfiguration
๋ฅผ ํตํด FilterChainProxy
๊ฐ์ฒด๋ฅผ ๋ง๋ค๊ธฐ ์ํด SecurityFilterChain
๋น์ ์์งํด์ ํ๋ค. ์ด ๊ณผ์ ์์ HttpSecurity
๊ฐ ํ์ํ๋ฏ๋ก, HttpSecurityConfiguration
์ httpSecurity
๋ฉ์๋๊ฐ ํธ์ถ๋๋ค. ๋ฉ์๋ ์ฒด์ด๋์ ํตํด HttpSecurity
๊ฐ์ฒด์ ์ฌ๋ฌ SecurityConfigurer
๋ฅผ ์ถ๊ฐํ๋ค.
1
2
3
4
5
public HttpSecurity csrf(Customizer<CsrfConfigurer<HttpSecurity>> csrfCustomizer) throws Exception {
ApplicationContext context = getContext();
csrfCustomizer.customize(getOrApply(new CsrfConfigurer<>(context)));
return HttpSecurity.this;
}
csrf
๋ฉ์๋๋ฅผ ๋ณด๋ฉด, getOrApply
๋ฉ์๋๋ฅผ ํตํด CsrfConfigurer
๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ๋๋จธ์ง ๋ฉ์๋๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์ ์ ํ SecurityConfigurer
๋ฅผ ์ถ๊ฐํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
@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();
}
}
์ดํ httpSecurity
๋ฉ์๋์์ ๋ฆฌํด๋ HttpSecurity
๋ SecurityFilterChainConfiguration
์ build
๋ฉ์๋๋ฅผ ํตํด SecurityFilterChain
์ ์ฃผ์
๋๋ค.
1
2
3
4
@Autowired(required = false)
void setFilterChains(List<SecurityFilterChain> securityFilterChains) {
this.securityFilterChains = securityFilterChains;
}
๋ง๋ค์ด์ง SecurityFilterChain
์ WebSecurityConfiguration
์ setFilterChains
๋ฉ์๋๋ฅผ ํตํด ์ค์ ๋๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
public Filter springSecurityFilterChain() throws Exception {
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
if (!hasFilterChain) {
this.webSecurity.addSecurityFilterChainBuilder(() -> {
this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
this.httpSecurity.formLogin(Customizer.withDefaults());
this.httpSecurity.httpBasic(Customizer.withDefaults());
return this.httpSecurity.build();
});
}
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
this.webSecurity.addSecurityFilterChainBuilder(() -> securityFilterChain);
}
for (WebSecurityCustomizer customizer : this.webSecurityCustomizers) {
customizer.customize(this.webSecurity);
}
return this.webSecurity.build();
}
์ค์ ๋ SecurityFilterChain
์ springSecurityFilterChain
๋ฉ์๋์์ ๋น๋ ํํ๋ก ๊ฐ์ธ์ WebSecurity
์ ์ถ๊ฐํ๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Override
protected Filter performBuild() throws Exception {
// ...
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
securityFilterChains.add(securityFilterChain);
requestMatcherPrivilegeEvaluatorsEntries
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
}
if (this.privilegeEvaluator == null) {
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
requestMatcherPrivilegeEvaluatorsEntries);
}
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
// ...
}
performBuild
๋ฉ์๋๋ WebSecurity
๊ฐ์ฒด์ build
๋ฉ์๋ ๋ด๋ถ์์ ํธ์ถ๋๋ค. addSecurityFilterChainBuilder
๋ฉ์๋๋ฅผ ํตํด ์์งํ SecurityFilterChain
์ ๋น๋๋ฅผ ๋ค์ SecurityFilterChain
๊ฐ์ฒด๋ก ๋ฆฌํด๋ฐ๊ณ , ์ด๋ค์ ํตํด FilterChainProxy
๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
๐ ๋์ ๊ณผ์
์ ์ฒด์ ์ธ ๋์ ๊ณผ์ ์ ์ ๋ฆฌํ์ฌ ์ดํด๋ณด์.
WebSecurityConfiguration
,HttpSecurityConfiguration
๊ณผ ๊ฐ์ ์ค์ ํ์ผ์ด IoC ์ปจํ ์ด๋์ ๋ก๋๋๋ค.WebSecurityConfiguration
๋น์ ์ด๊ธฐํํ๋ ๊ณผ์ ์์setFilterChainProxySecurityConfigurer
๋ฉ์๋๊ฐ ํธ์ถ๋๊ณ ,WebSecurity
๊ฐ์ฒด๊ฐ ์์ฑ๋๋ค.- ์กด์ฌํ๋ ๋ชจ๋
WebSecurityConfigurer
ํ์ ์ ๋น์ ์ฐพ๊ณ ,apply
๋ฉ์๋๋ฅผ ํตํดWebSecurity
๋น๋์ ์ ์ฉํ๋ค. - ๋น์ผ๋ก ๋ฑ๋ก๋
SecurityFilterChain
์ ์ฐพ๋๋ค. ๋จ,SecurityFilterChain
๋น์ ์์ฑํ๊ธฐ ์ํด ํ๋ผ๋ฏธํฐ๋กHttpSecurity
๊ฐ์ฒด๊ฐ ํ์ํ๋ฏ๋กHttpSecurityConfiguration
์httpSecurity
๋ฉ์๋๋ฅผ ํตํด ์๋ก์ดHttpSecurity
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ํ๋ผ๋ฏธํฐ์ ์ฃผ์ ํ๋ค. - ์ฃผ์
๋ฐ์
http
๊ฐ์ฒด์ ๋ฉ์๋ ์ฒด์ด๋์ ํตํด ๋ณด์ ์ค์ ์ ์ ์ฉํ๊ณ ,build
๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. build
๋ ๋ด๋ถ์ ์ผ๋กdoBuild
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌinit
๊ณผconfigure
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ณด์ ํํฐ๋ฅผ ๊ตฌ์ฑํ๊ณ , ์ด๋ค์ ๋ด์SecurityFilterChain
๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ ๋ฆฌํดํ๋ค. ์์ฑ๋SecurityFilterChain
๊ฐ์ฒด๋ ๋น์ผ๋ก ๋ฑ๋ก๋๋ค.- ๋ค์ 3๋ฒ ๋์์์,
setFilterChains
๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ์กด์ฌํ๋ ๋ชจ๋SecurityFilterChain
๋น๋ค์ ํ๋์List
๋ก ๋ฌถ๋๋ค. - ์ดํ
springSecurityFilterChain
๋ฉ์๋์์ ์ฃผ์ ๋ฐ์securityFilterChains
๋ฆฌ์คํธ๋ฅผ ์ํํ๋ฉฐSecurityFilterChain
๊ฐ์ฒด๋ฅผ ๋น๋ ํํ๋ก ๊ฐ์ธWebSecurity
๊ฐ์ฒด์ ๋ฑ๋กํ๋ค. - ๋ชจ๋ ๋ฑ๋ก๋๋ฉด
WebSecurity
์build
๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. ์ด ๋ฉ์๋๋ ๋ด๋ถ์ ์ผ๋กperformBuild
๋ฉ์๋๋ฅผ ํธ์ถํ๋ค. performBuild
๋ฉ์๋์์ ๋น๋ ํํ๋ก ๊ฐ์ผSecurityFilterChain
์ ๋ค์ ์๋SecurityFilterChain
๊ฐ์ฒด๋ก ๋ณํํ ํ, ์ด๋ค์ ํตํด ์ต์ขFilterChainProxy
๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค. ์์ฑ๋FilterChainProxy
๊ฐ์ฒด๋ ์คํ๋ง ์ปจํ ์ด๋์ ๋น์ผ๋ก ๋ฑ๋ก๋๋ค.
๐ ์ฐธ๊ณ
https://ttl-blog.tistory.com/1165