Spring Security #2 ๋ค์ํ ์ฌ์ฉ๋ฒ - ์ต๋ช ์ฌ์ฉ์, ์ธ์ ์ ์ด, ์์ธ์ฒ๋ฆฌ
by JiwonDev
์๋์ ๋ณด์ ๊ธฐ๋ฅ๋ค์ ์ง์ ๊ตฌํํ๊ณ ์ํ๋ฉด ์๋นํ ๊ท์ฐฎ๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ์์๋ ๊ฐ๋จํ ์ค์ ๊ฐ์ ํตํด ๋ค์ํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ค.
๐ญ ์ต๋ช ์ฌ์ฉ์ ํํฐ (AnonymousAuthenticationFilter)
์ด๋ฆ ๊ทธ๋๋ก ํ์ฌ ์ฌ์ฉ์๊ฐ ์ธ์ฆ(๋ก๊ทธ์ธ)์ด ๋์ด์๋์ง, ์๋์ง๋ฅผ ํ๋จํ๋ ํํฐ์ด๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ์์๋ ์ต๋ช ์ฌ์ฉ์๋ผ๊ณ ํ ์ง๋ผ๋, ์ธ์ฆ๊ฐ์ฒด๋ฅผ null๋ก ์ฒ๋ฆฌํ์ง ์๊ณ [์ต๋ช ์ฌ์ฉ์ ํ ํฐ]์ ์์ฑํ๋ค.
์ธ์ฆ๊ฐ์ฒด๊ฐ null์ด๋ผ๋ฉด SecurityContext๋ง์ผ๋ก๋ ์ต๋ช ์ฌ์ฉ์๊ฐ ์๋์ง, ์์ ์ ์์ ํ์ง ์์๊ฑด์ง ๊ตฌ๋ถํ ์ ์๋ค.
์ต๋ช ์ฌ์ฉ์ ํ ํฐ์ ํตํด ์ธ์ฆ ์ฌ๋ถ์ ๋ฐ๋ฅธ ๋ค๋ฅธ ํ๋ฉด์ ๊ตฌํํ ๋, ์๋์ ๊ฐ์ ๋ฉ์๋๋ฅผ ์ฌ์ฉํด์ ์ฝ๊ฒ ๋ง๋ค ์ ์๋ค.
- isAnonymous() - ์ต๋ช ์ฌ์ฉ์์ธ๊ฐ? (ํ์ด์ง์ ๋ก๊ทธ์ธ ๋ฉ๋ด ํ์)
- isAuthenticated() - ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์์ธ๊ฐ? (ํ์ด์ง ๋ก๊ทธ์์ ๋ฉ๋ด ํ์)
๋ฌผ๋ก ์ฒซ ์ ์์์ ์ต๋ช ์ฌ์ฉ์ ํ ํฐ(์ธ์ฆ๊ฐ์ฒด)๊ฐ ์์ฑ๋์๋ค๋ฉด ์ดํ ๋ง๋ฃ๋๊ธฐ ์ ๊น์ง๋ ํด๋นํํฐ๋ ๋์ํ์ง ์๋๋ค.
๐ญ ์ธ์ ๊ด๋ฆฌ ํํฐ (SessionManagementFilter)
๐ ๋์ ์ธ์ ์ ์ด (๋์ผ๊ณ์ ์ค๋ณต์ ์)
ํ ๊ณ์ ์ผ๋ก ์ฌ๋ฌ ์ปดํจํฐ์์ ์ ์ํ๋ ๊ฒฝ์ฐ, ์ธ์ ์ ์ฌ๋ฌ ๊ฐ๊ฐ ์์ฑ๋ ์ ์๋ค.
์ด๋ฅผ ์คํ๋ง ์ํ๋ฆฌํฐ์์๋ http.sessionManagement()๋ฅผ ์ด์ฉํด์ ๊ฐ๋จํ๊ฒ ์ฒ๋ฆฌํ ์ ์๋ค.
maxSessionPreventsLogin(true)๋ฅผ ํ๊ฒ๋๋ฉด ์๋ ๊ทธ๋ฆผ์ [2. ํ์ฌ์ฌ์ฉ์ ์ธ์ฆ์คํจ] ์ฒ๋ผ ๋์ํ๋ค.
์ด๋ ConcurrentSessionFilter ๋ฅผ ํตํด ๋์ํ๊ฒ๋๋ค. ํํฐ์ ๋์์ ์ ๊ธ์์ ๋ง์ด ๋ค๋ค์ผ๋ฏ๋ก, ๊ถ๊ธํ๋ฉด ๊ตฌ๊ธ ๊ฒ์ํ์
2021.11.22 - [๐ฑBackend/Spring Security] - Spring Security #1 ๊ธฐ๋ณธ๋์ ์ดํดํ๊ธฐ
๐ ์ธ์ ๊ณ ์ ๋ณดํธ (์ธ์ ์ฃผ์ ๊ณต๊ฒฉ, sessionFixation)
๋ณดํต ์น ์๋น์ค๋ ๋ก๊ทธ์์ ํ ๋ฐ๋ก ๋ค์ ๋ก๊ทธ์ธ์ ํ๋๋ผ๋ ๊ธฐ์กด์ JSESSIONID๋ฅผ ์ฌ์ฌ์ฉํ์ง ์๊ณ ์๋ก ๋ฐ๊ธํ๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ๋ ๋ง์ฐฌ๊ฐ์ง๋ก ์ธ์ ์ ๊ธฐ์กด์ ์ธ์ฆ๊ฐ์ฒด๊ฐ ์๋๋ผ๋ ๋ก๊ทธ์ธ์ ์์ฒญํ๋ฉด JSESSIONID๋ ์๋ก ๋ฐ๊ธํ๋ค.
๊ธฐ์กด์ ๊ฐ์ ์ด์ฉํด๋ ๋ ๊ฑฐ๊ฐ์๋ฐ, ๋ฒ๊ฑฐ๋กญ๊ฒ ๋ก๊ทธ์ธ ์์ฒญ๋ง๋ค ์๋กญ๊ฒ ๋ฐ๊ธํ๋ ์ด์ ๋ ๋ฌด์์ผ๊น?
์ด๋ ์ธ์ ๊ณ ์ ๊ณต๊ฒฉ (SessionFixation)์ผ๋ก ์ธํ ์ธ์ ํ์ด์ฌํน(์ธ์ ํ์ทจ)๋ฅผ ๋ง๊ธฐ ์ํจ์ด๋ค.
๋ง์ฝ ์๋์ ๊ฐ์ด ๊ณต๊ฒฉ์๊ฐ ๋จผ์ ๋ก๊ทธ์ธ์ ํ ํ, ์ฌ์ฉ์ ์ปดํจํฐ์ JSESSIONID ์ฟ ํค๋ฅผ ์ฌ์ด๋์๋ค๊ณ ๊ฐ์ ํด๋ณด์.
<!-- script ๋ฅผ ์ด์ฉํ ์ธ์
์ฃผ์
-->
<script>document.cookie="jsessionid=ํจ์ ์ธ์
ID";</script>
<!-- meta ํ๊ทธ๋ฅผ ์ด์ฉํ ์ธ์
์ฃผ์
-->
<meta http-equiv="Set-Cookie" content="jsessionid=ํจ์ ์ธ์
ID">
์ด๋ฅผ ์ธ์ ๊ณ ์ ๊ณต๊ฒฉ์ด๋ผ๊ณ ํ๋ค.
๋น์ฐํ ์คํ๋ง ์ํ๋ฆฌํฐ์์๋ ์ธ์ ๊ณ ์ ๋ณดํธ(changeSessionId)๊ฐ ๊ธฐ๋ณธ์ ์ฉ๋์ด์์ผ๋ฉฐ, ์ด๋ ์ค์ ์์ ๋ณ๊ฒฝํ ์ ์๋ค.
๋ฌผ๋ก ์ด๋ฅผ ๋ณ๊ฒฝ ํ ์ผ์ ๊ฑฐ์ ์๋ค. ์ฌ์ฉํ์ง ์๋๋ผ๋ ์๊ณ ๋ ์๋๋ก ํ์.
๐ : ์๋ ์น์ JSESSIONID๋ฅผ ๋งค๋ฒ ์๋กญ๊ฒ ๋ฐ๊ธํด์ฃผ๋๊ฑด๊ฐ์?
๐ : ์ด...์๋ชจ๋ฅด๊ฒ ๋๋ฐ ๋๋ ค๋ณด๋๊น ๊ทธ๋ ๋๋ผ๊ตฌ์. ์๋ง ๊ทธ๋ ์ง ์์๊น์? (???)
๐ ์ธ์ ์ ์ฑ ๋ณ๊ฒฝ
์น ์๋น์ค์ ๋ฐ๋ผ ๋ฐ๋ก ์ฌ์ฉ์ ์ธ์ฆ์ด ํ์ ์๋ ๊ฒฝ์ฐ, ์ธ์ ์ด ํ์์์ ์๋ ์๋ค.
๋๋ JWTํ ํฐ ๋ฑ์ ์ฌ์ฉํ๋ฉด ์๋ฒ์ ์์ ์ธ์ ์ ์์ฑํ์ง ์๊ธฐ ๋๋ฌธ์ ์ธ์ ์ ๋ง๋ค ํ์๊ฐ ์๋ค.
์ด๋ด ๋๋ ์คํ๋ง ์ํ๋ฆฌํฐ์ ์ธ์ ์ ์ฑ (sessionManagement().SessionCreationPolicy)์ ํตํด ์ฝ๊ฒ ์ค์ ํ ์ ์๋ค.
๐ ์ ๋ฆฌ (SessionManagementFilter)
- ๊ธฐ๋ณธ์ ์ธ ์ธ์ ๊ด๋ฆฌ
- ๋์์ ์ธ์ ์ ์ด
- ์ธ์ ๊ณ ์ ๋ณดํธ (sessionFixation)
- ์ธ์ ์์ฑ ์ ์ฑ (Always, If_Required, Never, Stateless)
์ฐธ๊ณ ๋ก ์ด ๊ณผ์ ์์๋ SessionManagementFilter์ ํจ๊ป ConcurrentSessionFilter๋ ์ฌ์ฉ๋๋ค.
๐ ConcurrentSessionFilter, ์ ์ฒด ๋์ํ๋ฆ
ConcurrentSessionFilter๋ ๋งค ์์ฒญ๋ง๋ค ํ์ฌ ์ฌ์ฉ์์ ์ธ์ ๋ง๋ฃ ์ํ๋ฅผ ํ์ธํ๋ฉฐ, ๋ง๋ฃ์ธ ๊ฒฝ์ฐ ์ฆ์ ์ฒ๋ฆฌ๋ฅผ ํ๋ค.
- ๋ก๊ทธ์์์ ํ์ํ ์ฒ๋ฆฌ
- ์ค๋ฅํ์ด์ง ์๋ต (ํํฐ์ฒด์ธ์ผ๋ก ๋๊ธฐ์ง ์๊ณ ConcurrentSessionFilter ์์ ์ฆ์ ์๋ต)
๊ทธ๋์ ์์์ ๋ฐฐ์ ๋ UsernamePasswordAuthenticationFilter, SessionManagementFilter์ ํจ๊ป ๋ณด๋ฉด ์๋ ๊ทธ๋ฆผ๊ณผ ๊ฐ๋ค.
๐ญ ์ธ๊ฐ API
์ธ์ฆ(Authentication)์ '๋ด๊ฐ ๋๊ตฌ์ธ์ง' ํ์ธํ๋ ์์ , ๋ก๊ทธ์ธ์ ์๋ฏธํ๋ค.
์ธ๊ฐ(Authorization)๋ '๋ด๊ฐ ๋๊ตฌ์ธ์ง๋ ์์ง๋ง, ๊ถํ์ด ์๋์ง ํ์ธํ๋' ์์ ์ด๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ธ๊ฐ, ์ฆ ์ ๊ทผ ๊ถํ ์ค์ ์ ์ ์ธ์ ๋ฐฉ์๊ณผ ๋์ ๋ฐฉ์์ผ๋ก ์ ๊ณตํด์ค๋ค.
๐ ์ฌ์ฉ์ ์์ฑ ๋ฐ ๊ถํ(Role) ๋ถ์ฌ
configure ๋ฉ์๋๋ฅผ ์ด์ฉํด์ ํ ์คํธ์ฉ ์ฌ์ฉ์๋ฅผ ์ฝ๊ฒ ๋ง๋ค ์ ์๋ค.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// ๋ฌผ๋ก ํ
์คํธ ์ฉ๋๋ก ์ฌ์ฉํ๋๊ฑฐ์ง, ์ค์ ์ฌ์ฉ์ ๋์ ์ผ๋ก DB์ ์ฐ๋ํด์ ๊ณ์ ์ ์์ฑํ๋ค.
auth.inMemoryAuthentication().withUser("user").password("{noop}1111").roles("USER");
auth.inMemoryAuthentication().withUser("system").password("{noop}1111").roles("SYS");
auth.inMemoryAuthentication().withUser("admin").password("{noop}1111").roles("ADMIN");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() // ์ธ๊ฐ, ๊ถํ์ด ํ์ํ ์์ฒญ URL
.antMatchers("/user").hasRole("USER") // user๋ง ์ ๊ทผ๊ฐ๋ฅ
.antMatchers("/admin/pay").hasRole("ADMIN") // admin๋ง ์ ๊ทผ๊ฐ๋ฅ
.antMatchers("/admin/**").access("hasRole('ADMIN') or hasRole('SYS')") // system,admin๋ง
.anyRequest().authenticated(); // ๊ทธ ์ธ ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ๋ง ํ์.
http.formLogin();
}
}
URL ์ค์ ์ ๊ตฌ์ฒด์ ์ธ ๊ฒฝ๋ก๋ฅผ ๋จผ์ ์์ฑํด์ผํ๋ค.
์ฐธ๊ณ ๋ก ํฌํจ๊ด๊ณ (System์ Admin๊ณผ User๊ถํ์ ํฌํจ)๋ผ๋ฉด ์๋์ ๊ฐ์ด ์์ฑํด์ฃผ์ด์ผํ๋ค.
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication().withUser("user").password("{noop}1111").roles("USER");
auth.inMemoryAuthentication().withUser("system").password("{noop}1111").roles("USER","SYS");
auth.inMemoryAuthentication().withUser("admin").password("{noop}1111").roles("ADMIN","SYS","USER");
}
๋ค๋ง ์คํ๋ง ์ํ๋ฆฌํฐ์๋ ๊ถํ ๊ณ์ธต ๊ธฐ๋ฅ์ด ์์ด์ ์ด๋ ๊ฒ ์ผ์ผ์ด ์ง์ ํ์ง ์์๋ ํฌํจ๊ด๊ณ๋ฅผ ์ฝ๊ฒ ์ค์ ํ ์ ์๋ค.
๐ URL ๊ถํ ์ค์ (์ธ๊ฐ)
์ฐธ๊ณ ๋ก ๊ถํ ์ค์ ์ ํ๋ ๋ฉ์๋๋ ์๋์ ๊ฐ๋ค.
๋ฉ์๋ | ๋์ |
authenticated() | ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๊ทผ์ ํ์ฉ |
fullyAuthenticated() | ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๊ทผ์ ํ์ฉ, rememberMe ์ธ์ฆ ์ ์ธ |
permitAll() | ๋ฌด์กฐ๊ฑด ์ ๊ทผ์ ํ์ฉ (๋ชจ๋๊ฐ ์ ๊ทผ ๊ฐ๋ฅ) |
denyAll() | ๋ฌด์กฐ๊ฑด ์ ๊ทผ์ ํ์ฉํ์ง ์์ |
anonymous() | ์ต๋ช ์ฌ์ฉ์์ ์ ๊ทผ์ ํ์ฉ (* ์ฃผ์ * ์ธ์ฆ๋ USER๋ ์ ๊ทผํ ์ ์์) |
rememberMe() | ๊ธฐ์ตํ๊ธฐ๋ฅผ ํตํด ์ธ์ฆ๋ ์ฌ์ฉ์์ ์ ๊ทผ์ ํ์ฉ |
access(String) | ์ฃผ์ด์ง SpEL (Spring Exp Lang) ํํ์์ ํ๊ฐ ๊ฒฐ๊ณผ๊ฐ true์ด๋ฉด ์ ๊ทผ์ ํ์ฉ |
hasRole(String) | ์ฌ์ฉ์๊ฐ ์ฃผ์ด์ง ์ญํ ์ด ์๋ค๋ฉด ์ ๊ทผ์ ํ์ฉ (User) |
hasAuthority(String) | ์ฌ์ฉ์๊ฐ ์ฃผ์ด์ง ๊ถํ์ด ์๋ค๋ฉด (Role User) |
hasAnyRole(String...) | ์ฌ์ฉ์๊ฐ ์ฃผ์ด์ง ๊ถํ์ด ์๋ค๋ฉด ์ ๊ทผ์ ํ์ฉ |
hasAnyAuthority(String...) | ์ฌ์ฉ์๊ฐ ์ฃผ์ด์ง ๊ถํ ์ค ์ด๋ค ๊ฒ์ด๋ผ๋ ์๋ค๋ฉด ์ ๊ทผ์ ํ์ฉ |
hasIpAddress(String) | ์ฃผ์ด์ง IP๋ก๋ถํฐ ์์ฒญ์ด ์๋ค๋ฉด ์ ๊ทผ์ ํ์ฉ |
๐ญ ์ธ์ฆ,์ธ๊ฐ ์์ธ์ฒ๋ฆฌ - ExceptionTranslationFilter
์ฐธ๊ณ ๋ก ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋ง์ง๋ง ํํฐ ์ฒด์ธ์ ํญ์ FilterSecurityInterceptor ํํฐ๋ก ๋๋๋ค.
์ด ํํฐ ์์ ์์นํ๋๊ฒ ExceptionTranslationFilter์ธ๋ฐ, ์ฝ๋๋ฅผ ์ดํด๋ณด๋ฉด ์ฌ์ฉ์ ์์ฒญ์ ๋ฐ์ ๋
try-catch๋ก FilterSecurityInterceptor๋ฅผ ๊ฐ์ธ๊ณ ์๋ค. ์ฆ ์์ธ์ฒ๋ฆฌ๋ฅผ ์ฌ๊ธฐ์ ๋ด๋นํ๋ค.
- AuthenticationException
1. AuthenticationEntryPoint๋ฅผ ํธ์ถํ๋ค. (๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋, 401 ์ค๋ฅ์ฝ๋ ์ ๋ฌ ๋ฑ์ ๋ด๋น)
2. ์ธ์ฆ ์์ธ๊ฐ ๋ฐ์ํ๊ธฐ ์ ์ ์์ฒญ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค.
โก RequestCache - ์ฌ์ฉ์์ ์ด์ ์์ฒญ์ ๋ณด๋ฅผ ์ธ์ ์ ์ ์ฅํ๊ณ , ์ด๋ฅผ ๊บผ๋ด์ค๋ ์บ์ ๋ฉ์ปค๋์ฆ
โก RequestCache ๋ด๋ถ์ SavedRequest ๊ฐ์ฒด - ์ฌ์ฉ์๊ฐ ์์ฒญํ๋ request ์์ฑ, ํค๋๋ค์ ์ ์ฅ. - AccessDeniedException
์ธ๊ฐ์ ๊ด๋ จ๋ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ค.
ํน๋ณํ ๋์์ ์๊ณ AccessDeniedHandler ์์ ์์ธ์ฒ๋ฆฌ๋ฅผ ํ๋๋ก ์ ๊ณตํ๋ค.
RequestCache๋ ์๋์ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
protected void configure(HttpSecurity http) throws Exception {
http.exceptionHandling() // ์์ธ์ฒ๋ฆฌ ํธ๋ค๋ฌ ๋ฑ๋ก (๋๋คํจ์ ์ฌ์ฉ)
.authenticationEntryPoint(
(request, response, authException) -> response.sendRedirect("/login"))
.accessDeniedHandler(
(request, response, accessDeniedException) -> response.sendRedirect("/denied"));
http.formLogin()
.successHandler((request, response, authentication) -> {
RequestCache requestCache = new HttpSessionRequestCache();
// ์ธ์ฆ ์์ธ๊ฐ ๋ฐ์ํ๊ธฐ ์ ์บ์๋ Request ๊ฐ์ฒด
SavedRequest savedRequest = requestCache.getRequest(request, response);
String redirectUrl = savedRequest.getRedirectUrl(); // ๊ธฐ์กด์ ๊ฐ๊ณ ์ ํ๋ URL
response.sendRedirect(redirectUrl);
});
}
'๐ฑ Spring Framework > Spring Security' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Security #3 ์ํคํ ์ฒ ์ดํดํ๊ธฐ, ์ธ์ฆ ๋ฐ ์ธ๊ฐ ์๋ฆฌ (0) | 2021.11.27 |
---|---|
Spring Security #1 ๊ธฐ๋ณธ๋์ (0) | 2021.11.22 |
Spring Security๋ ๋ฌด์์ผ๊น (1) | 2021.09.23 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev