
์๋ธ๋ฆฟ ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ Spring Security๊ฐ ์์ฒญ์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ๋์ง ์ดํดํ๋ ค๋ฉด ํํฐ(Filter)์ ๊ตฌ์กฐ์ ์ญํ ์ ํ์ ํ๋ ๊ฒ์ด ํ์์ ์ ๋๋ค. Spring Security๋ ํํฐ ์ฒด์ธ์ ํตํด ์๋ํ๋ฉฐ, ์์ฒญ์ด ์ฌ๋ฌ ํํฐ๋ฅผ ์ฐจ๋ก๋ก ๊ฑฐ์ณ ๋ณด์์ ์ ์ฉํ๋ ๊ตฌ์กฐ๋ฅผ ์ด๋ฃน๋๋ค.

1๏ธโฃ ํด๋ผ์ด์ธํธ ์์ฒญ
2๏ธโฃ FilterChain ์์ฑ
3๏ธโฃ DispatcherServlet ํธ์ถ
DispatcherServlet์
๋๋ค.DispatcherServlet์ ๋ชจ๋ HTTP ์์ฒญ์ ์ค์์์ ์ฒ๋ฆฌํ๋ฉฐ, ์์ฒญ์ ํด๋น ์ปจํธ๋กค๋ฌ๋ก ๋ผ์ฐํ
ํฉ๋๋ค.ํํฐ๋ FilterChain์ ํตํด ์์ฒญ๊ณผ ์๋ต์ ์กฐ์ํ ์ ์์ต๋๋ค:
HttpServletResponse์ ์๋ต์ ์์ฑํ์ฌ ์์ฒญ ์ฒ๋ฆฌ๋ฅผ ์ข
๋ฃํฉ๋๋ค.FilterChain์ ๊ฐ ํํฐ๊ฐ ์์ฒญ์ ์์ฐจ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์๊ฒ ์ฐ๊ฒฐํ๋ ๊ตฌ์กฐ๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ฐ ํํฐ๋ doFilter ๋ฉ์๋๋ก ๋ค์ ํํฐ์ ์์ฒญ์ ๋๊ธฐ๊ฑฐ๋, ํ์ ์ ์ง์ ์๋ต์ ์์ฑํด ์ข
๋ฃํ ์ ์์ต๋๋ค.
์ด ๊ฐ๋ ฅํ ๋ฉ์ปค๋์ฆ ๋๋ถ์ Spring Security์์๋ ๋ค์๊ณผ ๊ฐ์ ๋ณด์ ํํฐ๋ฅผ ์ค์ ํ์ฌ ์์ฒญ ๋ณดํธ๋ฅผ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค:
ํํฐ ์ฒด์ธ์ ๋ณด์ ๊ธฐ๋ฅ์ ์์ฒญ๊ณผ ์๋ต ํ๋ฆ์ ๋์ ์ผ๋ก ์ถ๊ฐํ๋ฉฐ ์ ์ดํ ์ ์๋ ๊ฐ๋ ฅํ ๊ตฌ์กฐ์ ๋๋ค. ๊ฐ ํํฐ๊ฐ ์์์ ๋ฐ๋ผ ์๋ํด ๋ณด์์ ์ ์ง์ ์ผ๋ก ๊ฐํํฉ๋๋ค. ๐ชโจ
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// do something before the rest of the application
chain.doFilter(request, response); // invoke the rest of the application
// do something after the rest of the application
}
์ด๋ ๊ฒ ๊ตฌ์ฑ๋ ํํฐ ์ฒด์ธ ๋๋ถ์, Spring Security์์๋ ๋ณด๋ค ์ ์ฐํ๊ณ ๊ฐ๋ ฅํ ๋ณด์ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅํฉ๋๋ค. ๐๐ฅ
CSRF(Cross-Site Request Forgery)๋ ์ฌ์ฉ์๊ฐ ์๋์น ์๊ฒ ๊ณต๊ฒฉ์์ ์ํด ์กฐ์๋ ์์ฒญ์ ์น ์ ํ๋ฆฌ์ผ์ด์ ์ผ๋ก ๋ณด๋ด๋๋ก ์ ๋ํ๋ ์น๋ช ์ ์ธ ๊ณต๊ฒฉ ๋ฐฉ์์ ๋๋ค. ์ด ๊ณต๊ฒฉ์ ์ฌ์ฉ์๊ฐ ์ด๋ฏธ ๋ก๊ทธ์ธ๋ ์ธ์ ์ ์ ์ฉํด ์ด๋ฃจ์ด์ง๋ฉฐ, ๋ฏผ๊ฐํ ์์ ์ด ๋ฌด๋จ์ผ๋ก ์คํ๋ ์ ์์ด ๋งค์ฐ ์ฌ๊ฐํ ๋ณด์ ์ํ์ด ๋ฉ๋๋ค.
1๏ธโฃ ์ฌ์ฉ์ ๋ก๊ทธ์ธ
2๏ธโฃ ์ ์์ ์ธ ์ฌ์ดํธ ๋ฐฉ๋ฌธ
3๏ธโฃ ์ฌ์ดํธ A๋ก ์์ฒญ ์ ์ก
4๏ธโฃ ์๋์น ์์ ์์ ์คํ
CSRF ํ ํฐ ์ฌ์ฉ ๐
SameSite ์ฟ ํค ์ค์ ๐ช
SameSite ์์ฑ์ ์ค์ ํด ์ฟ ํค๊ฐ ๋์ผ ์ฌ์ดํธ ๋ด์์๋ง ์ ์ก๋๋๋ก ์ ํํฉ๋๋ค. Strict: ๋์ผ ์ฌ์ดํธ ๋ด ์์ฒญ๋ง ์ฟ ํค ์ ์ก Lax: ์์ ํ ๋ฐฉ์์ ์์ฒญ(GET)๋ง ์ฟ ํค ์ ์ก์ฌ์ฉ์ ์ ๋ ฅ ๊ฒ์ฆ ๐
Referer ๋ฐ Origin ํค๋ ํ์ธ ๐
CSRF ๊ณต๊ฒฉ์ ์ฌ์ฉ์๊ฐ ์ ๋ขฐํ๋ ์ํ(๋ก๊ทธ์ธ ์ธ์ )๋ฅผ ์ ์ฉํด ๋ฏผ๊ฐํ ์์ ์ ์ํํ๋๋ก ์ ๋ํฉ๋๋ค. ํนํ ๊ธ์ต ์ฌ์ดํธ๋ ์ค์ํ ์ ๋ณด๊ฐ ์ค๊ฐ๋ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์ด๋ฌํ ๋ณด์ ์ํ์ ๋ฐฉ์งํ๊ธฐ ์ํด CSRF ํ ํฐ์ ํตํ ๋ฐฉ์ด๊ฐ ํ์์ ์ ๋๋ค. CSRF ๋ฐฉ์ด๋ ์ ์์ ์ธ ์ฌ์ดํธ๊ฐ ์ฌ๋ฐ๋ฅธ ํ ํฐ์ ํฌํจํ ์ ์๋๋ก ํ์ฌ, ์ฌ์ฉ์์ ์ ํ๋ฆฌ์ผ์ด์ ๋ชจ๋์ ์์ ์ ๋ณด์ฅํฉ๋๋ค! ๐ก๏ธโจ
Spring์ DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ํํฐ์ Spring ApplicationContext์ Bean์ ์ฐ๊ฒฐํด์ฃผ๋ ์ค์ํ ๋ฉ์ปค๋์ฆ์ผ๋ก, ํํฐ ํํ๋ก ์ธ์ฆ, ๊ถํ ๋ถ์ฌ, ํธ๋์ญ์ ๊ด๋ฆฌ ๋ฑ์ ์์ฝ๊ฒ ๊ตฌํํ ์ ์๊ฒ ํด์ค๋๋ค. Spring์์ ๊ด๋ฆฌํ๋ Bean์ ํํฐ๋ก ์ฌ์ฉํ ์ ์๋๋ก ๋๋ DelegatingFilterProxy์ ์๋ ์๋ฆฌ์ ์ค์ ๋ฐฉ๋ฒ์ ์์๋ณด๊ฒ ์ต๋๋ค.
์ผ๋ฐ์ ์ผ๋ก ์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ์์ฒด ํ์ค์ ๋ฐ๋ผ ํํฐ ์ธ์คํด์ค๋ฅผ ๊ด๋ฆฌํ์ฌ Spring์ด ๊ด๋ฆฌํ๋ Bean์ ์ธ์ํ์ง ๋ชปํฉ๋๋ค. DelegatingFilterProxy๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ์ฌ, ์๋ธ๋ฆฟ ์ปจํ ์ด๋๊ฐ Spring Bean์ ํํฐ๋ก ์ธ์ํ๊ณ ์ฌ์ฉํ ์ ์๋๋ก ์ฐ๊ฒฐํฉ๋๋ค.
1๏ธโฃ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ๋ฑ๋ก
web.xml์ด๋ Spring Boot ์๋ ์ค์ ์ ํตํด ์๋ธ๋ฆฟ ์ปจํ
์ด๋์ DelegatingFilterProxy๋ฅผ ๋ฑ๋กํฉ๋๋ค.2๏ธโฃ Spring Bean์ผ๋ก ์์
doFilter ๋ฉ์๋๋ ์์ฒญ์ Spring ApplicationContext์ ํน์ Bean์ ์ ๋ฌํด ์ค์ ํํฐ ์์
์ ์์ํฉ๋๋ค.3๏ธโฃ FilterChain ๋ด ์์น
web.xml์์ DelegatingFilterProxy ๋ฑ๋กํ๊ธฐ (Spring MVC ์ฌ์ฉ ์)<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
์ ์ค์ ์ผ๋ก springSecurityFilterChain์ด๋ผ๋ ์ด๋ฆ์ DelegatingFilterProxy๊ฐ ๋ฑ๋ก๋์ด ๋ชจ๋ ๊ฒฝ๋ก(/)์ ๋ํด ์๋ํฉ๋๋ค. Spring Security์ ๊ฐ์ ์ธ์ฆ ๋ฐ ๊ถํ ๊ด๋ฆฌ์ ์ ์ฉํฉ๋๋ค. ๐
Spring Boot์์๋ @Component๋ก ํํฐ Bean์ ๋ฑ๋กํ ์ ์์ต๋๋ค:
@Component("customFilter")
public class CustomFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
// ํํฐ ๋์ ๊ตฌํ
chain.doFilter(request, response);
}
}
์ด ์ค์ ์ผ๋ก customFilter๋ผ๋ ์ด๋ฆ์ ํํฐ๊ฐ Spring ApplicationContext์ ์ํด ๊ด๋ฆฌ๋๋ฉฐ, DelegatingFilterProxy๊ฐ ์ด๋ฅผ ์๋ธ๋ฆฟ ์ปจํ
์ด๋์์ ์ฌ์ฉํ ์ ์๊ฒ ํฉ๋๋ค.
DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring์ ์ฐ๊ฒฐํด ์ฃผ๋ฉฐ, Spring Security์ ํธ๋์ญ์ ๊ด๋ฆฌ ๋ฑ ๋ค์ํ Spring ๊ธฐ๋ฅ์ ํํฐ๋ก ์ฝ๊ฒ ํตํฉํ ์ ์๋ ๊ฐ๋ ฅํ ๋๊ตฌ์ ๋๋ค. ๐ก๏ธโจ

DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring์ ๋น ๊ด๋ฆฌ ์์คํ ์ ์ฐ๊ฒฐํด์ฃผ๋ ์ญํ ๋ก, ์๋ธ๋ฆฟ ์ปจํ ์ด๋๊ฐ Spring ๋น์ผ๋ก ๋ฑ๋ก๋ ํํฐ๋ฅผ ์ธ์ํ๊ฒ ํด์ฃผ๋ '๋ค๋ฆฌ' ์ญํ ์ ํฉ๋๋ค. ํ์ง๋ง ์ด ์ญํ ์ด ์ค์ ํํฐ ์คํ๊ณผ๋ ์ฐจ์ด๊ฐ ์๋ค๋ ์ ์ด ์ค์ํฉ๋๋ค.
1๏ธโฃ ํํฐ ๋น ์ ์
2๏ธโฃ DelegatingFilterProxy ๋ฑ๋ก
3๏ธโฃ FilterChain์์ ํธ์ถ
4๏ธโฃ DelegatingFilterProxy๊ฐ ํํฐ ๋น์ ์์
๋ฐ๋ผ์ DelegatingFilterProxy๋ Spring๊ณผ ์๋ธ๋ฆฟ ์ปจํ ์ด๋ ๊ฐ์ ์ฐ๊ฒฐ์ ๋์์ฃผ๋ ์ญํ ์ผ ๋ฟ, ์์ฒญ์ด ๋ค์ด์ค๊ณ FilterChain์ด ์คํ๋ ๋์์ผ ์ค์ ํํฐ๊ฐ ์๋ํ๊ฒ ๋ฉ๋๋ค. ์ด ์ฐจ์ด์ ์ ์ดํดํ๋ ๊ฒ์ด Spring Security์ ๊ฐ์ ๋ณด์ ํํฐ ์ค์ ์์ ๋งค์ฐ ์ค์ํฉ๋๋ค! ๐ก๏ธ๐ฅ
DelegatingFilterProxy์ ์์ฌ ์ฝ๋๋ฅผ ํตํด Spring Bean์ผ๋ก ๋ฑ๋ก๋ ํํฐ๊ฐ ์ด๋ป๊ฒ ์ง์ฐ ์ด๊ธฐํ(lazy initialization) ๋ฐฉ์์ผ๋ก ๋ถ๋ฌ์์ง๋์ง ์ ์ค๋ช ํด์ฃผ์ จ์ต๋๋ค. ์ด ์ฝ๋๋ DelegatingFilterProxy๊ฐ ์์ฒญ์ ๋ฐ์ ๋๋ง๋ค ApplicationContext์์ ํด๋น ์ด๋ฆ์ผ๋ก ๋ฑ๋ก๋ Bean์ ์ฐพ์ ๊ทธ ์ธ์คํด์ค๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฐฉ์์ ๋ณด์ฌ์ค๋๋ค.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Filter delegate = getFilterBean(someBeanName);
delegate.doFilter(request, response, chain);
}
someBeanName์ด๋ผ๋ ์ด๋ฆ์ผ๋ก ApplicationContext์์ ๋ฑ๋ก๋ ํํฐ ๋น์ ์ฐพ์, ์ง์ฐ ์ด๊ธฐํ ๋ฐฉ์์ผ๋ก ๊ฐ์ ธ์ต๋๋ค.delegate๋ Bean Filter0์ ์ธ์คํด์ค์ด๋ฉฐ, DelegatingFilterProxy๋ ์์ฒญ์ ์ด ํํฐ ๋น์ผ๋ก ์ ๋ฌํด ์์
์ ์์ํฉ๋๋ค.์ด๋ฌํ ๋ฐฉ์์ผ๋ก DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ์์ฒญ์ ApplicationContext์ ํํฐ ๋น์ผ๋ก ์ฐ๊ฒฐํ์ฌ, Spring์์ ๊ด๋ฆฌํ๋ ํํฐ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ๊ฒ ๋ง๋ญ๋๋ค.
DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์์ ํํฐ๋ก ์๋ํ๋ฉด์ Spring ApplicationContext์ ํํฐ ๋น์ ํธ์ถํ๋ ๋ฐฉ์์ผ๋ก ์๋ํฉ๋๋ค. ์ด ๋ฉ์ปค๋์ฆ์ ํต์ฌ์ ์ง์ฐ ์ด๊ธฐํ(lazy initialization) ๋ฅผ ํตํด ์ฑ๋ฅ ์ต์ ํ์ ์ข ์์ฑ ๋ฌธ์ ํด๊ฒฐ์ ๋๋๋ค๋ ๊ฒ์ ๋๋ค.
1๏ธโฃ ํํฐ ํธ์ถ
doFilter ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ, Spring ํํฐ ๋น์ ์ฐพ๊ณ ์คํํ๋ ๊ณผ์ ์ ์์ํฉ๋๋ค.2๏ธโฃ ํํฐ ๋น ์ฐพ๊ธฐ (getFilterBean ํธ์ถ)
getFilterBean ๋ฉ์๋๋ ApplicationContext์์ ์ง์ ๋ ์ด๋ฆ(someBeanName)์ ํํฐ Bean์ ์ฐพ์ delegate์ ์ ์ฅํฉ๋๋ค.3๏ธโฃ ์ค์ ํํฐ๋ก ์์ฒญ ์ ๋ฌ
delegate.doFilter(request, response)๋ฅผ ํธ์ถํด ์ค์ ์์ฒญ ์ฒ๋ฆฌ๋ delegate ํํฐ ๋น์ด ์ํํ๊ฒ ๋ฉ๋๋ค. DelegatingFilterProxy๋ ์ค๊ณ ์ญํ ๋ง ํ๋ฉฐ, ์ค์ ์์
์ Spring ํํฐ Bean์ด ๋ด๋นํฉ๋๋ค.์ง์ฐ ์ด๊ธฐํ๋ ๊ฐ์ฒด๋ ๋ฆฌ์์ค๋ฅผ ์ฒ์ ์ฌ์ฉํ ๋์์ผ ์์ฑํ๋ ๋ฐฉ์์ ๋๋ค. ์ฆ, ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ ๋ชจ๋ ๊ฐ์ฒด๋ฅผ ์ด๊ธฐํํ์ง ์๊ณ , ํ์ํ ๋ ์์ฑํ๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค.
doFilter ๋ฉ์๋๊ฐ ์ฒ์ ํธ์ถ๋ ๋ getFilterBean์ ํตํด ApplicationContext์์ ํํฐ Bean์ ์ฐพ์ delegate์ ์ ์ฅํฉ๋๋ค. ์ดํ๋ก๋ delegate์ ์ ์ฅ๋ ํํฐ Bean์ ์ฌ์ฉํ๋ฏ๋ก, ๋ฐ๋ณต ๊ฒ์ ์์ด ํจ์จ์ ์ผ๋ก ํํฐ๋ฅผ ํธ์ถํฉ๋๋ค.์ฑ๋ฅ ์ต์ ํ
์ข ์์ฑ ๋ฌธ์ ํด๊ฒฐ
DelegatingFilterProxy๋ ํ์ํ ๋ ํํฐ ๋น์ ๋ถ๋ฌ์ค๋ ์ง์ฐ ์ด๊ธฐํ ๋ฐฉ์์ ์ฌ์ฉํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๊ณ ๋ถํ์ํ ์์ ๋ญ๋น๋ฅผ ์ค์ด๋ฉฐ, ์ข ์์ฑ ๋ฌธ์ ๋ฅผ ์๋ฐฉํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด Spring์ ํํฐ ๋น์ด ์์ฒญ ์ฒ๋ฆฌ ์ค์๋ง ํ์ฑํ๋์ด ํจ์จ์ ์ด๊ณ ์ ์ฐํ ๋ณด์ ์ค์ ์ด ๊ฐ๋ฅํด์ง๋๋ค! ๐ช๐
DelegatingFilterProxy๊ฐ ์ง์ฐ ์ด๊ธฐํ๋ฅผ ํตํด Spring ApplicationContext์ ๋ฏธ๋ฆฌ ๋ฑ๋ก๋ ํํฐ Bean์ ์ค์ ์์ฒญ ์์ ์์๋ง ๊ฐ์ ธ์ ์ฌ์ฉํ๋ ๋ฐฉ์์ด ํต์ฌ์ ๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ด ์ฑ๋ฅ ์ต์ ํ์ ์ ์ฐ์ฑ์ ๋ชจ๋ ํ๋ณดํ ์ ์๊ฒ ํด์ค๋๋ค.
๋ฏธ๋ฆฌ ๋ฑ๋ก๋ Bean: Spring ApplicationContext๋ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ ์ ํํฐ Bean์ ๋ฏธ๋ฆฌ ์์ฑํ๊ณ ๋ฑ๋กํฉ๋๋ค. ๋ฐ๋ผ์ ํํฐ Bean ์์ฒด๋ ์ด๋ฏธ ApplicationContext์์ ๊ด๋ฆฌ๋๊ณ ์์ต๋๋ค.
์ง์ฐ ์ด๊ธฐํ์ ์๋ฏธ:
doFilter ๋ฉ์๋๊ฐ ํธ์ถ๋๊ธฐ ์ ๊น์ง๋ ํด๋น ํํฐ Bean์ ๊ฐ์ ธ์ค์ง ์์ต๋๋ค.doFilter๊ฐ ํธ์ถ๋๋ ์์ ์์์ผ ApplicationContext๋ก๋ถํฐ ํํฐ Bean์ ๊ฐ์ ธ์ ํํฐ ์์
์ ์คํํ๊ฒ ๋ฉ๋๋ค.์ด ๊ณผ์ ์ ํตํด ํํฐ ์ธ์คํด์ค์ ํธ์ถ ์์ ์ ๋ฆ์ถค์ผ๋ก์จ ๋ถํ์ํ ์ด๊ธฐํ๋ฅผ ๋ฐฉ์งํ๊ณ , ์ค์ ์์ฒญ์ด ๋ค์ด์ฌ ๋์๋ง ํํฐ๊ฐ ์๋ํ๋๋ก ํฉ๋๋ค.
doFilter๊ฐ ํธ์ถ๋ ๋๊น์ง ํด๋น Bean์ ํธ์ถํ์ง ์์์ผ๋ก์จ, ํ์ํ ์์ ๊น์ง ์ด๊ธฐํ๋ฅผ ๋ฏธ๋ฃน๋๋ค.์ด์ ๊ฐ์ ์ค๊ณ ๋๋ถ์ DelegatingFilterProxy๋ ์ฑ๋ฅ๊ณผ ์์ ํจ์จ์ฑ์ ๋์ด๋ ๋์์ Spring์ ์ ์ฐํ ์์กด์ฑ ์ฃผ์ ๋ฐ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์๊ฒ ํฉ๋๋ค! ๐ ๏ธ๐ก
DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring ApplicationContext์ ์ด๊ธฐํ ์์ ์ฐจ์ด๋ฅผ ๊ทน๋ณตํ๊ธฐ ์ํ ์ฅ์น๋ก, ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ง์ฐ ์กฐํ(lazy lookup) ๋ฐฉ์์ผ๋ก ํํฐ Bean์ ๋ค๋ฃน๋๋ค. ๋จ๊ณ๋ณ๋ก ๊ทธ ๋์ ์๋ฆฌ์ ์ ์ฉ์ฑ์ ๋ํด ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
1๏ธโฃ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ํํฐ ๋ฑ๋ก ๊ณผ์
2๏ธโฃ Spring์ Bean ์ด๊ธฐํ
ContextLoaderListener์ ๊ฐ์ ๋ฆฌ์ค๋๋ฅผ ํตํด ์ ํ๋ฆฌ์ผ์ด์
์ปจํ
์คํธ(ApplicationContext)๋ฅผ ๋์ค์ ๋ก๋ํ์ฌ Bean๋ค์ ์์ฑํฉ๋๋ค.3๏ธโฃ ํ์ด๋ฐ ๋ฌธ์
DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring ์ฌ์ด์ ํ์ด๋ฐ ์ฐจ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ํํฐ Bean์ ์ง์ฐ ์กฐํํ๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค. ์๋์ ๊ฐ์ด ๊ฐ ๋จ๊ณ๋ณ๋ก ์งํ๋ฉ๋๋ค:
1๏ธโฃ DelegatingFilterProxy ๋ฑ๋ก
2๏ธโฃ ์์ฒญ ์์ ์์ ํํฐ Bean ์กฐํ
doFilter ๋ฉ์๋๊ฐ ํธ์ถ๋ ๋, ApplicationContext์์ ํํฐ ์ญํ ์ ํ๋ Spring Bean์ ์ฐพ์ ๊ฐ์ ธ์ต๋๋ค. doFilter ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ, ์ค์ ํํฐ ๋ก์ง์ ์ํํ๋๋ก ํฉ๋๋ค.์ด๋ฌํ ์ง์ฐ ์กฐํ ๋ฐฉ์ ๋๋ถ์, Spring Bean ์ด๊ธฐํ๊ฐ ๋ฆ์ด์ ธ๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ผ๋ฉฐ, ํ์ํ ์์ ์๋ง ํํฐ Bean์ ํธ์ถํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ ์ ์์ต๋๋ค. ๐๐ก
DelegatingFilterProxy์ ๋์์ ์ดํดํ๊ธฐ ์ํด์๋ ์๋ธ๋ฆฟ ์ปจํ
์ด๋์ ContextLoaderListener์ ์ด๊ธฐํ ๊ณผ์ ์ ํ์
ํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค. ๊ฐ๊ฐ์ ์ญํ ๊ณผ ์ด๊ธฐํ ์์๋ฅผ ํตํด Spring์ด ๊ด๋ฆฌํ๋ ํํฐ๊ฐ ์ด๋ป๊ฒ ์ปจํ
์ด๋์์ ์๋ํ ์ ์๋์ง ๋จ๊ณ๋ณ๋ก ์ ๋ฆฌํด๋ณด๊ฒ ์ต๋๋ค.
์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ Tomcat ๊ฐ์ ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ๊ฐ ์๋ธ๋ฆฟ์ ์คํํ๊ณ ๊ด๋ฆฌํ๋ ํ๊ฒฝ์ ๋๋ค. ์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋ ๋ ํํฐ์ ์๋ธ๋ฆฟ์ ๋ฏธ๋ฆฌ ๋ฑ๋ก ๋ฐ ์ด๊ธฐํํ์ฌ, ์ดํ ๋ค์ด์ค๋ ์์ฒญ์ ์ฒ๋ฆฌํ ์ค๋น๋ฅผ ๊ฐ์ถฅ๋๋ค.
web.xml ๋๋ Spring Boot ์ค์ ์ ํตํด ํํฐ๋ฅผ ๋ฏธ๋ฆฌ ๋ฑ๋กํ๊ณ ์ค๋นํฉ๋๋ค.์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ํํฐ์ ์๋ธ๋ฆฟ์ ๋ชจ๋ ์ค๋นํ๊ณ ๋์์ผ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์๋๋ฉฐ, ์ดํ ๋ค์ด์ค๋ HTTP ์์ฒญ์ ํํฐ์ ์๋ธ๋ฆฟ์ ํตํด ์ฒ๋ฆฌํฉ๋๋ค.
ContextLoaderListener๋ Spring ApplicationContext๋ฅผ ์ด๊ธฐํํ๊ณ ๊ด๋ฆฌํ๋ ๋ฆฌ์ค๋์
๋๋ค. ApplicationContext๋ Spring์ ๋ชจ๋ ์ฃผ์ Bean์ ๊ด๋ฆฌํ๋ฉฐ, DelegatingFilterProxy๊ฐ ์ฌ์ฉํ ํํฐ Bean๋ ์ฌ๊ธฐ ๋ฑ๋ก๋ฉ๋๋ค.
ContextLoaderListener๋ฅผ ํธ์ถํ๋ฉด, Spring์ ApplicationContext๋ฅผ ๋ก๋ํ์ฌ ํ์ํ Bean์ ์์ฑํฉ๋๋ค.์๋ธ๋ฆฟ ์ปจํ ์ด๋ ์ด๊ธฐํ๊ฐ ๋๋ ๋ค์ ContextLoaderListener๊ฐ ํธ์ถ๋๋ฏ๋ก, ์ปจํ ์ด๋๊ฐ ์ง์ Spring ํํฐ Bean์ ํํฐ๋ก ์ธ์ํ๊ณ ๋ฑ๋กํ ์ ์์ต๋๋ค.
์๋ธ๋ฆฟ ์ปจํ ์ด๋ ์ด๊ธฐํ
ContextLoaderListener ์ด๊ธฐํ
DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring ApplicationContext ๊ฐ ์ด๊ธฐํ ์์ ์ฐจ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋ง๋ค์ด์ก์ต๋๋ค. DelegatingFilterProxy ๋๋ถ์, Spring ApplicationContext์ ์๋ ํํฐ Bean์ด ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ํํฐ๋ก ๋ฑ๋ก๋ ๊ฒ์ฒ๋ผ ์๋ํ ์ ์์ต๋๋ค.
ContextLoaderListener์ ์ด๊ธฐํ ์์ ์ฐจ์ด๋ก ์ธํด Spring ํํฐ Bean์ ์ง์ ์ปจํ
์ด๋์ ํํฐ๋ก ๋ฑ๋กํ ์ ์์ต๋๋ค.DelegatingFilterProxy๋ ์ด์ฒ๋ผ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring ApplicationContext ๊ฐ์ ์ฐ๊ฒฐ์ ๋ด๋นํ์ฌ Spring์ ๋ณด์, ํธ๋์ญ์ ๊ด๋ฆฌ ๋ฑ ํํฐ ๊ธฐ๋ฅ์ ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก ์ง์ํ๋ ์ค์ํ ๊ตฌ์ฑ ์์์ ๋๋ค. ๐ก๏ธโจ
์๋ธ๋ฆฟ ์ปจํ ์ด๋๊ฐ ๊ด๋ฆฌํ๋ ํํฐ์ ์๋ธ๋ฆฟ์ ๋ฑ๋ก๊ณผ Spring์ด ๊ด๋ฆฌํ๋ Bean ๋ฑ๋ก์ ์๋ก ๋ค๋ฅธ ์์คํ ์์ ์ด๋ฃจ์ด์ง๋ ๊ฐ๋ ์ ๋๋ค. ๊ฐ๊ฐ์ ์ด๊ธฐํ์ ๋ฑ๋ก ๊ณผ์ ์ ์ฐจ์ด๋ฅผ ์ดํดํ๋ฉด, DelegatingFilterProxy๊ฐ ํ์ํ ์ด์ ๊ฐ ๋ช ํํด์ง๋๋ค.
web.xml ํ์ผ ๋๋ Spring Boot์ ์๋ ์ค์ ์ ํตํด ํํฐ์ ์๋ธ๋ฆฟ์ ๋ฏธ๋ฆฌ ๋ฑ๋กํฉ๋๋ค.ContextLoaderListener๊ฐ ์ด๊ธฐํ๋ ์ดํ, Spring ApplicationContext๊ฐ ๊ด๋ฆฌํ๋ Bean๋ค์ด ๋ฑ๋ก๋ฉ๋๋ค. 1๏ธโฃ ์๋ธ๋ฆฟ ์ปจํ ์ด๋ ์ด๊ธฐํ
2๏ธโฃ Spring ApplicationContext ์ด๊ธฐํ
ContextLoaderListener๊ฐ ํธ์ถ๋๋ฉด Spring์ ApplicationContext๋ฅผ ๋ก๋ํ๊ณ Bean์ ์ด๊ธฐํํฉ๋๋ค. ์ด ์์ ์์ DelegatingFilterProxy์ ๊ฐ์ ํํฐ Bean๋ ์์ฑ๋ฉ๋๋ค.์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring ApplicationContext์ ์ด๊ธฐํ ์์ ์ฐจ์ด๋ก ์ธํด, Spring ํํฐ Bean์ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์์ ์ง์ ์ธ์ํ ์ ์๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํฉ๋๋ค. DelegatingFilterProxy๋ Spring ํํฐ Bean์ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์์ ์ฌ์ฉ๋ ์ ์๋๋ก ์ฐ๊ฒฐํด์ฃผ๋ ๋ค๋ฆฌ ์ญํ ์ ํฉ๋๋ค.
ContextLoaderListener๋ฅผ ํตํด Bean๋ค์ด ๋ฑ๋ก๋ฉ๋๋ค.์ด๋ก์จ DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring์ ์ฐ๊ฒฐ์ ํตํด Spring Bean ํํฐ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์๋๋ก ํด์ฃผ๋ ์ค์ํ ์ญํ ์ ์ํํฉ๋๋ค! ๐๐ก
DelegatingFilterProxy๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ Spring ApplicationContext ๊ฐ์ ์ด๊ธฐํ ์์ ์ฐจ์ด๋ฅผ ๋งค๋๋ฝ๊ฒ ํด๊ฒฐํ์ฌ, Spring์์ ๊ด๋ฆฌํ๋ ํํฐ Bean์ด ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ํํฐ์ฒ๋ผ ์๋ํ ์ ์๋๋ก ์ง์ํ๋ ํต์ฌ์ ์ธ ์ฐ๊ฒฐ ๊ณ ๋ฆฌ์ ๋๋ค.
์ด๋ฌํ ๋ฐฉ์ ๋๋ถ์, Spring ์ ํ๋ฆฌ์ผ์ด์ ์ ์ปจํ ์ด๋์ ํํฐ ๋ฑ๋ก ์์ ๊ณผ๋ ๋ ๋ฆฝ์ ์ผ๋ก ์ด๊ธฐํ๋๋ฉด์๋, Spring์ ์์กด์ฑ ์ฃผ์ ๊ณผ ๊ด๋ฆฌ ๊ธฐ๋ฅ์ ๊ทธ๋๋ก ํ์ฉํ ์ ์๊ฒ ๋ฉ๋๋ค. DelegatingFilterProxy๋ฅผ ํตํด ์์ฒญ ์์ ์์๋ง ํํฐ Bean์ ์ง์ฐ ์ฐ๊ฒฐํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๊ณ , ์ปจํ ์ด๋์ Spring ํ๊ฒฝ์ ํตํฉ์ ์ํํ ์ ์งํ ์ ์์ต๋๋ค.
ํต์ฌ ์์ ์ด ์ ์ ๋ฆฌ๋ ๋งํผ, Spring Security์ ๊ฐ์ด ํํฐ ๊ธฐ๋ฐ ๋ณด์ ์ค์ ์ด ํ์ํ ๊ฒฝ์ฐ DelegatingFilterProxy์ ์ญํ ์ ์ ์ดํดํ๊ณ ํ์ฉํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค! ๐ช๐
Spring Security์์ FilterChainProxy๋ ์์ฒญ์ด ๋ค์ด์์ ๋ ๋ณด์ ํํฐ๋ค์ ๊ด๋ฆฌํ๊ณ ์คํํ๋ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ๋ํ DelegatingFilterProxy๋ FilterChainProxy๊ฐ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์์ ํธ์ถ๋ ์ ์๋๋ก ์ฐ๊ฒฐํด ์ฃผ๋ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ์๋์์ ๋ ๊ตฌ์ฑ ์์์ ์ญํ ๊ณผ ๊ด๊ณ๋ฅผ ๋จ๊ณ๋ณ๋ก ์์ธํ ์ค๋ช ํ๊ฒ ์ต๋๋ค.
1๏ธโฃ ๋ค์ค ํํฐ ์์
2๏ธโฃ SecurityFilterChain ํ์ฉ
3๏ธโฃ ๋ณด์ ๋ก์ง์ ์บก์ํ
FilterChainProxy๋ Spring์ด ๊ด๋ฆฌํ๋ Bean์ด์ง๋ง, ์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ์ง์ Spring Bean์ ํธ์ถํ ์ ์์ต๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด DelegatingFilterProxy๊ฐ ํ์ํฉ๋๋ค.
DelegatingFilterProxy์ ์ญํ
๋์ ํ๋ฆ
1๏ธโฃ ํด๋ผ์ด์ธํธ๊ฐ ๋ก๊ทธ์ธ ํ์ด์ง์ ์ ๊ทผํ๋ ค๊ณ ์์ฒญ์ ๋ณด๋
2๏ธโฃ ์๋ธ๋ฆฟ ์ปจํ
์ด๋๋ DelegatingFilterProxy๋ฅผ ํธ์ถ
3๏ธโฃ DelegatingFilterProxy๋ FilterChainProxy๋ฅผ Spring ApplicationContext์์ ์ฐพ์ ํธ์ถ
4๏ธโฃ FilterChainProxy๋ SecurityFilterChain์ ์ค์ ๋ ํํฐ๋ค์ ์์๋๋ก ์คํ:
5๏ธโฃ ๋ชจ๋ ํํฐ๋ฅผ ํต๊ณผํ๋ฉด ์์ฒญ์ด ์ฒ๋ฆฌ๋๋ฉฐ, ํ๋๋ผ๋ ์คํจํ๋ฉด ์์ฒญ์ ์ฐจ๋จ

์ด ๋ ๊ตฌ์ฑ ์์๊ฐ ํจ๊ป ์๋ํจ์ผ๋ก์จ Spring Security๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์์ ๋ค์ํ ๋ณด์ ํํฐ๋ฅผ ์์ ์ ์ด๊ณ ์ผ๊ด์ฑ ์๊ฒ ์คํํ ์ ์์ต๋๋ค! ๐ก๏ธ๐ฅ
SecurityFilterChain์ FilterChainProxy๊ฐ ํ์ฌ ์์ฒญ์ ๋ํด ํธ์ถํด์ผ ํ Spring Security ํํฐ ์ธ์คํด์ค๋ฅผ ๊ฒฐ์ ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค.

FilterChainProxy์ SecurityFilterChain์ Spring Security๊ฐ ์ ๊ณตํ๋ ๋ณด์ ํํฐ ์ฒด์ธ์ ์ ์ฐํ๊ฒ ๊ด๋ฆฌํ๊ณ , ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ์ ์ฝ์ ๋์ด ์ต์ ํ๋ ๋ณด์ ์ค์ ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ์ด ๋ ๊ตฌ์ฑ ์์์ ์ญํ ๊ณผ ์ํธ ๊ด๊ณ๋ฅผ ์์ธํ ์์๋ณด๊ฒ ์ต๋๋ค.
1๏ธโฃ ์ค์ ๊ด๋ฆฌ ํฌ์ธํธ ์ ๊ณต
2๏ธโฃ SecurityContext ๊ด๋ฆฌ
3๏ธโฃ HttpFirewall ์ ์ฉ
์๋ธ๋ฆฟ ์ปจํ ์ด๋๋ ๋ณดํต URL ํจํด์ ๋ฐ๋ผ ํํฐ๋ฅผ ํธ์ถํ์ง๋ง, FilterChainProxy๋ SecurityFilterChain์ ํตํด ์์ฒญ์ ์ธ๋ถ ์กฐ๊ฑด์ ๋ง์ถฐ ํํฐ ์ฒด์ธ์ ์ ์ฉํ ์ ์์ต๋๋ค. Spring Security์ RequestMatcher ์ธํฐํ์ด์ค๋ฅผ ํ์ฉํด, URL๋ฟ๋ง ์๋๋ผ ์์ฒญ ํค๋, ํ๋ผ๋ฏธํฐ, ๋ฉ์๋ ๋ฑ ๋ค์ํ ์กฐ๊ฑด์ ๊ธฐ๋ฐ์ผ๋ก ํํฐ๋ฅผ ์คํํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด:
์ด๋ฌํ ๊ธฐ๋ฅ์ ํตํด FilterChainProxy๋ ๋จ์ URL ํจํด์ ์์กดํ์ง ์๊ณ , HttpServletRequest์ ๋ค์ํ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ณด์ ํํฐ๋ฅผ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก, FilterChainProxy๋ Spring Security์ ๋ชจ๋ ๋ณด์ ํํฐ๋ฅผ ํตํฉ ๊ด๋ฆฌํ๊ณ , ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ๊ธฐ๋ณธ ํํฐ ๊ด๋ฆฌ๋ณด๋ค ํจ์ฌ ์ ์ฐํ๊ณ ํ์ฅ ๊ฐ๋ฅํ ๋ณด์ ์ค์ ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค. ์ด ๊ตฌ์กฐ๋ Spring Security์ ๋ณด์ ๊ธฐ๋ฅ์ ํจ์จ์ ์ผ๋ก ๊ด๋ฆฌํ๊ณ ํ์ฅํ ์ ์๋ ์ค์ํ ๊ธฐ๋ฐ์ ์ ๊ณตํฉ๋๋ค. ๐ก๏ธโจ
SecurityContext๋ Spring Security๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์ ์ธ์ฆ ๋ฐ ๊ถํ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ๋ ์ค์ ์ ์ฅ์๋ก, ํ์ฌ ์ฌ์ฉ์์ ์ธ์ฆ ์ํ์ ๊ถํ์ ์ฝ๊ฒ ์ฐธ์กฐํ ์ ์๋๋ก ๋์ต๋๋ค.
1๏ธโฃ ์ธ์ฆ ์ ๋ณด ์ ์ฅ
Authentication ๊ฐ์ฒด์ ์ฌ์ฉ์์ ID, ์ธ์ฆ ๋ฐฉ์, ๊ถํ ์ ๋ณด ๋ฑ์ด ์ ์ฅ๋ฉ๋๋ค.2๏ธโฃ ๊ถํ ๊ด๋ฆฌ
3๏ธโฃ ์ ์ญ ์ ๊ทผ ๊ฐ๋ฅ
SecurityContextHolder๋ฅผ ์ฌ์ฉํ์ฌ ํ์ฌ ์ฌ์ฉ์์ SecurityContext์ ์ ๊ทผํ ์ ์์ต๋๋ค.๋ค์ ์ฝ๋๋ฅผ ํตํด SecurityContext์ ์ ์ฅ๋ ํ์ฌ ์ฌ์ฉ์ ์ด๋ฆ์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String username = authentication.getName(); // ํ์ฌ ์ฌ์ฉ์ ์ด๋ฆ ๊ฐ์ ธ์ค๊ธฐ
์ด ์ฝ๋๋ SecurityContextHolder๋ฅผ ํตํด SecurityContext์์ ํ์ฌ ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์กฐํํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค.
SecurityContext๋ Spring Security์์ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด์ ๊ถํ์ ์ค์์์ ๊ด๋ฆฌํ๋ฉฐ, ์์ฒญ๋ง๋ค ์ธ์ฆ๊ณผ ๊ถํ ๊ฒ์ฌ๋ฅผ ์ฝ๊ฒ ์ํํ ์ ์๊ฒ ํด์ค๋๋ค. ์์ฒญ์ด ๋๋ ํ์๋ SecurityContext๊ฐ ์ด๊ธฐํ๋์ด, ๋ฉ๋ชจ๋ฆฌ ๋์ ์์ด ์์ ํ๊ฒ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ๐ก๏ธ๐ก
ThreadLocal์ ๊ฐ ์ค๋ ๋๋ง๋ค ๋ ๋ฆฝ์ ์ผ๋ก ๊ฐ์ ์ ์ฅํ ์ ์๋ ์ ์ฅ์๋ฅผ ์ ๊ณตํ์ฌ, ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ณ์๋ฅผ ์ฌ์ฉํ๋๋ผ๋ ๊ฐ ์ค๋ ๋๊ฐ ์๋ก ๋ค๋ฅธ ๊ฐ์ ๊ฐ๋๋ก ๋ณด์ฅํฉ๋๋ค. ์ด๋ฅผ ํตํด ๋์์ฑ ๋ฌธ์ ์์ด ์ค๋ ๋๋ณ ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
1๏ธโฃ ์ค๋ ๋๋ณ ๋ ๋ฆฝ์ฑ
2๏ธโฃ ๋์์ฑ ๋ฌธ์ ํด๊ฒฐ
์ค๋ ๋๋ง๋ค ํน์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํ๊ณ ์ถ์ ๋, ThreadLocal์ ์ด์ฉํ๋ฉด ๊ฐ ์ค๋ ๋๊ฐ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ์ ์์ต๋๋ค.
public class UserContext {
private static ThreadLocal<String> userThreadLocal = new ThreadLocal<>();
public static void setUser(String user) {
userThreadLocal.set(user); // ํ์ฌ ์ค๋ ๋์ ์ฌ์ฉ์ ์ ๋ณด ์ ์ฅ
}
public static String getUser() {
return userThreadLocal.get(); // ํ์ฌ ์ค๋ ๋์ ์ฌ์ฉ์ ์ ๋ณด ๊ฐ์ ธ์ค๊ธฐ
}
public static void clear() {
userThreadLocal.remove(); // ํ์ฌ ์ค๋ ๋์ ์ฌ์ฉ์ ์ ๋ณด ์ญ์
}
}
์ ์ฝ๋์์ UserContext ํด๋์ค์ userThreadLocal ๋ณ์๋ ๊ฐ ์ค๋ ๋๊ฐ ๋
๋ฆฝ์ ์ธ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํ ์ ์๊ฒ ํด์ค๋๋ค.
Spring Security์์๋ SecurityContext๋ฅผ ThreadLocal์ ํตํด ๊ด๋ฆฌํ์ฌ, ๊ฐ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ์ค๋ ๋์ ์ธ์ฆ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ์ ์ฅํฉ๋๋ค. ์ด ๋ฐฉ์์ผ๋ก ์์ฒญ ์ค ์ธ์ ๋ ํ์ฌ ์ค๋ ๋์ ์ฌ์ฉ์ ์ ๋ณด์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์์ฒญ์ด ๋๋๋ฉด ์ด ์ ๋ณด๋ฅผ ์ด๊ธฐํํ์ฌ ๋ค๋ฅธ ์์ฒญ์ ์ํฅ์ ์ฃผ์ง ์๋๋ก ํฉ๋๋ค.
remove() ๋ฉ์๋๋ฅผ ํธ์ถํด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฆฌํ๋ ๊ฒ์ด ์ค์ํฉ๋๋ค.ThreadLocal์ ์ค๋ ๋๋ณ๋ก ๋ ๋ฆฝ์ ์ธ ๊ฐ์ ์ ๊ณตํ์ฌ ๋์์ฑ ๋ฌธ์ ์์ด ๋ฐ์ดํฐ๋ฅผ ๊ด๋ฆฌํ ์ ์๋ ์ ์ฉํ ์ ์ฅ์์ ๋๋ค. Spring Security์์๋ ์ด ๊ธฐ๋ฅ์ ํ์ฉํ์ฌ ๊ฐ ์์ฒญ์ ๋ํ ์ธ์ฆ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ๊ด๋ฆฌํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ธ์ฆ ์ ๋ณด๊ฐ ์ค๋ ๋ ๊ฐ์ ์์ด๋ ๋ฌธ์ ๋ฅผ ๋ฐฉ์งํ๊ณ , ์์ฒญ์ด ๋๋ ๋๋ง๋ค ๋ฐ์ดํฐ๋ฅผ ์ด๊ธฐํํ์ฌ ๋ฉ๋ชจ๋ฆฌ ๋์๋ฅผ ์๋ฐฉํ ์ ์์ต๋๋ค. ๐ก๏ธ๐ก
HttpFirewall์ Spring Security๊ฐ ์์ ํ HTTP ์์ฒญ์ ๊ฒ์ฌํ๊ณ , ์์ฌ์ค๋ฌ์ด ์์ฒญ์ ํํฐ๋ง ๋ฐ ์ฐจ๋จํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ด ์์ ํ๊ฒ ๋์ํ ์ ์๋๋ก ๋๋ ์ค์ํ ๋ณด์ ๊ธฐ๋ฅ์ ๋๋ค. ์ฃผ๋ก XSS, ๊ฒฝ๋ก ํ์, HTTP ํค๋ ์กฐ์๊ณผ ๊ฐ์ ๋ณด์ ์ํ์ ๋ฐฉ์งํฉ๋๋ค.
1๏ธโฃ URL ๋ฐ ๊ฒฝ๋ก ๊ฒ์ฌ
../์ ๊ฐ์ ์์ ๋๋ ํฐ๋ฆฌ ์ด๋ ์๋๋ฅผ ์ฐจ๋จํฉ๋๋ค. 2๏ธโฃ HTTP ํค๋ ๋ฐ ๋ฉ์๋ ๊ฒ์ฌ
3๏ธโฃ ํ์ฉ๋์ง ์์ ๋ฌธ์ ์ฐจ๋จ
Spring Security์์๋ ๊ธฐ๋ณธ์ ์ผ๋ก StrictHttpFirewall์ ํตํด ์์ฒญ ๊ฒ์ฌ๋ฅผ ์ค์ ํ ์ ์์ผ๋ฉฐ, ์ค์ ์ ํตํด ํน์ ์์ฒญ ํจํด์ ํ์ฉํ๊ฑฐ๋ ์ฐจ๋จํ ์ ์์ต๋๋ค.
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class SecurityConfig {
@Bean
public HttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowUrlEncodedSlash(true); // ํน์ ๋ฌธ์ ํ์ฉ ์ค์
return firewall;
}
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpFirewall(httpFirewall()); // ์ปค์คํ
HttpFirewall ์ค์ ์ ์ฉ
}
}
์ ์์์์ StrictHttpFirewall์ ์ฌ์ฉํด URL ์ธ์ฝ๋ฉ๋ ์ฌ๋์๋ฅผ ํ์ฉํ๋๋ก ์ค์ ํ์ต๋๋ค. ์ด์ฒ๋ผ HttpFirewall ์ค์ ์ ํตํด ํน์ ๋ฌธ์๋ ์์ฒญ ์กฐ๊ฑด์ ์ปค์คํฐ๋ง์ด์งํ ์ ์์ต๋๋ค.
RequestRejectedException์ ๋ฐ์์์ผ ํด๋น ์์ฒญ์ ๊ฑฐ๋ถํฉ๋๋ค.๊ฒฝ๋ก ํ์(Path Traversal)
../์ ๊ฐ์ ๊ฒฝ๋ก ์ด๋์ ํตํด ์์คํ
๋ฏผ๊ฐ ํ์ผ์ ์ ๊ทผํ๋ ค๋ ์๋๋ฅผ ๋ฐฉ์งํฉ๋๋ค.XSS(Cross-Site Scripting)
HTTP ํค๋ ์กฐ์
SQL Injection ๋ฐ ๊ธฐํ ์ธ์ฝ๋ฉ ๊ณต๊ฒฉ
HttpFirewall์ Spring Security์์ ์ ๊ณตํ๋ ํ์ ๋ณด์ ๊ธฐ๋ฅ์ผ๋ก, ์์ฌ์ค๋ฌ์ด HTTP ์์ฒญ์ ๊ฐ์งํ๊ณ ์ฐจ๋จํ์ฌ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ ์ฌ์ ๋ณด์ ์ํ์ผ๋ก๋ถํฐ ๋ณดํธํฉ๋๋ค. ์ฃผ๋ก ๊ฒฝ๋ก ํ์, XSS, HTTP ํค๋ ์กฐ์ ๋ฑ์ ๊ณต๊ฒฉ์ ๋ฐฉ์ดํ๋ฉฐ, ๊ธฐ๋ณธ์ ์ผ๋ก ์๊ฒฉํ ์์ฒญ ํํฐ๋ง์ ์ํํ๋ StrictHttpFirewall์ ์ฌ์ฉํฉ๋๋ค. ์ค์ ์ ํตํด ํน์ ํ์ฉ ์กฐ๊ฑด์ ์ถ๊ฐํ ์ ์์ผ๋ฉฐ, Spring Security์ ์ค์ํ ๋ฐฉ์ด์ ์ญํ ์ ์ํํฉ๋๋ค. ๐ก๏ธโจ
FilterChainProxy๋ Spring Security์์ RequestMatcher ์ธํฐํ์ด์ค๋ฅผ ํตํด URL ํจํด์ ๋์ด ์์ฒญ์ ๋ค์ํ ์กฐ๊ฑด์ ๋ฐํ์ผ๋ก ๋ณด์ ํํฐ๋ฅผ ์ ํ์ ์ผ๋ก ์ ์ฉํ ์ ์๊ฒ ํด์ค๋๋ค. ์ด๋ ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ URL ํจํด ๊ธฐ๋ฐ ํธ์ถ ๋ฐฉ์๊ณผ ์ฐจ๋ณํ๋ ์ ์ฐํ ๋ณด์ ์ค์ ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
์๋ธ๋ฆฟ ์ปจํ
์ด๋๋ ์ฃผ๋ก URL ํจํด์ ๋ฐ๋ผ ํํฐ๋ฅผ ํธ์ถํฉ๋๋ค. ์๋ฅผ ๋ค์ด, /admin/*์ ๊ฐ์ URL ํจํด์ ๋ฐ๋ผ ๋ชจ๋ ์์ฒญ์ ๋ํด ํํฐ๋ฅผ ์ ์ฉํ๋ ๋ฐฉ์์
๋๋ค. ํ์ง๋ง ์ด ๋ฐฉ์์ URL ์ธ์ ์กฐ๊ฑด์ ์ค์ ํ ์ ์์ด ๋ณด์ ์ ์ฉ ๋ฐฉ์์ด ์ ํ์ ์
๋๋ค.
FilterChainProxy๋ Spring Security์ ๋ค์ํ ๋ณด์ ํํฐ๋ฅผ ๊ด๋ฆฌํ๊ณ , RequestMatcher ์ธํฐํ์ด์ค๋ฅผ ํตํด ์์ฒญ์ ์กฐ๊ฑด์ ๋ฐ๋ผ ํํฐ๋ฅผ ์ ์ฐํ๊ฒ ์ ์ฉํ ์ ์์ต๋๋ค. RequestMatcher๋ฅผ ์ฌ์ฉํ๋ฉด, URL ์ธ์๋ ํค๋, HTTP ๋ฉ์๋, ํ๋ผ๋ฏธํฐ ๋ฑ์ ์กฐ๊ฑด์ ๋ฐํ์ผ๋ก ๋ณด์ ํํฐ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ๋ณด์ ํค๋๊ฐ ํฌํจ๋ ์์ฒญ๋ง ํํฐ๋ฅผ ์ ์ฉํ๊ณ ์ถ๋ค๋ฉด RequestMatcher๋ก ํน์ ํค๋ ์กฐ๊ฑด์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ์๋๋ X-Security-Header ํค๋๊ฐ ์๋ ์์ฒญ๋ง ํํฐ๋ฅผ ์ ์ฉํ๋ ์์์
๋๋ค:
RequestMatcher headerMatcher = request ->
"true".equals(request.getHeader("X-Security-Header"));
http
.securityMatcher(headerMatcher)
.authorizeRequests()
.anyRequest().authenticated();
์ด ์ค์ ์ผ๋ก X-Security-Header ํค๋๊ฐ ํฌํจ๋ ์์ฒญ๋ง ํํฐ ์ฒด์ธ์ ๊ฑฐ์น๋ฉฐ, ํค๋๊ฐ ์๋ ์์ฒญ์ ์ ์ฉ๋์ง ์์ต๋๋ค.
RequestMatcher๋ HTTP ๋ฉ์๋์ ๋ฐ๋ฅธ ํํฐ ์ ์ฉ๋ ์ง์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, GET ์์ฒญ์ ์ธ์ฆ์ ์๊ตฌํ์ง ์๊ณ POST ์์ฒญ์๋ ์ธ์ฆ์ ์๊ตฌํ๋ ๊ฒฝ์ฐ:
RequestMatcher postMatcher = request ->
"POST".equals(request.getMethod());
http
.securityMatcher(postMatcher)
.authorizeRequests()
.anyRequest().authenticated();
์ด ์ค์ ์ผ๋ก POST ์์ฒญ๋ง ์ธ์ฆ์ด ํ์ํ ํํฐ๋ฅผ ๊ฑฐ์น๊ณ , GET ์์ฒญ์ ์ ์ฉ๋์ง ์์ต๋๋ค.
RequestMatcher๋ ์์ฒญ ํ๋ผ๋ฏธํฐ๋ฅผ ์กฐ๊ฑด์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, secure=true ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋ ์์ฒญ์๋ง ํํฐ๋ฅผ ์ ์ฉํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ์ ์์ต๋๋ค:
RequestMatcher parameterMatcher = request ->
"true".equals(request.getParameter("secure"));
http
.securityMatcher(parameterMatcher)
.authorizeRequests()
.anyRequest().authenticated();
์ด ์ค์ ์ผ๋ก ?secure=true ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋ ์์ฒญ๋ง ํํฐ๋ฅผ ๊ฑฐ์น๊ฒ ๋ฉ๋๋ค.
์ด์ ๊ฐ์ ์กฐ๊ฑด ์ค์ ์ ํตํด FilterChainProxy๋ ๋จ์ํ URL ํจํด์ ๋์ด HttpServletRequest์ ๋ค์ํ ์กฐ๊ฑด์ ๋ฐ๋ฅธ ๋ณด์ ํํฐ๋ง์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.

๋ค์ค SecurityFilterChain ๊ทธ๋ฆผ์์, FilterChainProxy๋ ์ด๋ค SecurityFilterChain์ ์ฌ์ฉํ ์ง ๊ฒฐ์ ํฉ๋๋ค. ์ผ์นํ๋ ์ฒซ ๋ฒ์งธ SecurityFilterChain๋ง ํธ์ถ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, /api/messages/ URL ์์ฒญ์ด ๋ค์ด์ค๋ฉด, /api/** ํจํด๊ณผ ์ผ์นํ๋ SecurityFilterChain0์ด ๊ฐ์ฅ ๋จผ์ ๋งค์นญ๋๋ฏ๋ก SecurityFilterChain0๋ง ํธ์ถ๋๊ณ , SecurityFilterChainn๊ณผ๋ ์ผ์นํ๋๋ผ๋ ํธ์ถ๋์ง ์์ต๋๋ค.
๋ฐ๋ฉด, /messages/ URL ์์ฒญ์ด ๋ค์ด์ค๋ฉด SecurityFilterChain0์ /api/** ํจํด๊ณผ ์ผ์นํ์ง ์์ผ๋ฏ๋ก, FilterChainProxy๋ ๊ณ์ํด์ ๋ค๋ฅธ SecurityFilterChain์ ํ์ธํฉ๋๋ค. ๋ง์ฝ ๋ค๋ฅธ SecurityFilterChain์ด ์ผ์นํ์ง ์์ผ๋ฉด, ์ต์ข
์ ์ผ๋ก SecurityFilterChainn์ด ํธ์ถ๋ฉ๋๋ค.
SecurityFilterChain0์๋ ๋ณด์ ํํฐ ์ธ์คํด์ค๊ฐ 3๊ฐ๋ง ์ค์ ๋ ๋ฐ๋ฉด, SecurityFilterChainn์๋ 4๊ฐ์ ๋ณด์ ํํฐ ์ธ์คํด์ค๊ฐ ์ค์ ๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๊ฐ SecurityFilterChain์ ๊ณ ์ ํ ์ ์์ผ๋ฉฐ ๋ ๋ฆฝ์ ์ผ๋ก ๊ตฌ์ฑํ ์ ์๋ค๋ ์ ์ด ์ค์ํฉ๋๋ค. ๋ํ, ์ ํ๋ฆฌ์ผ์ด์ ์ด ํน์ ์์ฒญ์ Spring Security๊ฐ ๋ฌด์ํ๋๋ก ์ค์ ํ๋ ค๋ ๊ฒฝ์ฐ, SecurityFilterChain์ ๋ณด์ ํํฐ ์ธ์คํด์ค๊ฐ ์์ ์๋ ์์ต๋๋ค.
FilterChainProxy์ RequestMatcher๋ ์์ฒญ URL ์ธ์๋ ํค๋, HTTP ๋ฉ์๋, ํ๋ผ๋ฏธํฐ ๋ฑ์ ์กฐ๊ฑด์ ํ์ฉํ์ฌ Spring Security์ ๋ณด์ ํํฐ๋ฅผ ์ ์ฐํ๊ฒ ์ ์ฉํ ์ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์๋ธ๋ฆฟ ์ปจํ ์ด๋์ ๋จ์ URL ํจํด ๊ธฐ๋ฐ ํธ์ถ์ ๋์ด ๋์ฑ ์ธ๋ฐํ๊ณ ๋ง์ถคํ ๋ณด์ ์ค์ ์ ์ ๊ณตํฉ๋๋ค. ๐๐ช
Spring Security์์ ๋ณด์ ํํฐ๋ SecurityFilterChain API๋ฅผ ํตํด FilterChainProxy์ ์ฝ์ ๋์ด, ์ธ์ฆ, ๊ถํ ๋ถ์ฌ, ๊ณต๊ฒฉ ๋ฐฉ์ง ๋ฑ์ ๋ค์ํ ๋ชฉ์ ์ ์ํํฉ๋๋ค. ์ด๋ฌํ ํํฐ๋ ํน์ ์์์ ๋ฐ๋ผ ์คํ๋๋ฉฐ, ๊ฐ ํํฐ๊ฐ ์ฌ๋ฐ๋ฅธ ์์ ์ ํธ์ถ๋๋๋ก ๋ณด์ฅ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ธ์ฆ ํํฐ๋ ๊ถํ ๋ถ์ฌ ํํฐ๋ณด๋ค ๋จผ์ ํธ์ถ๋์ด์ผ ํฉ๋๋ค.
Spring Security์์ ์์ฃผ ์ฌ์ฉ๋๋ ๋ณด์ ํํฐ๋ค์ ๋ค์๊ณผ ๊ฐ์ ์์๋ก ๋ฐฐ์น๋์ด ํน์ ๋ชฉ์ ์ ์ํํฉ๋๋ค.
| ํํฐ | ์ถ๊ฐ๋ ์์น |
|---|---|
| CsrfFilter | HttpSecurity#csrf |
| UsernamePasswordAuthenticationFilter | HttpSecurity#formLogin |
| BasicAuthenticationFilter | HttpSecurity#httpBasic |
| AuthorizationFilter | HttpSecurity#authorizeHttpRequests |
1๏ธโฃ CsrfFilter
2๏ธโฃ ์ธ์ฆ ํํฐ (UsernamePasswordAuthenticationFilter ๋ฐ BasicAuthenticationFilter)
HttpSecurity#formLogin ์์น์์ ์ค์ ๋ฉ๋๋ค.HttpSecurity#httpBasic ์์น์์ ์ค์ ๋ฉ๋๋ค.3๏ธโฃ AuthorizationFilter
ํํฐ ์์ ํ์ธ
์ผ๋ฐ์ ์ผ๋ก Spring Security๋ ํํฐ ์์๋ฅผ ์๋์ผ๋ก ๊ด๋ฆฌํ๋ฏ๋ก ์์๋ฅผ ๋ช
ํํ ์ ํ์๋ ์์ต๋๋ค. ํ์ง๋ง ํ์ํ ๊ฒฝ์ฐ, FilterOrderRegistration ์ฝ๋๋ฅผ ํตํด ๊ฐ ํํฐ์ ์์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
์ถ๊ฐ์ ์ธ ํํฐ ํ์ธ
๋ณด์ ๊ตฌ์ฑ ์ธ์๋ ์ถ๊ฐ์ ์ผ๋ก ์ฌ์ฉ์ ์ ์ ํํฐ๋ฅผ ์ถ๊ฐํ ์ ์์ผ๋ฉฐ, ํน์ ์์ฒญ์ ๋ํด ํธ์ถ๋ ํํฐ ๋ชฉ๋ก์ ์ถ๋ ฅํ์ฌ ์ค์ ํํฐ ์คํ ์์๋ฅผ ํ์ธํ ์ ์์ต๋๋ค.
Spring Security์ ๋ณด์ ํํฐ ์ฒด์ธ์ ์ด์ฒ๋ผ ์์์ ๋ง์ถฐ ํํฐ๋ค์ด ์คํ๋๋ฉฐ, ๊ฐ๊ฐ์ ํํฐ๊ฐ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ณด์์ ๋ค๊ฐ๋๋ก ๊ฐํํ ์ ์๋๋ก ๋์ต๋๋ค. ๐ก๏ธโจ
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults());
return http.build();
}
}
ํน์ ์์ฒญ์ ๋ํด ์ด๋ค ๋ณด์ ํํฐ๊ฐ ํธ์ถ๋๋์ง ํ์ธํ๋ ๊ฒ์ ์ ์ฉํ ๋๊ฐ ๋ง์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ถ๊ฐํ ์ปค์คํ ํํฐ๊ฐ ํํฐ ์ฒด์ธ์ ํฌํจ๋์ด ์๋์ง ํ์ธํ๊ฑฐ๋, ๊ฐ ํํฐ์ ์คํ ์์๋ฅผ ๋๋ฒ๊น ํ๋ ๋ฐ ๋์์ ์ค๋๋ค.
INFO - Security filter chain:
- SecurityContextPersistenceFilter
- UsernamePasswordAuthenticationFilter
- BasicAuthenticationFilter
- CsrfFilter
- AuthorizationFilter
...
์ด ๋ชฉ๋ก์ ํตํด ์ ํ๋ฆฌ์ผ์ด์ ์ด ์ด๊ธฐํ๋ ๋ ์ด๋ค ํํฐ๋ค์ด ํ์ฑํ๋๋์ง ํ์ธํ ์ ์์ต๋๋ค.
Spring Boot์ application.properties ๋๋ application.yml ํ์ผ์์ Spring Security์ ๋ก๊น
๋ ๋ฒจ์ DEBUG๋ก ์ค์ ํ์ฌ, ๊ฐ ํํฐ ํธ์ถ ์ด๋ฒคํธ๋ฅผ ๊ธฐ๋กํ ์ ์์ต๋๋ค.
logging.level.org.springframework.security.web.FilterChainProxy=DEBUG
logging.level.org.springframework.security=DEBUG
์ด ์ค์ ์ ํตํด, ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค Spring Security์ ๊ฐ ํํฐ ํธ์ถ์ด ๋ก๊ทธ์ ๊ธฐ๋ก๋ฉ๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ํน์ ์์ฒญ์ ๋ํด ์ด๋ค ํํฐ๊ฐ ์คํ๋๋์ง, ์ถ๊ฐํ ํํฐ๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ ํธ์ถ๋๋์ง, ์์ธ๊ฐ ๋ฐ์ํ๋ ์์น๋ ์ด๋์ธ์ง ๋ฑ์ ์ ๋ณด๋ฅผ ๋๋ฒ๊น ํ ์ ์์ต๋๋ค.
DEBUG ๋ ๋ฒจ๋ก ๋ก๊น
์ ์ค์ ํ์ฌ, ํน์ ์์ฒญ์ ๋ํด ๊ฐ๋ณ ํํฐ ํธ์ถ ๋ด์ฉ๊ณผ ์คํ ์์๋ฅผ ๊ธฐ๋กํ ์ ์์ต๋๋ค.์ด์ ๊ฐ์ ์ค์ ์ ํตํด ๋ณด์ ํํฐ ์ฒด์ธ์ ๋ํ ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ๊ณ , Spring Security์ ํํฐ ์ฒด์ธ ์๋ ๋ฐฉ์์ ํจ๊ณผ์ ์ผ๋ก ํ์ ํ ์ ์์ต๋๋ค. ๐ก๏ธโจ
๊ธฐ๋ณธ ๋ณด์ ํํฐ๋ค์ด ์ ํ๋ฆฌ์ผ์ด์ ์ ์ถฉ๋ถํ ๋ณด์์ ์ ๊ณตํ์ง๋ง, ํน์ ์ํฉ์์ ์ปค์คํ ํํฐ๊ฐ ํ์ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ํ ๋ํธ ID(tenant id) ํค๋๋ฅผ ํตํด ํ์ฌ ์ฌ์ฉ์๊ฐ ํด๋น ํ ๋ํธ์ ์ ๊ทผํ ์ ์๋ ๊ถํ์ด ์๋์ง ํ์ธํ๋ ํํฐ๋ฅผ ์ถ๊ฐํ๋ ค๋ฉด ์ธ์ฆ ํํฐ ์ดํ์ ์ปค์คํ ํํฐ๋ฅผ ๋ฐฐ์นํ๋ ๊ฒ์ด ์ ์ ํฉ๋๋ค.
UsernamePasswordAuthenticationFilter ๋๋ BasicAuthenticationFilter)๊ฐ ์คํ๋ ํ์๋ง SecurityContext์ ์ ์ฅ๋ฉ๋๋ค.SecurityContext์ ์ ์ฅํฉ๋๋ค.SecurityContext์์ ํ์ฌ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ์ฐธ์กฐํ ์ ์์ต๋๋ค.SecurityContext์ ์ฌ์ฉ์ ์ ๋ณด๊ฐ ์ค์ ๋์ง ์์๊ธฐ ๋๋ฌธ์ ํ์ํ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ฐธ์กฐํ ์ ์์ต๋๋ค.์๋๋ tenant id ํค๋์ ์ฌ์ฉ์ ์ ๋ณด๋ก ํ
๋ํธ ์ ๊ทผ ๊ถํ์ ๊ฒ์ฌํ๋ ์ปค์คํ
ํํฐ๋ฅผ ์ธ์ฆ ํํฐ ์ดํ์ ์ถ๊ฐํ๋ ์์์
๋๋ค:
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.stereotype.Component;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class TenantIdFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String tenantId = request.getHeader("tenant id");
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && tenantId != null) {
// ํ
๋ํธ ์ ๊ทผ ๊ถํ ๊ฒ์ฌ๋ฅผ ์ํ
// ์: ํ์ฌ ์ฌ์ฉ์ ์ ๋ณด์ tenantId๋ฅผ ์ด์ฉํ์ฌ ์ ๊ทผ ๊ถํ ํ์ธ ๋ก์ง ์ถ๊ฐ
}
filterChain.doFilter(request, response); // ๋ค์ ํํฐ๋ก ์ ๋ฌ
}
}
์์์ ์์ฑํ TenantIdFilter๋ฅผ Spring Security์ ํํฐ ์ฒด์ธ์ ์ถ๊ฐํ๊ณ , ์ธ์ฆ ํํฐ ์ดํ์ ๋ฐฐ์นํฉ๋๋ค.
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final TenantIdFilter tenantIdFilter;
public SecurityConfig(TenantIdFilter tenantIdFilter) {
this.tenantIdFilter = tenantIdFilter;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.addFilterAfter(tenantIdFilter, UsernamePasswordAuthenticationFilter.class) // ์ธ์ฆ ํํฐ ํ์ ์ปค์คํ
ํํฐ ์ถ๊ฐ
.authorizeRequests()
.anyRequest().authenticated();
}
}
์ ์ฝ๋์์ TenantIdFilter๋ UsernamePasswordAuthenticationFilter ์ดํ์ ์คํ๋๋ฏ๋ก, SecurityContext์์ ์์ ํ๊ฒ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ฐธ์กฐํ ์ ์์ต๋๋ค.
์ธ์ฆ ํํฐ ์คํ
SecurityContext์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํฉ๋๋ค.์ปค์คํ ํํฐ ์คํ
TenantIdFilter๊ฐ SecurityContext์์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์ tenant id ํค๋์ ํจ๊ป ์ ๊ทผ ๊ถํ์ ํ์ธํฉ๋๋ค.๊ถํ ๋ถ์ฌ ํํฐ ์คํ
TenantIdFilter์ ๊ฐ์ด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ํ์๋ก ํ๋ ์ปค์คํ
ํํฐ๋ ์ธ์ฆ ํํฐ ์ดํ์ ๋ฐฐ์น๋์ด SecurityContext์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์์ ํ๊ฒ ์ฌ์ฉํ ์ ์์ต๋๋ค.addFilterAfter๋ฅผ ํตํด ํํฐ ์์๋ฅผ ์กฐ์ ํ ์ ์์ผ๋ฉฐ, ์ด๋ฅผ ํตํด ์ ํํ ์์๋ก ํํฐ๋ค์ด ์คํ๋๋๋ก ๋ณด์ฅํฉ๋๋ค. ๐ก๏ธ๐ผ์ TenantFilter ์ํ ์ฝ๋์์๋ Filter ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ์ฌ, tenant id ํค๋ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์์ ์ ๊ทผ ๊ถํ์ ํ์ธํ๋ ์์
์ ์ํํฉ๋๋ค. ์์ฒญ๋ง๋ค ํํฐ๊ฐ ์คํ๋๋๋ก ์ค์ ๋์์ง๋ง, Spring Security์์ OncePerRequestFilter๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฒญ๋น ํ ๋ฒ๋ง ์คํ๋๋๋ก ๋ณด์ฅํ ์ ์์ต๋๋ค.
OncePerRequestFilter๋ Spring์์ ์ ๊ณตํ๋ ํํฐ์ ๊ธฐ๋ณธ ํด๋์ค์ด๋ฉฐ, ๋์ผํ ์์ฒญ ๋ด์์ ์ค๋ณต ํธ์ถ์ ๋ฐฉ์งํฉ๋๋ค. OncePerRequestFilter๋ฅผ ์ฌ์ฉํ๋ฉด ์์ฒญ์ด ์ฌ๋ฌ ๋ฒ ํํฐ ์ฒด์ธ์ ๊ฑฐ์น๋๋ผ๋ ํํฐ๊ฐ ํ ๋ฒ๋ง ์คํ๋๋๋ก ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
OncePerRequestFilter๋ก ๋ณํํ์ฌ ์ค๋ณต ํธ์ถ์ ๋ฐฉ์งํ๊ณ , HttpServletRequest์ HttpServletResponse ๋งค๊ฐ๋ณ์๋ฅผ ์ฌ์ฉํ๋ doFilterInternal ๋ฉ์๋๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค.
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
public class TenantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String tenantId = request.getHeader("X-Tenant-Id"); // ํค๋์์ tenant id ๊ฐ์ ธ์ค๊ธฐ
// tenant id์ ๋ํ ์ ๊ทผ ๊ถํ ํ์ธ
boolean hasAccess = isUserAllowed(tenantId);
if (hasAccess) {
filterChain.doFilter(request, response); // ์ฒด์ธ์์ ๋๋จธ์ง ํํฐ ํธ์ถ
} else {
throw new AccessDeniedException("Access denied"); // ์ ๊ทผ ๊ฑฐ๋ถ ์์ธ ๋ฐ์
}
}
private boolean isUserAllowed(String tenantId) {
// tenant id์ ๋ํ ์ ๊ทผ ๊ถํ์ ํ์ธํ๋ ๋ก์ง์ ์ถ๊ฐํ์ธ์.
return tenantId != null && tenantId.equals("authorizedTenantId"); // ์์๋ก "authorizedTenantId" ๋น๊ต
}
}
OncePerRequestFilter ์์
OncePerRequestFilter๋ฅผ ์์ํ์ฌ, ํํฐ๊ฐ ๋์ผํ ์์ฒญ ๋ด์์ ์ค๋ณต ํธ์ถ๋์ง ์๋๋ก ๋ณด์ฅํฉ๋๋ค.
doFilterInternal ๋ฉ์๋
doFilterInternal ๋ฉ์๋์์ tenant id ํค๋๋ฅผ ๊ฐ์ ธ์ isUserAllowed ๋ฉ์๋๋ฅผ ํตํด ์ ๊ทผ ๊ถํ์ ํ์ธํฉ๋๋ค.
์ ๊ทผ ๊ถํ ํ์ธ ๋ฐ ์ฒ๋ฆฌ
isUserAllowed ๋ฉ์๋๊ฐ true๋ฅผ ๋ฐํํ๋ฉด filterChain.doFilter๋ฅผ ํธ์ถํ์ฌ ์ฒด์ธ์ ๋๋จธ์ง ํํฐ๋ฅผ ์คํํฉ๋๋ค.AccessDeniedException์ ๋์ ธ ์์ฒญ์ ์ฐจ๋จํฉ๋๋ค.HttpServletRequest์ HttpServletResponse๋ฅผ ๋งค๊ฐ๋ณ์๋ก ๊ฐ์ง๋ doFilterInternal ๋ฉ์๋๋ฅผ ์ ๊ณตํ์ฌ Spring ํ๊ฒฝ์์ ํํฐ ์ค์ ์ด ์ฉ์ดํฉ๋๋ค.OncePerRequestFilter๋ฅผ ํตํด ํํฐ๋ฅผ ๊ด๋ฆฌํจ์ผ๋ก์จ Spring Security์์ ์ค๋ณต ํธ์ถ ์์ด ์์ฒญ๋น ํ ๋ฒ์ ํํฐ๋ง์ ๋ณด์ฅํ ์ ์์ต๋๋ค.
TenantFilter๋ฅผ AuthorizationFilter ์์ ์ถ๊ฐํจ์ผ๋ก์จ, Spring Security์ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ฌ์ฉํด ํ
๋ํธ ์ ๊ทผ ๊ถํ์ ๊ฒ์ฌํ ์ ์์ต๋๋ค. ์ด ์ค์ ์ HttpSecurity#addFilterBefore ๋ฉ์๋๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋๋ค.
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ๋ค๋ฅธ ๋ณด์ ์ค์
.addFilterBefore(new TenantFilter(), AuthorizationFilter.class);
return http.build();
}
์ธ์ฆ(Authentication):
UsernamePasswordAuthenticationFilter๊ฐ ๋ก๊ทธ์ธ ๊ณผ์ ์์ ์ฌ์ฉ์์ ์ ์ ํ์ธ์ ์ํํ๋ฉฐ, ์ฑ๊ณต ์ SecurityContext์ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์ ์ฅํฉ๋๋ค.TenantFilter ์ถ๊ฐ:
AuthorizationFilter ์์ ๋ฐฐ์น๋์ด, ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ
๋ํธ ์ ๊ทผ ๊ถํ์ ๊ฒ์ฌํฉ๋๋ค.tenant id ํค๋์ SecurityContext์ ์ ์ฅ๋ ์ฌ์ฉ์ ์ ๋ณด๊ฐ ํ์ํ๋ฏ๋ก, ์ธ์ฆ ํํฐ๊ฐ ๋จผ์ ์คํ๋ ํ TenantFilter๊ฐ ์คํ๋์ด์ผ ํฉ๋๋ค.์ธ๊ฐ(Authorization):
TenantFilter๊ฐ ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด์ ์์กดํ๋ฏ๋ก, ์ธ์ฆ ํํฐ๊ฐ ๋จผ์ ์คํ๋ ํ AuthorizationFilter๋ณด๋ค ์์ ๋ฐฐ์นํฉ๋๋ค.TenantFilter๋ tenant id์ ๋ํ ์ ๊ทผ ๊ถํ์ ์ฌ์ ์ ๊ฒ์ฌํ์ฌ, ๊ถํ์ด ์๋ ์์ฒญ์ ๋ฏธ๋ฆฌ ์ฐจ๋จํ๊ณ ๋ถํ์ํ ๊ถํ ๊ฒ์ฌ ๊ณผ์ ์ ์ค์ผ ์ ์์ต๋๋ค.์ด์ ๊ฐ์ ์์๋ก ํํฐ๋ฅผ ๋ฐฐ์นํจ์ผ๋ก์จ, Spring Security๋ ์ ํํ ๋ณด์ ๊ฒ์ฆ ์ ์ฐจ๋ฅผ ๋ฐ๋ฅด๋ฉฐ ์์ ํ๊ฒ ์์ฒญ์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
Spring Boot์์ ํํฐ๋ฅผ ๋ฑ๋กํ๋ ๋ฐฉ์๊ณผ Spring Security์ ๋ณด์ ํํฐ ์ฒด์ธ์ ์ถ๊ฐํ๋ ๋ฐฉ์์ด ๊ฒน์น ๋, ํํฐ๊ฐ ์ค๋ณต ํธ์ถ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด FilterRegistrationBean์ ์ฌ์ฉํ์ฌ Spring Boot๊ฐ ๋ด์ฅ ์ปจํ ์ด๋์ ํํฐ๋ฅผ ์๋์ผ๋ก ๋ฑ๋กํ์ง ์๋๋ก ์ค์ ํ ์ ์์ต๋๋ค.
Spring Boot์ ์๋ ํํฐ ๋ฑ๋ก
@Component๋ @Bean์ ์ฌ์ฉํด ํํฐ๋ฅผ Spring Bean์ผ๋ก ๋ฑ๋กํ๋ฉด, Spring Boot๋ ์ด๋ฅผ ๋ด์ฅ ์น ์ปจํ
์ด๋(Tomcat ๋ฑ)์ ์๋์ผ๋ก ๋ฑ๋กํฉ๋๋ค. Spring Security์ ๋ณด์ ํํฐ ์ฒด์ธ ๋ฑ๋ก
HttpSecurity.addFilterBefore() ๋๋ HttpSecurity.addFilterAfter()๋ฅผ ํตํด ํํฐ๋ฅผ ๋ณด์ ํํฐ ์ฒด์ธ์ ์ถ๊ฐํฉ๋๋ค.๋ฐ๋ผ์ ๋์ผํ ํํฐ๊ฐ ๋ด์ฅ ์ปจํ ์ด๋์ Spring Security์ ๋ณด์ ํํฐ ์ฒด์ธ์์ ๊ฐ๊ฐ ํ ๋ฒ์ฉ ํธ์ถ๋์ด ์ค๋ณต ํธ์ถ๋๋ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
ํํฐ๊ฐ ์ค๋ณต ํธ์ถ๋์ง ์๋๋ก ํ๋ฉด์๋ ์์กด์ฑ ์ฃผ์ ์ด ํ์ํ ๋๋ FilterRegistrationBean์ ์ฌ์ฉํด Spring Boot๊ฐ ๋ด์ฅ ์ปจํ ์ด๋์ ํํฐ๋ฅผ ๋ฑ๋กํ์ง ์๋๋ก ํ ์ ์์ต๋๋ค.
@Bean
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter tenantFilter) {
FilterRegistrationBean<TenantFilter> registrationBean = new FilterRegistrationBean<>(tenantFilter);
registrationBean.setEnabled(false); // ๋ด์ฅ ์ปจํ
์ด๋์ ์๋ ๋ฑ๋ก ๋ฐฉ์ง
return registrationBean;
}
setEnabled(false)๋ฅผ ์ค์ ํ๋ฉด, Spring Boot๊ฐ ํด๋น ํํฐ๋ฅผ ๋ด์ฅ ์ปจํ
์ด๋์ ์๋์ผ๋ก ๋ฑ๋กํ์ง ์๋๋ก ํฉ๋๋ค.TenantFilter๋ ๋ณด์ ํํฐ ์ฒด์ธ์์๋ง ํธ์ถ๋๋ฉฐ, ์ค๋ณต ํธ์ถ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ง ์์ต๋๋ค.์ด์ TenantFilter๊ฐ ๋ด์ฅ ์ปจํ ์ด๋์์ ํธ์ถ๋์ง ์๊ณ , ์ค์ง Spring Security ๋ณด์ ํํฐ ์ฒด์ธ์์๋ง ํธ์ถ๋๋๋ก ์ค์ ํ ์ ์์ต๋๋ค.
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ๋ค๋ฅธ ๋ณด์ ์ค์
.addFilterBefore(new TenantFilter(), AuthorizationFilter.class);
return http.build();
}
์ด ์ค์ ์ ํตํด TenantFilter๋ ๋ณด์ ํํฐ ์ฒด์ธ์์ AuthorizationFilter ์์ ์์นํ์ฌ ์ธ์ฆ๋ ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ
๋ํธ ์ ๊ทผ ๊ถํ์ ํ์ธํฉ๋๋ค.
FilterRegistrationBean์ ํตํด Spring Boot๊ฐ TenantFilter๋ฅผ ๋ด์ฅ ์ปจํ
์ด๋์ ๋ฑ๋กํ์ง ์๋๋ก ์ค์ ํ์ฌ, ํํฐ๊ฐ ์ค๋ณต ํธ์ถ๋์ง ์๋๋ก ํฉ๋๋ค.HttpSecurity.addFilterBefore() ๋ฑ์ ์ฌ์ฉํ์ฌ ํํฐ๋ฅผ ๋ณด์ ํํฐ ์ฒด์ธ์๋ง ์ถ๊ฐํด, ์๋ํ ๋๋ก ์์์ ๋ฐ๋ผ ํธ์ถ๋๋๋ก ํฉ๋๋ค.TenantFilter๋ฅผ Spring Bean์ผ๋ก ๊ด๋ฆฌํ๋ฉด์, ์์กด์ฑ ์ฃผ์
์ ํ์ฉํด ํํฐ์ ๊ธฐ๋ฅ์ ํ์ฅํ ์ ์์ต๋๋ค.์ด ์ค์ ์ ํตํด ํํฐ๊ฐ ๋ณด์ ํํฐ ์ฒด์ธ์์๋ง ํธ์ถ๋๋ฉฐ ์ค๋ณต ํธ์ถ์ ๋ฐฉ์งํ๊ณ , Spring Security ๋ณด์ ๋ก์ง์ ๋ง๊ฒ ์ ํํ๊ฒ ๋์ํ๊ฒ ๋ฉ๋๋ค. ๐ก๏ธ
ExceptionTranslationFilter๋ Spring Security์์ ์ธ์ฆ ์คํจ(AuthenticationException) ๋ฐ ๊ถํ ๋ถ์กฑ(AccessDeniedException) ์์ธ๋ฅผ ๊ฐ์งํ๊ณ , ์ด๋ฅผ ์ ์ ํ HTTP ์๋ต์ผ๋ก ๋ณํํ์ฌ ์ฌ์ฉ์์๊ฒ ๋ฐํํ๋ ์ญํ ์ ํฉ๋๋ค. ๊ฐ๋ฐ์๊ฐ ์ง์ ์์ธ ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ์์ฑํ์ง ์์๋, ๋ณด์ ๊ด๋ จ ์ค๋ฅ๊ฐ ๋ฐ์ํ ๋ ์ผ๊ด์ฑ ์๋ ์๋ต์ ์ ๊ณตํ ์ ์์ต๋๋ค.
1๏ธโฃ ์์ธ ๊ฐ์ง
2๏ธโฃ ์์ธ ์ฒ๋ฆฌ ๋ฐ HTTP ์๋ต ๋ณํ
์ธ์ฆ ์์ธ (AuthenticationException):
์ธ์ฆ๋์ง ์์ ์ฌ์ฉ์๊ฐ ๋ณดํธ๋ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ค๊ณ ํ ๋ ๋ฐ์ํฉ๋๋ค.
ExceptionTranslationFilter๋ ์ด๋ฌํ ์์ธ๋ฅผ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
ํ๊ฑฐ๋, 401 Unauthorized ์๋ต์ ๋ฐํํฉ๋๋ค.
๊ถํ ์์ธ (AccessDeniedException):
์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ ๊ถํ์ด ์๋ ๋ฆฌ์์ค์ ์ ๊ทผํ๋ ค๊ณ ํ ๋ ๋ฐ์ํฉ๋๋ค.
์ด ๊ฒฝ์ฐ ExceptionTranslationFilter๋ 403 Forbidden ์ํ ์ฝ๋๋ก ์๋ต์ ๋ณํํ์ฌ, ์ ๊ทผ์ด ๊ฑฐ๋ถ๋์์์ ์ฌ์ฉ์์๊ฒ ์๋ฆฝ๋๋ค.
3๏ธโฃ ํํฐ ์ฒด์ธ์์์ ์์น
์์ฒญ ์ฒ๋ฆฌ ์ค ์์ธ ๋ฐ์
ExceptionTranslationFilter๊ฐ ์์ธ๋ฅผ ๊ฐ์ง ๋ฐ ์ฒ๋ฆฌ
HTTP ์๋ต ๋ฐํ
์ค์ ์ง์คํ๋ ์์ธ ์ฒ๋ฆฌ
ExceptionTranslationFilter๋ฅผ ํตํด ์ธ์ฆ ๋ฐ ๊ถํ ์ค๋ฅ์ ๋ํ ์ผ๊ด์ฑ ์๋ ์๋ต์ ์ ๊ณตํ์ฌ, ๋ณด์ ์์ธ ์ฒ๋ฆฌ์ ๋ณต์ก์ฑ์ ์ค์ด๊ณ ์ฝ๋ ์ผ๊ด์ฑ์ ์ ์งํ ์ ์์ต๋๋ค.
๊ฐ๋ฐ์ ํธ์์ฑ
๊ฐ๋ฐ์๊ฐ ์ง์ ์์ธ ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ์์ฑํ์ง ์์๋ ๋๋ฏ๋ก, ๋ณด์ ์์ธ์ ๋ํ ์๋ต ์ฒ๋ฆฌ๊ฐ ๊ฐ์ํ๋ฉ๋๋ค.
์ฌ์ฉ์ ๊ฒฝํ ํฅ์
ExceptionTranslationFilter๋ ์ธ์ฆ ๋ฐ ๊ถํ ์ค๋ฅ ์ ์ ์ ํ ๋ฆฌ๋๋ ์
๊ณผ ์๋ต ๋ณํ์ ํตํด ์ฌ์ฉ์๊ฐ ์ค๋ฅ๋ฅผ ์ดํดํ ์ ์๋๋ก ๋์ต๋๋ค.
ExceptionTranslationFilter๋ Spring Security์์ ์ธ์ฆ ์คํจ์ ๊ถํ ๋ถ์กฑ๊ณผ ๊ฐ์ ๋ณด์ ์์ธ๋ฅผ ๊ฐ์งํ์ฌ ์ ์ ํ HTTP ์๋ต์ผ๋ก ๋ณํํฉ๋๋ค. ์ด๋ก ์ธํด ๋ณด์ ์์ธ๊ฐ ๋ฐ์ํ ๋ ์ค์ํ๋ ์์ธ ์ฒ๋ฆฌ์ ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๋ฉฐ, ๊ฐ๋ฐ์๋ ๋ณด์ ์์ธ ์ฒ๋ฆฌ์ ๋ํ ๋ถ๋ด์ ์ค์ผ ์ ์์ต๋๋ค. ๐ก๏ธ
ExceptionTranslationFilter๋ Spring Security์์ ๋ฐ์ํ๋ ์ธ์ฆ ๋ฐ ๊ถํ ์ค๋ฅ๋ฅผ ์ ์ ํ ์ฒ๋ฆฌํ๋ ํํฐ๋ก, ์์ธ๊ฐ ๋ฐ์ํ๋ฉด ์ธ์ฆ ์์ฒญ์ด๋ ์ ๊ทผ ๊ฑฐ๋ถ ์ฒ๋ฆฌ๋ฅผ ์๋์ผ๋ก ์คํํฉ๋๋ค. ์๋๋ ExceptionTranslationFilter๊ฐ ๋์ํ๋ ๊ตฌ์ฒด์ ์ธ ๋จ๊ณ์ ๊ฐ ๊ณผ์ ์ ์๋ฏธ์ ๋๋ค.
FilterChain.doFilter(request, response)๋ฅผ ํธ์ถํ์ฌ ๋๋จธ์ง ํํฐ์ ์์(Resource)์ ์คํํ๋๋ก ๋๊น๋๋ค.AuthenticationException ๋ฐ์ ์: ์ธ์ฆ ์์AuthenticationException์ผ๋ก ๋ํ๋ฉ๋๋ค.SecurityContextHolder ์ด๊ธฐํ
SecurityContextHolder๋ ํ์ฌ์ ์ธ์ฆ ์ํ๋ฅผ ์ ์งํ๋ ์ ์ฅ์์
๋๋ค. SecurityContextHolder๋ฅผ ์ด๊ธฐํํ์ฌ, ์ด์ ์์ฒญ์ ์ธ์ฆ ์ ๋ณด๊ฐ ๋จ์ง ์๋๋ก ํฉ๋๋ค.HttpServletRequest ์ ์ฅ
AuthenticationEntryPoint ํธ์ถ
AuthenticationEntryPoint๋ ํด๋ผ์ด์ธํธ์๊ฒ ์๊ฒฉ ์ฆ๋ช
(๋ก๊ทธ์ธ)์ ์๊ตฌํ๋ ์ญํ ์ ํฉ๋๋ค. formLogin์ ์ค์ ํ ๊ฒฝ์ฐ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
ํ๊ณ , httpBasic์ ์ฌ์ฉํ ๊ฒฝ์ฐ WWW-Authenticate ํค๋๋ฅผ ํตํด ์ฌ์ฉ์ ์๊ฒฉ์ ์์ฒญํฉ๋๋ค.AccessDeniedException ๋ฐ์ ์: ์ ๊ทผ ๊ฑฐ๋ถ ์ฒ๋ฆฌAccessDeniedHandler๋ AccessDeniedException์ด ๋ฐ์ํ์ ๋ ํธ์ถ๋์ด, ์ ๊ทผ ๊ฑฐ๋ถ ์ํฉ์ ์ฒ๋ฆฌํฉ๋๋ค.AccessDeniedHandler๋ฅผ ์ปค์คํฐ๋ง์ด์งํ์ฌ ํน์ ์ ๊ทผ ๊ฑฐ๋ถ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
ํ๊ฑฐ๋ ์ฌ์ฉ์์๊ฒ ์ ์ ํ ๋ฉ์์ง๋ฅผ ์ ๊ณตํ ์ ์์ต๋๋ค.์ค์ ์ง์คํ๋ ์์ธ ์ฒ๋ฆฌ
ExceptionTranslationFilter๋ ์ธ์ฆ ๋ฐ ๊ถํ ์ค๋ฅ๋ฅผ ํต์ผ๋ ๋ฐฉ์์ผ๋ก ์ฒ๋ฆฌํ์ฌ ์ฝ๋์ ์ผ๊ด์ฑ์ ์ ์งํ๊ณ , ์์ธ ์ฒ๋ฆฌ ๋ก์ง์ ๋จ์ํํฉ๋๋ค.
๊ฐ๋ฐ์ ํธ์์ฑ
๊ฐ๋ฐ์๊ฐ ์์ธ ์ฒ๋ฆฌ ์ฝ๋๋ฅผ ์์ฑํ ํ์ ์์ด, Spring Security๊ฐ ๊ธฐ๋ณธ์ ์ธ ๋ณด์ ์์ธ์ ๋ํด ์๋์ผ๋ก ์๋ต์ ์ ๊ณตํฉ๋๋ค.
์ฌ์ฉ์ ๊ฒฝํ ํฅ์
ExceptionTranslationFilter๋ ์ธ์ฆ ์คํจ ์ ๋ก๊ทธ์ธ ํ์ด์ง ๋ฆฌ๋๋ ์
์ด๋, ๊ถํ ๋ถ์กฑ ์ 403 ์๋ต์ ํตํด ์ฌ์ฉ์๊ฐ ์ค๋ฅ๋ฅผ ์ดํดํ ์ ์๋๋ก ๋์ต๋๋ค.
AuthenticationEntryPoint๊ฐ ์๊ฒฉ ์ฆ๋ช
์ ์์ฒญํฉ๋๋ค.AccessDeniedHandler๊ฐ 403 ์๋ต์ ๋ฐํํฉ๋๋ค.ExceptionTranslationFilter๋ ์ธ์ฆ ๋ฐ ๊ถํ ์ค๋ฅ๋ฅผ ์ฒ๋ฆฌํ์ฌ HTTP ์๋ต์ ์ ์ ํ ๋ณํํ๋ ์ญํ ์ ๋ด๋นํ๋ฉฐ, Spring Security์์ ์ค์ ์์ธ ์ฒ๋ฆฌ ๋ฐ ๋ณด์ ๊ด๋ฆฌ๋ฅผ ์ํ ์ค์ํ ๊ตฌ์ฑ ์์์ ๋๋ค. ๐ก๏ธ
์ ํ๋ฆฌ์ผ์ด์ ์ด AccessDeniedException์ด๋ AuthenticationException์ ๋ฐ์์ํค์ง ์์ผ๋ฉด, ExceptionTranslationFilter๋ ์๋ฌด๋ฐ ์์ ๋ ์ํํ์ง ์์ต๋๋ค.
ExceptionTranslationFilter๋ ํํฐ ์ฒด์ธ์์ ๋ฐ์ํ๋ ๋ณด์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ์ฌ, ์ธ์ฆ์ด ํ์ํ๋ฉด ์ธ์ฆ ์ ์ฐจ๋ฅผ ์์ํ๊ณ , ์ ๊ทผ ๊ถํ์ด ์์ผ๋ฉด ์ ๊ทผ์ ๊ฑฐ๋ถํ๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค. ์๋๋ ExceptionTranslationFilter์ ์์ฌ ์ฝ๋์ ํด๋น ๋์์ ์ค๋ช ํ๋ ๋จ๊ณ๋ณ ๋ฆฌ๋ทฐ์ ๋๋ค.
try {
filterChain.doFilter(request, response);
} catch (AccessDeniedException | AuthenticationException ex) {
if (!authenticated || ex instanceof AuthenticationException) {
startAuthentication(); // ์ธ์ฆ ์์
} else {
accessDenied(); // ์ ๊ทผ ๊ฑฐ๋ถ
}
}
1๏ธโฃ filterChain.doFilter(request, response) ํธ์ถ
filterChain.doFilter(request, response)๋ฅผ ํธ์ถํ์ฌ, ์ ํ๋ฆฌ์ผ์ด์
์ ๋๋จธ์ง ๋ถ๋ถ(์: FilterSecurityInterceptor ๋๋ ๋ฉ์๋ ๋ณด์)์ผ๋ก ์์ฒญ์ ๋๊น๋๋ค.2๏ธโฃ ์์ธ ๋ฐ์ ์ ์ธ์ฆ ๋๋ ์ ๊ทผ ๊ฑฐ๋ถ ์ฒ๋ฆฌ
์ธ์ฆ๋์ง ์์๊ฑฐ๋ AuthenticationException์ด ๋ฐ์ํ ๊ฒฝ์ฐ
!authenticated๊ฐ ์ฐธ์ด๊ฑฐ๋, ์์ธ๊ฐ AuthenticationException์ธ ๊ฒฝ์ฐ.startAuthentication() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ์ธ์ฆ ํ๋ก์ธ์ค๋ฅผ ์์ํฉ๋๋ค.AuthenticationEntryPoint๋ฅผ ํธ์ถํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ์ธ์ฆ ์ ์ฐจ๋ฅผ ์์ฒญํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
ํ๊ฑฐ๋, WWW-Authenticate ํค๋๋ก ์๊ฒฉ ์ฆ๋ช
์ ์์ฒญํ ์ ์์ต๋๋ค.๊ทธ ์ธ์ ๊ฒฝ์ฐ (AccessDeniedException)
AccessDeniedException์ธ ๊ฒฝ์ฐ.accessDenied() ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ์ ๊ทผ ๊ฑฐ๋ถ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค.AccessDeniedHandler๊ฐ 403 Forbidden ์๋ต์ ๋ฐํํ์ฌ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๊ทผ ๊ฑฐ๋ถ ์ํ๋ฅผ ์๋ฆฝ๋๋ค. ํ์์ ๋ฐ๋ผ, ํน์ ์ ๊ทผ ๊ฑฐ๋ถ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
ํ ์๋ ์์ต๋๋ค.ExceptionTranslationFilter๋ ๋ณด์ ํํฐ ์ฒด์ธ์์ ๋ฐ์ํ๋ AuthenticationException ๋ฐ AccessDeniedException์ ๊ฐ์งํ๊ณ , ์ํฉ์ ๋ง๊ฒ ์ธ์ฆ ์์ฒญ ๋๋ ์ ๊ทผ ๊ฑฐ๋ถ ์ฒ๋ฆฌ๋ฅผ ์ํํฉ๋๋ค.
AuthenticationEntryPoint๋ฅผ ํตํด ์๊ฒฉ ์ฆ๋ช
์ ์์ฒญํฉ๋๋ค.์ด ํํฐ๋ Spring Security์์ ์์ธ๋ฅผ ์ผ๊ด๋๊ฒ ์ฒ๋ฆฌํ๊ณ ์ฌ์ฉ์ ๊ฒฝํ์ ํฅ์์ํค๋ ๋ฐ ์ค์ํ ์ญํ ์ ํฉ๋๋ค. ๐ก๏ธ
RequestCache๋ Spring Security์์ ์ธ์ฆ์ด ํ์ํ ์์์ ๋ํ ์์ฒญ์ ์์๋ก ์ ์ฅํ๊ณ , ์ฌ์ฉ์๊ฐ ์ธ์ฆ์ ์๋ฃํ ํ ์๋ ์์ฒญ์ ๋ค์ ์คํํ ์ ์๊ฒ ๋๋ ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์๋ ์ธ์ฆ ๊ณผ์ ์ดํ์๋ ์๋ ์์ฒญํ ์์์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ์ ๊ทผํ ์ ์์ต๋๋ค.
1๏ธโฃ ์ธ์ฆ๋์ง ์์ ์ํ๋ก ๋ณดํธ๋ ์์ ์์ฒญ ์
2๏ธโฃ RequestCache๋ฅผ ํตํ ์์ฒญ ์ ์ฅ
HttpSessionRequestCache๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ์์๋ก ์ ์ฅํฉ๋๋ค.HttpSessionRequestCache๋ HttpSession์ HttpServletRequest ๊ฐ์ฒด๋ฅผ ์ ์ฅํ์ฌ, ์ธ์ฆ์ด ์๋ฃ๋๋ฉด ์ด๋ฅผ ๋ค์ ๊บผ๋ด์ ์๋ ์์์ ์์ฒญํ ์ ์๊ฒ ํฉ๋๋ค.3๏ธโฃ ์ธ์ฆ ํ ์๋ ์์ฒญ์ผ๋ก ๋ณต๊ท
๋ณดํธ๋ ํ์ด์ง์ ์ ๊ทผ ์๋
/member/profile ํ์ด์ง์ ์ ๊ทผํ๋ ค ํฉ๋๋ค.์์ฒญ์ ์ ์ฅ
HttpSessionRequestCache๊ฐ ํ์ฌ HttpServletRequest ๊ฐ์ฒด๋ฅผ ์ฌ์ฉ์์ ์ธ์
์ ์ ์ฅํ์ฌ, ์ธ์ฆ ์ดํ์ ์ด ์์ฒญ์ ๋ค์ ์คํํ ์ ์๋๋ก ์ค๋นํฉ๋๋ค.๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋๋ ์
AuthenticationEntryPoint๊ฐ ํธ์ถ๋์ด ์ฌ์ฉ์๋ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋ํ๊ฒ ๋ฉ๋๋ค.์ธ์ฆ ํ ์๋ ์์ฒญ์ผ๋ก ๋ฆฌ๋๋ ์
/member/profile ํ์ด์ง๋ฅผ ์์ฒญํ๊ฒ ํฉ๋๋ค.Spring Security๋ RequestCache ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ์ฌ๋ฌ ๊ฐ์ง ๊ตฌํ์ฒด๋ฅผ ์ ๊ณตํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก๋ HttpSessionRequestCache๊ฐ ์ฌ์ฉ๋๋ฉฐ, ํ์์ ๋ฐ๋ผ ๋ค๋ฅธ ๊ตฌํ์ฒด๋ฅผ ์ ํํ๊ฑฐ๋ ์ฌ์ฉ์ ์ ์ ๊ตฌํ์ฒด๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค.
HttpSessionRequestCache
NullRequestCache
RequestCache๋ ์ธ์ฆ์ด ํ์ํ ์์์ ๋ํด ์๋ ์์ฒญ์ ์ ์ฅํ๊ณ , ์ฌ์ฉ์๊ฐ ์ธ์ฆ์ ์๋ฃํ ํ ์ ์ฅ๋ ์์ฒญ์ ๋ค์ ์คํํ ์ ์๋๋ก ๋๋ ์ญํ ์ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ฌ์ฉ์๋ ์ธ์ฆ ๊ณผ์ ์ดํ์๋ ์๋ ์์ฒญํ ํ์ด์ง์ ์ฝ๊ฒ ์ ๊ทผํ ์ ์์ผ๋ฉฐ, Spring Security๋ ๊ธฐ๋ณธ์ ์ผ๋ก HttpSessionRequestCache๋ฅผ ํตํด ์ด ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค.
์ด ๊ธฐ๋ฅ์ ์ธ์ฆ ํ ์ฌ์ฉ์ ๊ฒฝํ์ ๊ฐ์ ํ์ฌ, ์์ ์ ๊ทผ์ ๋ณด๋ค ์์ฐ์ค๋ฝ๊ณ ๋งค๋๋ฝ๊ฒ ์ฒ๋ฆฌํ ์ ์๊ฒ ํฉ๋๋ค. ๐ก๏ธ
์ ์ฝ๋์์๋ HttpSessionRequestCache๊ฐ continue๋ผ๋ ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋ ์์ฒญ์ ๋ํด์๋ง ์ ์ฅ๋ ์์ฒญ์ ํ์ธํ๋๋ก ์ค์ ํ์ต๋๋ค. ์ด ์ค์ ์ ํตํด ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ ์ ์ฅ๋ ์์ฒญ์ ํ์ธํ ์ ์์ผ๋ฉฐ, ์ฌ์ฉ์ ๊ฒฝํ์ ๋์ฑ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ต๋๋ค.
@Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName("continue"); // "continue" ํ๋ผ๋ฏธํฐ๊ฐ ์์ ๋๋ง ์์ฒญ ํ์ธ
http
.requestCache((cache) -> cache
.requestCache(requestCache)
);
return http.build();
}
HttpSessionRequestCache ์ธ์คํด์ค ์์ฑ ๋ฐ ํ๋ผ๋ฏธํฐ ์ค์
HttpSessionRequestCache ๊ฐ์ฒด๋ฅผ ์์ฑํ ํ, setMatchingRequestParameterName("continue")๋ฅผ ํธ์ถํ์ฌ continue ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋ ์์ฒญ์ ๋ํด์๋ง ์ ์ฅ๋ ์์ฒญ์ ํ์ธํ๋๋ก ์ค์ ํฉ๋๋ค.HttpSessionRequestCache๋ ๋ชจ๋ ์ธ์ฆ๋์ง ์์ ์์ฒญ์ ์ ์ฅํ์ง๋ง, ํน์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ ํจ์ผ๋ก์จ ์ ์ฅ๋ ์์ฒญ ํ์ธ ์กฐ๊ฑด์ ์ ํํ ์ ์์ต๋๋ค.HttpSecurity ์ค์ ์์ RequestCache ์ ์ฉ
.requestCache((cache) -> cache.requestCache(requestCache))๋ฅผ ํตํด, Spring Security์ ๋ณด์ ์ค์ ์ ์ปค์คํ
requestCache๋ฅผ ์ ์ฉํฉ๋๋ค.continue ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋ ์์ฒญ๋ง RequestCache์ ์ํด ํ์ธ๋๊ณ , ์ธ์ฆ ํ ์ ์ฅ๋ ์์ฒญ์ ๋ค์ ์คํํ ์ ์๊ฒ ๋ฉ๋๋ค.์์ฒญ์ด ์ ์ฅ๋๋ ๊ฒฝ์ฐ
/protected/resource?continue=true์ ๊ฐ์ด continue ํ๋ผ๋ฏธํฐ๊ฐ ํฌํจ๋ URL์ ์ ๊ทผํฉ๋๋ค.HttpSessionRequestCache๋ ์ด ์์ฒญ์ ์ฌ์ฉ์ ์ธ์
์ ์ ์ฅํ์ฌ, ์ธ์ฆ์ด ์๋ฃ๋ ํ ์์ฒญ์ ๋ค์ ์คํํ ์ ์๋๋ก ์ค๋นํฉ๋๋ค.์์ฒญ์ด ์ ์ฅ๋์ง ์๋ ๊ฒฝ์ฐ
continue ํ๋ผ๋ฏธํฐ ์์ด /protected/resource์ ์ ๊ทผํ๋ฉด, HttpSessionRequestCache๋ ์์ฒญ์ ์ ์ฅํ์ง ์์ต๋๋ค.continue ํ๋ผ๋ฏธํฐ๋ฅผ ํตํด ํน์ ์์ฒญ์ ๋ํด์๋ง RequestCache๋ฅผ ํ์ฑํํ์ฌ, ๋ถํ์ํ ์์ฒญ ์ ์ฅ์ ์ค์ผ ์ ์์ต๋๋ค.์ด ์ค์ ์ RequestCache๊ฐ ํน์ ์์ฒญ๋ง ์ ์ฅํ๋๋ก ํ์ฌ, ์ธ์ฆ ํ ์๋ ์์ฒญ์ผ๋ก ๋์๊ฐ๋ ๊ธฐ๋ฅ์ ๋ ์ธ๋ฐํ๊ฒ ์ ์ดํ ์ ์์ต๋๋ค. ๐ก๏ธ
NullRequestCache๋ Spring Security์์ ์ธ์ฆ๋์ง ์์ ์์ฒญ์ ์ธ์
์ ์ ์ฅํ์ง ์๋๋ก ์ค์ ํ ์ ์๋ RequestCache ๊ตฌํ์ฒด์
๋๋ค. ํน์ ์ํฉ์์ ์์ฒญ์ ์ ์ฅํ์ง ์์ผ๋ ค๋ฉด NullRequestCache๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ๋ณ๋๋ก ๊ธฐ๋กํ์ง ์๊ณ , ์ธ์ฆ ํ์ ํญ์ ์ง์ ๋ ํ์ด์ง(์: ํ ํ์ด์ง)๋ก ๋ฆฌ๋๋ ์
ํ ์ ์์ต๋๋ค.
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
.requestCache((cache) -> cache
.requestCache(nullRequestCache) // NullRequestCache๋ฅผ ์ฌ์ฉํ์ฌ ์์ฒญ์ ์ ์ฅํ์ง ์๋๋ก ์ค์
);
return http.build();
}
NullRequestCache ๊ฐ์ฒด๋ฅผ ์์ฑํ์ฌ, ์ธ์ฆ๋์ง ์์ ์์ฒญ์ ์ธ์
์ ์ ์ฅํ์ง ์๋๋ก ํฉ๋๋ค..requestCache((cache) -> cache.requestCache(nullRequestCache))๋ฅผ ํตํด, Spring Security์ ๋ณด์ ์ค์ ์ NullRequestCache๋ฅผ ์ ์ฉํฉ๋๋ค.NullRequestCache๋ฅผ ํตํด ์ธ์ ์ ์์ฒญ์ ์ ์ฅํ์ง ์๋๋ก ์ค์ ํจ์ผ๋ก์จ, Spring Security๊ฐ ์ธ์ฆ ํ ํน์ ํ์ด์ง๋ก ์ผ๊ด๋๊ฒ ๋ฆฌ๋๋ ์ ํ๊ฑฐ๋ ๋ค๋ฅธ ์ ์ฅ ๋ฐฉ์์ ํ์ฉํ ์ ์์ต๋๋ค. ๐ก๏ธ
RequestCacheAwareFilter๋ Spring Security์์ ์ธ์ฆ ์๋ฃ ํ, ์ธ์ฆ ์ ์์ฒญํ๋ ์์์ ์๋์ผ๋ก ์ ๊ทผํ ์ ์๋๋ก ๋์์ฃผ๋ ํํฐ์ ๋๋ค. ์ด ํํฐ๋ RequestCache์ ์ ์ฅ๋ ์๋ ์์ฒญ์ ๋ถ๋ฌ์ ๋ค์ ์ฒ๋ฆฌํ์ฌ, ์ฌ์ฉ์๊ฐ ์ธ์ฆ ์ ์ ์์ฒญํ๋ ์์์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ๋ณต๊ทํ ์ ์๊ฒ ํฉ๋๋ค.
RequestCache์์ ์๋ ์์ฒญ ํ์ธ ๋ฐ ๋ถ๋ฌ์ค๊ธฐ
์๋ ์ ๊ทผ ๊ธฐ๋ฅ
๋ณดํธ๋ ํ์ด์ง ์ ๊ทผ ์๋ ๋ฐ ์ธ์ฆ ์์ฒญ
/protected/resource ํ์ด์ง์ ์ ๊ทผํ๋ฉด Spring Security๋ ์ธ์ฆ ์์ธ๋ฅผ ๋ฐ์์ํค๊ณ , ExceptionTranslationFilter๊ฐ ์ด๋ฅผ ๊ฐ์งํ์ฌ ์ธ์ฆ์ ์์ํฉ๋๋ค.์๋ ์์ฒญ ์ ์ฅ
HttpSessionRequestCache)๊ฐ ์๋ ์์ฒญ(/protected/resource)์ ์ธ์
์ ์ ์ฅํฉ๋๋ค.์ฌ์ฉ์ ์ธ์ฆ ์๋ฃ ํ
RequestCacheAwareFilter๊ฐ ์๋ ์์ฒญ ํ์ธ ๋ฐ ์ฌ์คํ
์๋ ์์ฒญ์ผ๋ก ๋งค๋๋ฌ์ด ๋ณต๊ท
RequestCacheAwareFilter๋ ์ฌ์ฉ์๊ฐ ์ธ์ฆ ํ ์์ฐ์ค๋ฝ๊ฒ ์๋ ์์ฒญํ ์์์ ์ ๊ทผํ ์ ์๋๋ก ํ์ฌ, ์ฌ์ฉ์ ๊ฒฝํ์ ๋งค๋๋ฝ๊ฒ ์ ์งํฉ๋๋ค.
์๋ ์์ฒญ ์คํ
์ธ์ฆ ํ ๋ณ๋์ ์กฐ์ ์์ด RequestCache์ ์ ์ฅ๋ ์๋ ์์ฒญ์ ์๋์ผ๋ก ์คํํ์ฌ, ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค.
RequestCacheAwareFilter๋ RequestCache์ ์ ์ฅ๋ ์๋ ์์ฒญ์ ์ธ์ฆ ํ ์๋์ผ๋ก ๋ถ๋ฌ์ ๋ค์ ์คํํ์ฌ, ์ฌ์ฉ์๊ฐ ์ธ์ฆ ์ ์ ์์ฒญํ๋ ์์์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ๋ณต๊ทํ ์ ์๋๋ก ์ง์ํ๋ ์ค์ํ ํํฐ์ ๋๋ค. ์ด๋ก ์ธํด ์ฌ์ฉ์๋ ์ธ์ฆ ํ์๋ ์๋ ์์ฒญํ ์์์ ์ค๋จ ์์ด ์ ๊ทผํ ์ ์๊ฒ ๋์ด, ๋งค๋๋ฌ์ด ๋ณด์ ํ๋ฆ๊ณผ ์ผ๊ด๋ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค. ๐ก๏ธ
Spring Security๋ ๋ณด์ ๊ด๋ จ ์ด๋ฒคํธ์ ๋ํด ํฌ๊ด์ ์ธ ๋ก๊ทธ๋ฅผ ์ ๊ณตํ์ฌ, ์ ํ๋ฆฌ์ผ์ด์ ์ ๋๋ฒ๊น ํ๊ฑฐ๋ ์์ฒญ์ด ๊ฑฐ๋ถ๋ ์ด์ ๋ฅผ ์ดํดํ ๋ ๋งค์ฐ ์ ์ฉํฉ๋๋ค. Spring Security๋ ๋ณด์ ์กฐ์น๋ก ์ธํด ๋ฐ์ํ 401 ๋ฐ 403 ์ค๋ฅ์ ์ธ๋ถ ์ ๋ณด๋ฅผ ์๋ต ๋ณธ๋ฌธ์ ํฌํจํ์ง ์๊ธฐ ๋๋ฌธ์, ๋ก๊ทธ๋ฅผ ํตํด ๋ฌด์จ ์ผ์ด ๋ฐ์ํ๋์ง ํ์ ํ ์ ์์ต๋๋ค.
์๋ฅผ ๋ค์ด, ์ฌ์ฉ์๊ฐ CSRF ๋ณดํธ๊ฐ ํ์ฑํ๋ ์์์ CSRF ํ ํฐ ์์ด POST ์์ฒญ์ ์๋ํ๋ ๊ฒฝ์ฐ๋ฅผ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค.
Securing POST /hello
... (๋ค์ํ ํํฐ ํธ์ถ)
Invalid CSRF token found for http://localhost:8080/hello
Responding with 403 status code
Securing POST /hello
/hello ๊ฒฝ๋ก์ ๋ํ POST ์์ฒญ ๋ณดํธ๊ฐ ์์๋์์์ ๋ํ๋
๋๋ค.์ฌ๋ฌ ํํฐ ํธ์ถ
DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SecurityContextHolderFilter, HeaderWriterFilter, CsrfFilter ๋ฑ ์ฌ๋ฌ ํํฐ๊ฐ ์์ฒญ์ ๋ณดํธํ๊ธฐ ์ํด ์์๋๋ก ํธ์ถ๋ฉ๋๋ค.Invalid CSRF token found
CsrfFilter๊ฐ ์ ํจํ์ง ์์ CSRF ํ ํฐ์ ๋ฐ๊ฒฌํ์ฌ ์์ฒญ์ด ๊ฑฐ๋ถ๋์์์ ์๋ฆฝ๋๋ค.Responding with 403 status code
AccessDeniedHandlerImpl์ด 403 ์ํ ์ฝ๋๋ก ์๋ตํ๊ณ ์์์ ๋ํ๋
๋๋ค.์ด ๋ก๊ทธ ๋ฉ์์ง๋ฅผ ํตํด CSRF ํ ํฐ์ด ์์ด์ ์์ฒญ์ด ๊ฑฐ๋ถ๋ ์ด์ ๋ฅผ ๋ช ํํ ํ์ ํ ์ ์์ต๋๋ค.
Spring Security์ ๋ชจ๋ ๋ณด์ ์ด๋ฒคํธ๋ฅผ ๊ธฐ๋กํ๋ ค๋ฉด, ์ ํ๋ฆฌ์ผ์ด์ ์ค์ ํ์ผ์ ๋ค์๊ณผ ๊ฐ์ ์ค์ ์ ์ถ๊ฐํ์ธ์.
logging.level.org.springframework.security=DEBUG
logging.level.org.springframework.security.web.FilterChainProxy=TRACE
logging.level.org.springframework.security=DEBUG
Spring Security์ ๋ณด์ ์ด๋ฒคํธ๋ฅผ DEBUG ์์ค์์ ๊ธฐ๋กํฉ๋๋ค.
logging.level.org.springframework.security.web.FilterChainProxy=TRACE
๋ณด์ ํํฐ ์ฒด์ธ์ ์์ธ ๋ก๊ทธ๋ฅผ TRACE ์์ค์์ ๊ธฐ๋กํ์ฌ, ํํฐ์ ํธ์ถ ์์์ ๋์์ ์์ธํ ํ์ธํ ์ ์์ต๋๋ค.
Spring Security์ ๋ก๊ทธ ๊ธฐ๋ฅ์ ํตํด ๋ณด์ ์ด๋ฒคํธ์ ๋ํ ์์ธ ์ ๋ณด๋ฅผ ํ์ธํ๊ณ , ๋ฌธ์ ๋ฅผ ๋ณด๋ค ๋น ๋ฅด๊ฒ ์ง๋จํ ์ ์์ต๋๋ค. DEBUG ๋ฐ TRACE ๋ก๊ทธ๋ ๋ณด์ ์์ฒญ์ด ๊ฑฐ๋ถ๋ ์ด์ ์ ๋ณด์ ํํฐ ์ฒด์ธ์ ๋์์ ์์ธํ ๋ณด์ฌ์ฃผ์ด, ํนํ CSRF ์ค๋ฅ์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค. ๐ก๏ธ