Spring Boot

[Spring Security] docs : Architecture(4) : FilterChain์˜ ์—ญํ• ๊ณผ ์ˆœ์„œ, ์ปค์Šคํ…€ ํ•„ํ„ฐ

kiritoni 2024. 8. 30. 16:20
๋ฐ˜์‘ํ˜•

๐Ÿ”ป Spring Security docs  ๊ฒŒ์‹œ๊ธ€ ๋ชฉ์ฐจ  

๋”๋ณด๊ธฐ

1. [Spring Security] docs : Getting Started

 

[Spring Security] docs : Getting Started

Spring Security๋Š” ์ธ์ฆ, ๊ถŒํ•œ๋ถ€์—ฌ ๋ฐ ์ผ๋ฐ˜์ ์ธ ๊ณต๊ฒฉ์— ๋Œ€ํ•œ ๋ณดํ˜ธ๋ฅผ ์ œ๊ณตํ•˜๋Š” ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ณด์•ˆ ์„ค์ •์— ์ถ”๊ฐ€์ ์œผ๋กœ ์‹ ๊ฒฝ์“ฐ์ง€ ์•Š๋”๋ผ๋„ ์•ˆ์ „ํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋น ๋ฅด๊ฒŒ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋„

kiritoni.tistory.com

 

 

2. [Spring Security] docs : Architecture (1) - Filter

 

[Spring Security] docs : Architecture (1) - Filter

Spring Security docs Hello Spring Security :: Spring SecurityRunning Spring Boot Application $ ./mvnw spring-boot:run ... INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: 8e557245-73e2-4286-969a

kiritoni.tistory.com

 

 

3. [Spring Security] docs: Architecture (2) - DelegatingFilterProxy

 

[Spring Security] docs: Architecture (2) - DelegatingFilterProxy

Spring Security docs Hello Spring Security :: Spring SecurityRunning Spring Boot Application $ ./mvnw spring-boot:run ... INFO 23689 --- [ restartedMain] .s.s.UserDetailsServiceAutoConfiguration : Using generated security password: 8e557245-73e2-4286-969a

kiritoni.tistory.com

 

 

4. [Spring Security] docs: Architecture (3) - FilterChainProxy & SecurityFilterChain

 

[Spring Security] docs: Architecture (3) - FilterChainProxy & SecurityFilterChain

2024.08.29 - [Spring Boot] - [Spring Security] docs: Architecture (2) - DelegatingFilterProxy [Spring Security] docs: Architecture (2) - DelegatingFilterProxySpring Security docs Hello Spring Security :: Spring SecurityRunning Spring Boot Application $ .

kiritoni.tistory.com

 

 

 

๐ŸŒฑ Spring Security docs ๋ฐ”๋กœ๊ฐ€๊ธฐ

 

Architecture :: Spring Security

The Security Filters are inserted into the FilterChainProxy with the SecurityFilterChain API. Those filters can be used for a number of different purposes, like authentication, authorization, exploit protection, and more. The filters are executed in a spec

docs.spring.io

 

 

Spring Security์˜ ๋ณด์•ˆ ํ•„ํ„ฐ๋Š” FilterChainProxy์— ์‚ฝ์ž…๋˜๋ฉฐ, SecurityFilterChain API๋ฅผ ํ†ตํ•ด ๊ตฌ์„ฑ๋œ๋‹ค. ์ด ํ•„ํ„ฐ๋“ค์€ ํŠน์ • ์ˆœ์„œ๋Œ€๋กœ ์‹คํ–‰๋˜์–ด์•ผ ์˜ฌ๋ฐ”๋ฅธ ํƒ€์ด๋ฐ์— ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ธ์ฆ์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•„ํ„ฐ๋Š” ๊ถŒํ•œ ๋ถ€์—ฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋Š” ํ•„ํ„ฐ๋ณด๋‹ค ๋จผ์ € ํ˜ธ์ถœ๋˜์–ด์•ผ ํ•œ๋‹ค. 

 

ํ•„ํ„ฐ์˜ ์ˆœ์„œ๋ฅผ ํ•ญ์ƒ ์™ธ์šฐ๊ณ  ์žˆ์„ ํ•„์š”๋Š” ์—†์ง€๋งŒ, ํ•„์š”ํ•  ๋•Œ์—๋Š” `FilterOrderRegistration` ์ฝ”๋“œ๋ฅผ ํ™•์ธํ•˜์—ฌ ์ˆœ์„œ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

๐Ÿ“ ๋ณด์•ˆ ๊ตฌ์„ฑ ๋ฐ ํ•„ํ„ฐ ์ˆœ์„œ ์˜ˆ์ œ

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .csrf(Customizer.withDefaults()) // CSRF ํ•„ํ„ฐ ์ถ”๊ฐ€
            .authorizeHttpRequests(authorize -> authorize
                .anyRequest().authenticated() // ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์ธ์ฆ ํ•„์š”
            )
            .httpBasic(Customizer.withDefaults()) // HTTP ๊ธฐ๋ณธ ์ธ์ฆ ํ•„ํ„ฐ ์ถ”๊ฐ€
            .formLogin(Customizer.withDefaults()); // ํผ ๋กœ๊ทธ์ธ ํ•„ํ„ฐ ์ถ”๊ฐ€
        return http.build();
    }
}

 

์ด ๊ตฌ์„ฑ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ˆœ์„œ๋กœ ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค. 

Filter ์ถ”๊ฐ€ ๋ฐฉ๋ฒ•
CsrfFilter HttpSecurity#csrf
UsernamePasswordAuthenticationFilter HttpSecurity#formLogin
BasicAuthenticationFilter HttpSecurity#httpBasic
AuthorizationFilter HttpSecurity#authorizeHttpRequests

 

1. CsrfFilter

์ฒซ ๋ฒˆ์งธ๋กœ ์‹คํ–‰๋˜์–ด CSRF(Cross-Site Request Forgery) ๊ณต๊ฒฉ์„ ๋ฐฉ์ง€ํ•œ๋‹ค. 

2. ์ธ์ฆ ํ•„ํ„ฐ

๋‘ ๋ฒˆ์งธ๋กœ ์‹คํ–‰๋˜์–ด ์š”์ฒญ์„ ์ธ์ฆํ•œ๋‹ค. 

`UsernamePasswordAuthenticationFilter`(ํผ ๋กœ๊ทธ์ธ์„ ํ†ตํ•œ ์ธ์ฆ) ์™€ `BasicAuthenticationFilter`(HTTP ๊ธฐ๋ณธ ์ธ์ฆ)๊ฐ€ ์ธ์ฆ ํ•„ํ„ฐ์— ํ•ด๋‹นํ•œ๋‹ค.

3. AuthorizationFilter

๋งˆ์ง€๋ง‰์œผ๋กœ ์‹คํ–‰๋˜์–ด ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ๋‹ค. 

์š”์ฒญ์ด ์ธ์ฆ๋˜์—ˆ๋Š”์ง€ ํ™•์ธํ•œ ๋’ค, ์‚ฌ์šฉ์ž๊ฐ€ ์š”์ฒญ์— ์•ก์„ธ์Šคํ•  ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. 

 

ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ๊ธฐ์กด ํ•„ํ„ฐ์˜ ๋™์ž‘์„ ๋ณ€๊ฒฝํ•˜๋ ค๋ฉด, ํ•„ํ„ฐ๊ฐ€ ์ •ํ™•ํ•œ ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜๋„๋ก ๋ณด์žฅํ•ด์•ผ ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ํ•„ํ„ฐ์˜ ์ˆœ์„œ๋ฅผ ์•Œ์•„๋‘๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. 

 

๋ณด์•ˆ ํ•„ํ„ฐ ๋ชฉ๋ก์€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹œ์ž‘ ์‹œ INFO ์ˆ˜์ค€์—์„œ ์ฝ˜์†”์— ์ถœ๋ ฅ๋œ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. 

 

2023-06-14T08:55:22.321-03:00  INFO 76975 --- [           main] o.s.s.web.DefaultSecurityFilterChain     : Will secure any request with [
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]

 

๐Ÿ“ ์ปค์Šคํ…€ ํ•„ํ„ฐ

Spring Security๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์— ํ•„์š”ํ•œ ๊ธฐ๋ณธ ๋ณด์•ˆ ํ•„ํ„ฐ๋“ค์„ ์ œ๊ณตํ•˜์ง€๋งŒ, ํŠน์ • ์š”๊ตฌ์‚ฌํ•ญ์— ๋งž๋Š” ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜๋„ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์‚ฌ์šฉ์ž๊ฐ€ ํ˜„์žฌ ์š”์ฒญ์˜ tenant ID ํ—ค๋”๋ฅผ ํ™•์ธํ•˜์—ฌ ํ•ด๋‹น ํ…Œ๋„ŒํŠธ์— ๋Œ€ํ•œ ์ ‘๊ทผ ๊ถŒํ•œ์ด ์žˆ๋Š”์ง€ ํ™•์ธํ•˜๋Š” ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ์‹ถ์„ ์ˆ˜๋„ ์žˆ๋‹ค. 

 

๐Ÿ”ป ์ฐธ๊ณ ์ž๋ฃŒ: Tenant ๋ž€?

๋”๋ณด๊ธฐ

Tenant๋Š” '์ž„์ฐจ์ธ'์ด๋ผ๋Š” ๋œป์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค. ์ฆ‰ ์ž์‹ ์˜ ๊ฑด๋ฌผ์ด ์•„๋‹Œ, ๋‹ค๋ฅธ ๊ฑด๋ฌผ์„ ๋นŒ๋ ค์„œ ์‚ฌ์šฉํ•˜๋Š” ์ฃผ์ฒด์ด๋‹ค. ์—ฌ๊ธฐ์„œ์˜ ํ…Œ๋„ŒํŠธ๋Š” ํด๋ผ์šฐ๋“œ ์ปดํ“จํŒ…, Saas(Software as a Service) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ์˜ ์ž„์ฐจ์ธ์„ ๋งํ•œ๋‹ค. ์ž์‹ ์˜ ์ž์›์ด ์•„๋‹Œ ์„œ๋น„์Šค ์ œ๊ณต์ž์˜ ํด๋ผ์šฐ๋“œ ์ž์›์„ ๋นŒ๋ ค์„œ ์„œ๋น„์Šค๋ฅผ ์ด์šฉํ•˜๋Š” ์ฃผ์ฒด๊ฐ€ Tenant์ด๋‹ค. ํด๋ผ์šฐ๋“œ ํ•˜๋‚˜์˜ ์ž์›์„ ์ชผ๊ฐœ์„œ tenant๋“ค์—๊ฒŒ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์€ Multi Tenancy๋ผ๊ณ  ํ•œ๋‹ค. ํ…Œ๋„ŒํŠธ๋งˆ๋‹ค ๊ณ ์œ  ID๊ฐ€ ์žˆ๋‹ค. ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ ์‹œ์Šคํ…œ์€ ํ…Œ๋„ŒํŠธ ID ํ—ค๋” ๋“ฑ์„ ํ†ตํ•ด ์š”์ฒญ์ž๊ฐ€ ์–ด๋–ค ํ…Œ๋„ŒํŠธ์ธ์ง€ ํŒŒ์•…ํ•˜๊ณ , ํ•ด๋‹น ํ…Œ๋„ŒํŠธ์— ๋งž๋Š” ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•œ๋‹ค. ํ…Œ๋„ŒํŠธ์˜ ์˜ˆ์‹œ๋กœ๋Š” ํด๋ผ์šฐ๋“œ ๊ธฐ๋ฐ˜ ์ด๋ฉ”์ผ ์„œ๋น„์Šค (Microsoft 365, Google Workspace), ERP ์‹œ์Šคํ…œ (SAP, Oracle ERP), ์†Œ์…œ ๋ฏธ๋””์–ด ๋˜๋Š” ํ˜‘์—… ํˆด (Slack, Jira) ๋“ฑ์ด ์žˆ๋‹ค. ์š”์•ฝํ•˜๋ฉด, ํ…Œ๋„ŒํŠธ๋Š” ๋‹ค์ค‘ ํ…Œ๋„ŒํŠธ ํ™˜๊ฒฝ์—์„œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ณต์œ ํ•˜๋Š” ๊ฐ๊ฐ์˜ ๊ณ ๊ฐ์ด๋‚˜ ์‚ฌ์šฉ์ž ๊ทธ๋ฃน์„ ๋งํ•œ๋‹ค. ๊ฐ ํ…Œ๋„ŒํŠธ๋Š” ์ž์ฒด์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๊ณ , ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋‚ด๋ถ€์ ์œผ๋กœ๋Š” ๋‹ค๋ฅธ ํ…Œ๋„ŒํŠธ์™€ ๊ฒฉ๋ฆฌ๋œ ์ƒํƒœ๋กœ ์กด์žฌํ•œ๋‹ค. 

 

1. ์ปค์Šคํ…€ ํ•„ํ„ฐ ์ƒ์„ฑ (`TenantFilter`)

import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.AccessDeniedException;

public class TenantFilter implements Filter {

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        // ์š”์ฒญ ํ—ค๋”์—์„œ ํ…Œ๋„ŒํŠธ ID๋ฅผ ๊ฐ€์ ธ์˜ด
        String tenantId = request.getHeader("X-Tenant-Id"); 

        // ํ˜„์žฌ ์‚ฌ์šฉ์ž๊ฐ€ ํ…Œ๋„ŒํŠธ์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ํ™•์ธ
        boolean hasAccess = isUserAllowed(tenantId); 
        if (hasAccess) {
            filterChain.doFilter(request, response); // ๋‹ค์Œ ํ•„ํ„ฐ๋กœ ์š”์ฒญ์„ ์ „๋‹ฌ
            return;
        }
        // ์ ‘๊ทผ ๊ถŒํ•œ์ด ์—†์„ ๊ฒฝ์šฐ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ด
        throw new AccessDeniedException("Access denied"); 
    }

}

 

2. ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ๋ณด์•ˆ ํ•„ํ„ฐ ์ฒด์ธ์— ์ถ”๊ฐ€ํ•˜๊ธฐ

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        // ๊ธฐ์กด ๋ณด์•ˆ ์„ค์ •๋“ค ...
        .addFilterBefore(new TenantFilter(), AuthorizationFilter.class); // AuthorizationFilter ์•ž์— TenantFilter๋ฅผ ์ถ”๊ฐ€
    return http.build();
}

 

3. ํ•„ํ„ฐ๋ฅผ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ์„ ์–ธํ•  ๋•Œ ์ค‘๋ณต ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ•ด์•ผ ํ•œ๋‹ค!

์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ์Šคํ”„๋ง ๋นˆ์œผ๋กœ ๋“ฑ๋กํ•  ๋•Œ (`@Component`๋ฅผ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ๊ตฌ์„ฑ ํด๋ž˜์Šค์—์„œ ์ง์ ‘ ๋นˆ์„ ์„ ์–ธํ•  ๋•Œ) ์ฃผ์˜๊ฐ€ ํ•„์š”ํ•˜๋‹ค. 

Spring Boot๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ Spring ๋นˆ์œผ๋กœ ๋“ฑ๋ก๋œ ํ•„ํ„ฐ๋ฅผ ๋‚ด์žฅ ์ปจํ…Œ์ด๋„ˆ์— ์ž๋™์œผ๋กœ ๋“ฑ๋กํ•œ๋‹ค. 

์ด ๊ฒฝ์šฐ, ํ•„ํ„ฐ๊ฐ€ ์ปจํ…Œ์ด๋„ˆ์— ์˜ํ•ด ํ•œ ๋ฒˆ ํ˜ธ์ถœ๋˜๊ณ , ์Šคํ”„๋ง ์„ธํ๋ฆฌํ‹ฐ์— ์˜ํ•ด ์ค‘๋ณต ํ˜ธ์ถœ๋  ์ˆ˜ ์žˆ๋‹ค. ํ•„ํ„ฐ๊ฐ€ ๋‘ ๋ฒˆ ํ˜ธ์ถœ๋˜๋ฉด ์ž˜๋ชป๋œ ์ˆœ์„œ๋กœ ์‹คํ–‰๋˜๋Š” ๋ฌธ์ œ๋ฅผ ์ผ์œผํ‚ฌ ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค. 

 

์ค‘๋ณต ํ˜ธ์ถœ์„ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด์„œ `FilterRegistrationBean`์„ ์‚ฌ์šฉํ•˜์—ฌ ํ•„ํ„ฐ์˜ ๋“ฑ๋ก์„ ๋น„ํ™œ์„ฑํ™”ํ•ด์•ผ ํ•œ๋‹ค. 

@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
    FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
    registration.setEnabled(false); // ์ปจํ…Œ์ด๋„ˆ์— ํ•„ํ„ฐ ๋“ฑ๋ก ๋น„ํ™œ์„ฑํ™”
    return registration;
}

 

๊ฒฐ๊ณผ์ ์œผ๋กœ `TenantFilter`๋Š” ์ธ์ฆ ํ•„ํ„ฐ๋“ค ์ดํ›„, ๊ถŒํ•œ ๋ถ€์—ฌ ํ•„ํ„ฐ ์ด์ „์— ํ˜ธ์ถœ๋œ๋‹ค. ์ปค์Šคํ…€ ํ•„ํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜์—ฌ Spring Security์˜ ๋ณด์•ˆ ์ฒด์ธ์„ ํ™•์žฅํ•˜๊ณ  ๋ณด๋‹ค ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค. 

๋ฐ˜์‘ํ˜•