Spring Security #1 ๊ธฐ๋ณธ๋์
by JiwonDev
๐ญ Spring Security
์คํ๋ง ๊ธฐ๋ฐ์ ์ฑ์์ ๋ณด์(์ธ์ฆ, ๊ถํ, ์ธ๊ฐ)๋ฑ์ ๋ด๋นํ๊ธฐ ์ํด ๋ง๋ค์ด์ง ํ์ ํ๋ ์์ํฌ์ด๋ค.
Spring Security๋ ๋ณด์์ ๋ด๋นํ๋ ํ๋ ์์ํฌ์ด๋ค. ๊ทธ๋์ ์คํ๋ง ์ฑ๊ณผ์ ์์กด์ฑ์ ๋ถ๋ฆฌ์ํค๊ธฐ ์ํด ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ธ๋ฆฟ ํํฐ๊ธฐ๋ฐ์ผ๋ก ๋์ํ๊ฒ๋ ๋ง๋ค์ด์ ธ์๋ค. ์ฆ ์คํ๋ง์ ์ฌ์ฉํ์ง ์๋๋ผ๋ ์๋ธ๋ฆฟ์ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด ๋ฐ๋ก ์ ์ฉ์ด ๊ฐ๋ฅํ๋ค.
Spring Security๊ฐ ์๋ค๊ณ ์คํ๋ง ์น์ฑ์ ๋ณด์์ ๋ง๋ค ์ ์๋๊ฑด ์๋๋ค. ํ์ง๋ง ๋ณด์๊ณผ ๊ด๋ จํด์ ์ฒด๊ณ์ ์ผ๋ก ๋ง์ ์ต์ ์ ์ ๊ณตํด์ฃผ๊ธฐ ๋๋ฌธ์, ๊ฐ๋ฐ์ ์ ์ฅ์์๋ ์ผ์ผ์ด ๋ณด์ ๊ด๋ จ ๋ก์ง์ ๊ณ ์ํด์ ์์ฑํ์ง ์์๋ ์ฝ๊ฒ ๋ณด์์ด ๊ฐ๋ฅํ๋ค.
๐ญ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ
maven์ด๋ gradle์ ์ฌ์ฉํ๋ค๋ฉด ๋ณต์กํ ์ค์น๊ณผ์ ์์ด ์๋ ํ์ค์ด๋ฉด ์ ์ฉ ๊ฐ๋ฅํ๋ค.
ํด๋น ์์กด์ฑ์ ์ถ๊ฐํ๊ฒ๋๋ฉด ์๋ฒ (ex ์คํ๋ง๋ถํธ์ ๋ด์ฅํฐ์บฃ)์ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ด๋ จ๋ ์ด๊ธฐํ ์์ ๋ฐ ๋ณด์ ์ค์ ์ด ์ด๋ฃจ์ด์ง๋ค. ์ฆ ๋ณ๋์ ์ค์ ์ ํ์ง ์์๋ ๊ธฐ๋ณธ์ ์ธ ์น ๋ณด์ ๊ธฐ๋ฅ์ด ์ ์ฉ๋๋ค๊ณ ์ดํดํ๋ฉด ๋๋ค.
๊ธฐ๋ณธ์ ์ธ ์น ๋ณด์ ๊ธฐ๋ฅ์ด๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ์ธ์ฆ ๋ฐฉ์์ผ๋ก ํผ ๋ก๊ทธ์ธ ๋ฐฉ์ / httpBasic ๋ก๊ทธ์ธ ๋ฐฉ์์ ์ ๊ณตํ๋ค.
- ๋ชจ๋ ์์ฒญ(Request)์ ์ธ์ฆ์ด ๋์ด์ผ ์์์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
- ๊ฐ๋ฐ์ฉ์ผ๋ก ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํ์ด์ง์ ๊ธฐ๋ณธ ๊ณ์ ์ ํ๊ฐ๋ฅผ ์ ๊ณตํด์ค๋ค.
์ฐธ๊ณ ๋ก ๊ฐ๋ฐ์ฉ ๊ธฐ๋ณธ ๊ณ์ ์ application.properties์์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ๋ค.
spring.security.user.name=user
spring.security.user.password=1234โ
- pom.xml (maven)
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- build.gradle (gradle)
implementation 'org.springframework.boot:spring-boot-starter-security'
๐ญ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ธฐ๋ณธ ๊ตฌ์กฐ
์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ฐ์ข ์ค์ ์ HttpSecurity๋ก ๋๋ถ๋ถ ํ๊ฒ ๋๋ค.
- ๋ฆฌ์์ค(URL) ์ ๊ทผ ๊ถํ
- ์ค์ ์ธ์ฆ ์ ์ฒด ํ๋ฆ์ ํ์ํ Login, Logout ํ์ด์ง ์ธ์ฆ์๋ฃ ํ ํ์ด์ง ์ธ์ฆ ์คํจ ์ ์ด๋ํ์ด์ง ๋ฑ
- ์ค์ ์ธ์ฆ ๋ก์ง์ ์ปค์คํ ํ๊ธฐ์ํ ์ปค์คํ ํํฐ
- ์ค์ ๊ธฐํ csrf, ๊ฐ์ https ํธ์ถ ๋ฑ๋ฑ ๊ฑฐ์ ๋ชจ๋ ์คํ๋ง์ํ๋ฆฌํฐ์ ์ค์
HttpSecurity ๋ WebSecurityConfigurer ๋ฅผ ํตํด ๋ง๋ค ์ ์๋ค.
์ฆ, ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด์๋ WebSecurityConfigurer ๊ฐ์ฒด๋ฅผ ๋ง๋ค์ด์ ๋น์ผ๋ก ๋ฑ๋กํด์ผํ๋ค.
์ง์ ๋ง๋ค๊ธฐ๋ ๋ฒ๊ฑฐ๋ก์ฐ๋ฏ๋ก, ๋ณดํต์ ์ด๋ฅผ ๊ตฌํํ ์ถ์ํด๋์ค์ธ WebSecurityConfigurerAdapter ๋ฅผ ์ด์ฉํ๋ค.
์คํ๋ง์ ์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฝ๊ฒ ์ฌ์ฉํ ์ ์๋๋ก, @EnableWebSecurity๋ผ๋ ์ ๋ ธํ ์ด์ ์ ์ ๊ณตํ๋ค.
๊ทธ๋ฅ ์ ๋ ธํ ์ด์ ๋ง ๋ถ์ธ๋ค๊ณ ๋์ํ๋ ๊ฑด ์๋๊ณ , Spring ์ํ๋ฆฌํฐ ๋ชจ๋ ์์กด์ฑ์ ์กด์ฌํ๋ ์ํ์์ WebSecurityConfigurerAdapter ํด๋์ค ์์๋ฐ์ผ๋ฉด ์ฑ ์คํ ์์ ์์ ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋๋ค.
@EnableWebSecurity // @Configuration ์ ํฌํจํ๊ณ ์๋ค. ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋จ.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest().authenticated(); // ์ด๋ค ์์ฒญ์๋ ๋ณด์์ ๋ฐ๋๋ก ์ธ๊ฐ์ ์ฑ
์ค์ .
http.formLogin(); // form-login ์ธ์ฆ ์ ์ฑ
}
}
SecurityConfig ์์
@Configuration // ์๋ตํด๋ ์๊ด์์ง๋ง, ๋ช
์์ ์ผ๋ก ํ์ํด์ฃผ์
@EnableWebSecurity
public class SecuriyConfig extends WebSecurityConfigurerAdapter{
/*
- Spring Security๋ฅผ ๋ฌด์ํ url์ ์ ์ธ
- ๋ณดํต ์ ์ ์์์ด ์ ์ฅ๋ ๊ฒฝ๋ก๋ฅผ ๋ช
์
*/
@Override
public void configure(WebSecurity web) throws Exception {
web,ignoring().antMatchers("/webjars/**");
}
// - Spring Security ์ ์ฉ ๊ท์ฝ ๋ช
์
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/**").permitAll() //๋ช
์๋ ๊ฒฝ๋ก์ ๋ํด์๋ ๋ชจ๋ ์ ๊ทผ ๊ฐ๋ฅ
.anyRequest().authenticated();
http.formLogin()
.loginPage("/loginPage") //๋ช
์๋ ๊ฒฝ๋ก๋ฅผ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ฌ์ฉ
.loginProcessingUrl("/login") //๋ช
์๋ ๊ฒฝ๋ก๋ฅผ ๋ก๊ทธ์ธ url๋ก ์ฌ์ฉ
.usernameParameter("usrId") //ํ๋ผ๋ฏธํฐ์ ๋์ผํ name์ ๊ฐ์ง๋ input ํ๊ทธ๋ก๋ถํฐ ์์ด๋๊ฐ์ ๋ฐ์์ด
.passwordParameter("usrPw") //ํ๋ผ๋ฏธํฐ์ ๋์ผํ name์ ๊ฐ์ง๋ input ํ๊ทธ๋ก๋ถํฐ ๋น๋ฐ๋ฒํธ๊ฐ์ ๋ฐ์์ด
.successHandler(successHandler()).failureHandler(failureHandler());
http.logout()
.logoutUrl("/logout") //๋ช
์๋ ๊ฒฝ๋ก๋ฅผ ๋ก๊ทธ์์ url๋ก ์ฌ์ฉ
.logoutSuccessUrl("/") //๋ก๊ทธ์์ ์ฑ๊ณต์ ๋ฆฌ๋๋ ์
๋ url์ ์ง์
.invalidateHttpSession(true) //์ธ์ฆ ๊ด๋ จ ์ธ์
์ ๋ณด ์ญ์
.deleteCookies("JSESSIONID"); //์ฟ ํค ์ญ์
http.exceptionHandling()
.accessDeniedPage("/");
}
// - ์ฌ์ฉ์ ์ธ์ฆ ๋ฐฉ๋ฒ ๋ช
์(์ฌ๊ธฐ์๋ ์ธ๋ฉ๋ชจ๋ฆฌ๋ฐฉ์์ ์ฌ์ฉ)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.inMemoryAuthentication()
.passwordEncoder(encoder())
.withUser("admin")
,password("admin")
.authroties("USER","ADMIN");
}
@Bean
public BCryptPasswordEncoder encoder(){
return new BCryptPasswordEncoder();
}
// Sucess, Failure Handler ๊ตฌํํ์ฌ Bean์ผ๋ก ๋ฑ๋ก
@Bean
public AuthenticationSuccessHandler successHandler(){
return new AuthSuccessHandler("/");
}
@Bean
public AuthenticationFailureHandler failureHandler(){
return new AuthFailureHandler();
}
}
์ด์ฒ๋ผ ์ด๋ํฐ๋ฅผ ์ด์ฉํ๊ฒ๋๋ฉด ๋ด๋ถ์ configure() ๋ฉ์๋๋ง ์ ์ ํ๊ฒ ๋ฐ๊ฟ์ ํธํ๊ฒ ์ฌ์ฉ๊ฐ๋ฅํ๋ค.
- configure(WebSecurity web) : WebSecurity๋ฅผ ์ค์ ํ ์ ์๋ค. ํน์ ์์ฒญ์ ๋ฌด์ํ๊ฑฐ๋ ํ ์ ์๋ค.
- configure(HttpSecurity http) : HttpSecurity๋ฅผ ์ค์ ํ ์ ์๋ค. ์ ๊ทผ๊ถํ, ๋ก๊ทธ์ธ ๋ฑ๋ฑ ์์ฒญ ๊ฒฝ๋ก ์ง์ ๊ฐ๋ฅ
- configure(AuthenticationManagerBuilder) : ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด๋ฅผ ๊ตฌ์ฑํ๋ค. in-memory ๊ธฐ๋ฐ ์ ์ ๋ฅผ ์์ฑํ ์ ์๋ค.
๋ฌผ๋ก ์คํ๋ง๋ถํธ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ค๋ฉด apllication.yml์์๋ ์ค์ ์ด ๊ฐ๋ฅํ์ง๋ง, ๋ชจ๋ ์ค์ ์ yml์์ ์ฒ๋ฆฌํ ์ ์๊ธฐ์ ๋ณดํต ์์ ์ฝ๋์ฒ๋ผ Configuration Class๋ฅผ ๋ง๋ค์ด์ ํจ๊ป ์ฌ์ฉํ๋ค.
๐ ์ด? ๊ทธ๋ฐ ์ค์ ์์ด๋ ์คํ๋ง ์ํ๋ฆฌํฐ๋ ๋์ํ๋๋ฐ์?
๊ทธ๊ฑด ์ฐ๋ฆฌ๊ฐ ์คํ๋ง ๋ถํธ๋ฅผ ์ฌ์ฉํด์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์๊ธฐ ๋๋ฌธ์ด๋ค.
implementation 'org.springframework.boot:spring-boot-starter-security'
์คํ๋ง๋ถํธ๋ SecurityAutoConfiguration ํด๋์ค๋ฅผ ์ค์ ํ์ผ๋ก ์ฐธ๊ณ ํ๋๋ฐ,
ํด๋น ๊ฐ์ฒด๋ DefaultAuthenticationEventPublisher๋ฅผ @Bean์ผ๋ก ๋ฑ๋กํ๋ค.
@Configuration
@ConditionalOnClass(DefaultAuthenticationEventPublisher.class)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({SpringBootWebSecurityConfiguration.class, WebSecurityEnablerConfiguration.class,
SecurityDataConfiguration.class})
public class SecurityAutoConfiguration {
@Bean
@ConditionalOnMissingBean(AuthenticationEventPublisher.class)
public DefaultAuthenticationEventPublisher authenticationEventPublisher(
ApplicationEventPublisher publisher) {
return new DefaultAuthenticationEventPublisher(publisher);
}
}
์ด๋ ๊ฒ ๋ฑ๋ก๋ publisher๋ ์๋์ ๊ฐ์ ๊ธฐ๋ณธ ์ด๋ฒคํธ๋ฅผ ๋งคํํด์, ์ธ์ฆ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํจ๋ค.
- BadCredentialsException
- UsernameNotFoundException
- AccountExpiredException ๋ฑ๋ฑ..
ํ์ง๋ง ์คํ๋ง ๋ถํธ๋ฅผ ์ฌ์ฉํ๋ค๊ณ ํด๋, ๊ฒฐ๊ตญ ๋์์๋ฆฌ๋ ๊ฐ๋ค. ์คํ๋ง ๋ถํธ๊ฐ ์ ๊ณตํ๋ ์ํ๋ฆฌํฐ ์ค์ ๊ฐ์ฒด๋ฅผ ๋ณด๋ฉด ์์์ ์ค๋ช ํ ๊ฒ ์ฒ๋ผ WebSecurityConfigurerAdapter๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉํ๋ค.
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnMissingBean(WebSecurityConfigurerAdapter.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
public class SpringBootWebSecurityConfiguration {
@Configuration
@Order(SecurityProperties.BASIC_AUTH_ORDER)
static class DefaultConfigurerAdapter extends WebSecurityConfigurerAdapter {
}
}
์ฐธ๊ณ ๋ก ์ฑ์ด ์คํ๋ ๋ ์๊ธฐ๋ ๊ธฐ๋ณธ user ๊ณ์ ์ UserDetailsService๋ฅผ ์์๋ฐ์์ ์ฌ์ ์ํ๋ฉด ๋ง๋ค์ด์ง์ง ์๋๋ค.
๊ถ๊ธํ๋ฉด UserDetailsServiceAutoConfiguration ๊ฐ์ฒด๋ฅผ ๊ตฌ๊ธ์ ๊ฒ์ํด์ ์ฐพ์๋ณด์.
๐ญ ์ํ๋ฆฌํฐ ์ฌ์ฉํด๋ณด๊ธฐ - ์ธ์ ๊ธฐ๋ฐ ์น ๋ก๊ทธ์ธ
์คํ๋ง ์ํ๋ฆฌํฐ๋ฅผ ์ฒ์ ์ ์ฉํ๋ฉด, ์๋์ฒ๋ผ ์ ํต์ ์ธ ์ธ์ ์น ๋ก๊ทธ์ธ ๋ฐฉ์์ form ์ธ์ฆ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํด์ค๋ค.
์ด๋ ์๋์ ๊ฐ์ด configure ๋ฉ์๋์์์, http.formLogin() ์ค์ ์ ํตํด ์ฝ๊ฒ ์ปค์คํ ์ด ๊ฐ๋ฅํ๋ค.
@EnableWebSecurity // @Configuration ์ ํฌํจํ๊ณ ์๋ค. ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋จ.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated()
.and() // ์ด๋ ๊ฒ and()๋ฅผ ์ด์ฉํด์ ์ธ์ฆ, ์ธ๊ฐ๋ฅผ ํ๋ฒ์ ์ค์ ํ ์๋ ์๋ค.
.formLogin()
.loginPage("/loginPage") // ๋ด๊ฐ ๋ง๋ ๋ก๊ทธ์ธ ํ์ด์ง
.defaultSuccessUrl("/") // ๋ก๊ทธ์ธ ์ฑ๊ณต์ ์ด๋ ๊ฒฝ๋ก
.failureUrl("/login.html?error=true") // ๋ก๊ทธ์ธ ์คํจ์ ์ด๋ ๊ฒฝ๋ก
.loginProcessingUrl("/login_proc") // form ํ๊ทธ์ Action URL. ๊ธฐ๋ณธ๊ฐ์ "/login"
.usernameParameter("userId")
.passwordParameter("passwd")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println("์ธ์ฆ์ฑ๊ณต(authentication) : " + authentication.getName());
response.sendRedirect("/");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
System.out.println("์ธ์ฆ์คํจ(exception) : "+exception.getMessage());
response.sendRedirect("/login");
}
})
.permitAll() // loginForm ์ ์ ํ์ฉ. ์ด๊ฒ ์์ผ๋ฉด loginPage๋ ์ธ์ฆ์์ด๋ ์์ฒญํ์ง ๋ชปํ๋ค.
;
}
}
์ฐธ๊ณ ๋ก Login HTML ํผ์ ์ค์ ๊ฐ๊ณผ ๋ค๋ฅด๊ฒ ์์ฑํ๋ค๋ฉด Bad crednetials ๋ฑ์ ์์ธ๋ฅผ ๋ฐ์์ํจ๋ค.
๋์ค์ ์ธ๊ธํ ๊ฑฐ์ง๋ง, ์คํ๋ง ์ํ๋ฆฌํฐ (@EnableWebSecurity)๋ ๊ธฐ๋ณธ์ ์ผ๋ก CSRF ๊ณต๊ฒฉ๋ฐฉ์ด๋ฑ์ ๋ค์ํ ๋ณด์ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ค. ์ด๋ฐ๊ฒ๋ค๋ configure ์์ http ๊ฐ์ฒด๋ฅผ ํตํด ์ฝ๊ฒ ์ค์ ์ ๋ฐ๊ฟ ์ ์๋ค.
CSRF (Cross-Site Request Forgery)๊ฐ ๋ญ๋ฐ์?
์ฆ ์ฌ์ฉ์์ ์ธ์ฆ ์ดํ, ์คํฌ๋ฆฝํธ๋ ํผ ํ๊ทธ๋ฅผ ํตํด ํน์ ์น์ฌ์ดํธ์ ๋ณธ์ธ์ด ์๋ํ์ง ์์ ์์ฒญ์ ํ๋๋ก ๋ง๋๋ ๊ณต๊ฒฉ์ด๋ค. ์ค์ ํ๊ตญ์์๋ 2008๋ ์ฅ์ ์ ๊ฐ์ธ์ ๋ณด ์ ์ถ์ฌ๊ฑด์ CSRF ๊ณต๊ฒฉ์ ์ด์ฉํด ๊ด๋ฆฌ์๊ณ์ ์ ํ์ทจํ์๋ค.
์ฝ๊ฒ๋งํ๋ฉด ์ธ์ฆ๋ ์ฌ์ฉ์์๊ฒ ์๋์ ๊ฐ์ HTML ์ฝ๋๋ฅผ ์ด์ฉํด ํด์ปค๊ฐ ์ํ๋ ์๋ฒ ์์ฒญ์ ๋ฐ์์ํค๋ ๊ฒ์ด๋ค.
<!-- ์ฌ์ฉ์๊ฐ ํด๋น ๋งํฌ๋ฅผ ํด๋ฆญํ๋ฉด ์์ฒญ์ด ๋ฐ์๋จ -->
<a href="http://bank.com/transfer?accountNumber=5678&amount=10000">
Pictures@
</a>
<!-- ํ์ด์ง ๋ก๋์ ์ด๋ฏธ์ง๋ฅผ ๋ถ๋ฌ์ค๊ธฐ์ํด ํด๋น ๋งํฌ๋ฅผ ์ฌ์ฉ์ ๊ณ์ ์ผ๋ก ์์ฒญํ๊ฒ๋จ -->
<img src="http://bank.com/transfer?accountNumber=5678&amount=10000"/>
๋ฌผ๋ก 1990๋
๋ ์น๋ ์๋๊ณ , ์์ ๊ฐ์ Get ์์ฒญ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋ ์ผ์ ์๋ค.
ํ์ง๋ง ์๋์ ๊ฐ์ด Post ์์ฒญ๋ HTML form์ ์ด์ฉํ๋ ๊ฒฝ์ฐ ์ธ๋ผ์ธ ์คํฌ๋ฆฝํธ๋ฅผ ์ด์ฉํด์ ์์ฒญ์ ํ ์ ์๋ค.
<!-- Post๋ฅผ ์ด์ฉํ๋ Form ํ๊ทธ๋ ์๋์ ๊ฐ์ด HTML ์ธ๋ผ์ธ ์๋ฐ์คํฌ๋ฆฝํธ๋ฅผ ํตํด ๊ณต๊ฒฉ๊ฐ๋ฅ. -->
<body onload="document.forms[0].submit()">
<form action="http://bank.com/transfer" method="POST">
<input type="hidden" name="accountNumber" value="5678"/>
<input type="hidden" name="amount" value="10000"/>
<input type="submit" value="Pictures@"/>
</form>
...
์ด๋ฌํ CSRF๋ฅผ ๋ง์๋ ค๊ณ Http์ Referrer ํค๋๋ฅผ ๊ฒ์ฆํด์ ์์ฒญ์ด ๊ธฐ์กด domain url์ด๋ ๋์ผํ์ง ๊ฒ์ฆํ๊ธฐ๋ ํ๋ค. ์คํ๋ง ์ํ๋ฆฌํฐ๋ CSRF Token์ ๋ฐ๊ธํ์ฌ ํ ํฐ๊ณผ ํจ๊ป ์์ฒญ์ ๋ฐ์ ๊ณต๊ฒฉ์ ๋ฐฉ์งํ๋ค.
(* X-XSRF-TOKEN ๊ฐ์ด ์๋ค๋ฉด 403 Forbidden์ ๋ฐํํ๋๋ก ๊ธฐ๋ณธ์ผ๋ก ์ค์ ๋์ด์๋ค.)
๊ทธ๋ฌ๋ ์์ ์์ ์์๋ ๋ณด๋ค์ถ์ด, CSRF๋ ์๋ฒ ์ธก์์ HTML์ ์์ฑํ๋ ๊ตฌ์กฐ (Thymeleaf, JSP)์์๋ง ๋ฐ์ํ๋ ๋ฌธ์ ์ด๋ค.
REST API์ฒ๋ผ ๋งค๋ฒ HTML ํ์ด์ง๋ฅผ ์ฃผ๋๊ฒ ์๋๋ผ API๋ง ๋
ธ์ถํ๊ณ JSON์ผ๋ก ํต์ ํ๋ ๊ฒฝ์ฐ ์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ์ถ๊ฐํ ์ ์๊ธฐ ๋๋ฌธ์ CSRF๋ ์ ํ ๋ฌธ์ ๊ฐ ๋์ง ์๋๋ค. ์ฆ ์ถ๊ฐ์ ์ธ CSRF ๋ณด์์ ํ ํ์๊ฐ ์ ํ์๊ธฐ์ http.csrf.disable()๋ก ๋นํ์ฑํ ํด์ฃผ๋๊ฒ ์ข๋ค. โก permitAll()์ ํ๋๋ฐ 403 forbidden์ด ๋ฌ๋ค๋ฉด ์ด๋ฅผ ํ์ธํด๋ณด์.
๐ญ ์คํ๋ง ์ํ๋ฆฌํฐ์ ๋์๊ณผ์
๊ธฐ๋ณธ์ ์ผ๋ก ์๋ฐ ์๋ธ๋ฆฟ ํํฐ์ ๊ฒฝ์ฐ, FilterChain์ ๊ตฌ์ฑํ์ฌ ์๋ธ๋ฆฟ์ด ์คํ๋๊ธฐ ์ ํํฐ๋ฅผ ๊ฑฐ์น๊ฒ ๋๋ค.
์คํ๋ง ์ํ๋ฆฌํฐ์ ๊ฒฝ์ฐ ์๋ธ๋ฆฟ ํํฐ์ฒด์ธ์ DelegatingFilterProxy๋ฅผ ๋ฑ๋กํ๋ค. ์ด๋ ์๋ธ๋ฆฟ ํํฐ์ ๊ตฌํ์ฒด์ด๋ค.
๊ทธ๋ฆฌ๊ณ ํด๋น ํํฐ๊ฐ SecurityFilterChain์ ํธ์ถํ๊ณ , ์คํ๋ง์ ๋ฑ๋ก๋ ๋น์ ๊ฐ์ ธ์์ ์์กด์ฑ์ ์ฃผ์ ํ๊ฒ ๋๋ค.
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
// Lazily get Filter that was registered as a Spring Bean
// For the example in DelegatingFilterProxy delegate is an instance of Bean Filter0
Filter delegate = getFilterBean(someBeanName);
// delegate work to the Spring Bean
delegate.doFilter(request, response);
}
DelegatingFilterProxy ๋ ์คํ๋ง ๋น์ด ์๋, ์๋ธ๋ฆฟ ํํฐ๋ผ์ ์ค์ ๋์์ FilterChainProxy์ ์์ํ๊ฒ๋๋ค.
- FilterChainProxy๋ ์์ฒญ๊ณผ ์๋ต์ ์คํ๋ง ์ํ๋ฆฌํฐ ํํฐ ์ฒด์ธ์ ์์ํ๋ ์ญํ ์ ๋ด๋นํ๋ค.
[์๋ธ๋ฆฟ โก ์คํ๋ง ์ํ๋ฆฌํฐ์ ์ง์ ์ ]์ด๋ฉฐ, ์๋ธ๋ฆฟ์์ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค๋ฉด ํด๋น ๊ฐ์ฒด์์ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
- ์ฐธ๊ณ ๋ก SecurityFilterChain์ ์ฌ๋ฌ ๊ฐ๋ก ๊ตฌ์ฑํ ์๋ ์๋ค.
์ฆ ์๋ ๊ทธ๋ฆผ์ฒ๋ผ DelegatingFilterProxy์์ URL์ ๋ฐ๋ผ ๋ค๋ฅธ ์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์๋ํ๋๋ก ๊ตฌ์ฑํ ์๋ ์๋ค.
์ํ๋ฆฌํฐ ํํฐ๋ SecurityFilterChain API๋ฅผ ํตํด FilterChainProxy์ ์ฝ์ ๋๊ณ , ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋๋ค.
์ฆ, ํํฐ๋ก ๋์์ ํ์ง๋ง '์ํ๋ฆฌํฐ ํํฐ' ์์ฒด๊ฐ ์๋ธ๋ฆฟ ํํฐ๋ก ๋ฐ๋ก ๋ฑ๋ก๋๋๊ฒ ์๋๋ผ๋ ๊ฒ์ ์ดํดํ๊ณ ์์.
์คํ๋ง ์ํ๋ฆฌํฐ๋ ๊ธฐ๋ณธ์ ์ผ๋ก ๋ง์ ํํฐ๋ฅผ ์ ๊ณตํด์ฃผ๋๋ฐ, ์ ์ฉ๋๋ ์์์ ์ข ๋ฅ๋ ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์.
์๊ณ ์์ผ๋ฉด ์ข๊ธดํ๋ฐ, ์๋ ์ข ๋ฅ๊ฐ ๋ง๊ธฐ๋ํ๊ณ ๋ชจ๋ฅธ๋ค๊ณ ํด์ ์ฌ์ฉํ๋๋ฐ ๋ฌธ์ ๊ฐ ์๊ธฐ์ง๋ ์๋๋ค.
์กฐ๊ธ ๋ ์์ธํ ๋์์๋ฆฌ๋ ํด๋น ๋ธ๋ก๊ทธ๋ฅผ ์ฐธ๊ณ ํ๋ฉด ์ดํดํ๋๋ฐ ๋์์ด ๋ ๊ฒ ๊ฐ๋ค.
๐ Login-Form์ ์ด๋ป๊ฒ ๋์ํ์๊น
Login-Form์๋ ์ด๋ฏธ ๋ง๋ค์ด์ ธ์๋ UsernamePasswordAuthenticationFilter๋ฅผ ์ฌ์ฉํ์ฌ ๋์ํ๋๋ฐ, ํด๋น ์ํ๋ฆฌํฐ ํํฐ์ ๋์๊ตฌ์กฐ๋ ์๋์ ๊ฐ๋ค.
์ด๋ ๊ฒ ์ธ์ฆ์ ์ฑ๊ณตํ๋ฉด, SecurityContext์ ์ธ์ฆ ๊ฐ์ฒด๊ฐ ์ ์ฅ๋๋ฉฐ, ๋์ค์ ์ธ์ฆ์ด ํ์ํ ๋ ์ด๋ฅผ ๊บผ๋ด ์ธ ์ ์๊ฒ๋๋ค.
์กฐ๊ธ ๋ ์์ธํ ํด๋์ค ๊ตฌ์กฐ(Spring Security Flow Communication Diagram)
๐ Logout์ ์ด๋ป๊ฒ ์ฒ๋ฆฌํ์ฃ ?
๋ก๊ทธ์์๋ LogoutFilter๋ฅผ ํตํด ๋์ํ๋ค.
์ฌ์ฉ๋ฒ์ ์๋์ ๊ฐ๋ค.
์ฐธ๊ณ ๋ก ์ต๋ช ํด๋์ค ๋์ ๋๋ค์์ ์ฌ์ฉํ๋ฉด ํจ์ฌ ๊ฐ๊ฒฐํ๊ฒ ์ฝ๋๋ฅผ ์์ฑํ ์ ์๋ค.
@EnableWebSecurity // @Configuration ์ ํฌํจํ๊ณ ์๋ค. ์คํ๋ง ๋น์ผ๋ก ๋ฑ๋ก๋จ.
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.anyRequest()
.authenticated();
http.formLogin()
.and()// ๋ฌผ๋ก http.logout()์ผ๋ก ๋ฐ๋ก ์์ฑํด๋ ๋จ.
.logout()
.logoutUrl("/logout") // ๋ก๊ทธ์์ ์์ฒญ URL
.logoutSuccessUrl("/login") // ๋ก๊ทธ์์ ์ฑ๊ณต ํ ์ด๋
.deleteCookies("JSESSIONID","remember-me") // ๋ก๊ทธ์์ ํ ์ธ์
์ฟ ํค ์ญ์
.addLogoutHandler(new LogoutHandler() {
// handler ๋ฅผ ์ง์ ์์ฑํด์ ์ถ๊ฐ์ ์ธ ๋ก๊ทธ์์ ์ฒ๋ฆฌ๊ฐ ๊ฐ๋ฅ.
@Override
public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
System.out.println("Logout ์ฒ๋ฆฌ ํธ๋ค๋ฌ");
HttpSession session = request.getSession();
session.invalidate();
}
})
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
System.out.println("๋ก๊ทธ์์ ์ดํ");
response.sendRedirect("/login");
}
});
}
}
๋์๊ณผ์ ์ ๋ก๊ทธ์ธ๊ณผ ๋น์ทํ๋ค.
๐ ๋ง์ง๋ง, Remember Me ๊ธฐ๋ฅ ์ฌ์ฉํ๊ธฐ
์ฐธ๊ณ ๋ก ์ด๋ ์ธ์ ์ด ๋ง๋ฃ๋๊ณ , ์น ๋ธ๋ผ์ฐ์ ๊ฐ ์ข ๋ฃ๋ ํ์๋ ์ฑ์ด ์ฌ์ฉ์๋ฅผ ๊ธฐ๋กํ๋ ๊ธฐ๋ฅ์ด๋ค.
์ฌ์ฉ์๊ฐ Remember-me ์ฟ ํค๋ฅผ ์์ฒญ์ ๋ณด๋ด๋ฉด, ์ ํจ์ฑ์ ๊ฒ์ฌํ๊ณ ๋ณ๋์ ์ธ์ฆ๊ณผ์ ์์ด ๋ก๊ทธ์ธ์ ์ ์งํด์ค๋ค.
Login๊ณผ Logout์ ๋ด์ ์ด๋ฏธ ์๊ฒ ์ง๋ง, ์ด๊ฒ๋ configure ๋ฉ์๋์ http๋ฅผ ์ด์ฉํด์ ์ฝ๊ฒ ์ฌ์ฉ๊ฐ๋ฅํ๋ค.
์ธ์ ์ฟ ํค๊ธฐ๋ฐ ๋ก๊ทธ์ธ์์, ํด๋ผ์ด์ธํธ๊ฐ ๋ก๊ทธ์ธ์ ์ฑ๊ณตํ๋ฉด JSessionID์ ํจ๊ป Remember-me ์ฟ ํค๋ฅผ ํจ๊ป ์ ๋ฌํ๋ค.
ํด๋น remeber-me ํ ํฐ์๋ [์ํธํ๋ UserID, Password]์ ๋ง๋ฃ์ผ์ด ์ ํ์๋ค.
1. ์น๋ธ๋ผ์ฐ์ ๋ฅผ ์ข ๋ฃํด์ JSESSIONID๊ฐ ์๊ฑฐ๋ ์๋ฒ์ ์ธ์ ์ด ๋ง๋ฃ๋์๋๋ผ๋
2. remember-me ํ ํฐ์ ์ฟ ํค๋ก ๋ณด๋ด๋ฉด
3. ํ์ฌ Security Session โก Security Context์ ์ผ์นํ๋ ํ ํฐ์ด ์๋์ง ๋น๊ตํ๊ณ , ์๋ค๋ฉด ์๋ก์ด ์ธ์ฆ๊ฐ์ฒด๋ฅผ ์์ฑํ๊ณ ์๋ก์ด JSESSIONID๋ฅผ ์๋ต์ ๋ณด๋ธ๋ค.
๋ฌผ๋ก [์๋ฒ์ ์ธ์ ๋ฉ๋ชจ๋ฆฌ or ์๋ฒ ํ ํฐ DB]์ ์ ํจํ ํ ํฐ์ด ์์ด์ผํ๋ค. ๊ทธ๊ฒ๋ ์๋ค๋ฉด ๋น์ฐํ ๋ก๊ทธ์ธ๋์ง ์๋๋ค.
๐ญ ์ ๋ฆฌ
์คํ๋ง ์ํ๋ฆฌํฐ๋ ์ด๋ค ์์ผ๋ก ๋์ํ๋์ง, ๋๋ต์ ์ธ ๊ฐ์ ์ก์๋ณด์๋ค.
๋ค์ ๊ธ์์๋ ์ธ์ ์ ์ดํํฐ, ์์ธ์ฒ๋ฆฌ ํํฐ๋ฑ์ ๋ํด์ ์์๋ณด๋๋ก ํ์.
'๐ฑ Spring Framework > Spring Security' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Spring Security #3 ์ํคํ ์ฒ ์ดํดํ๊ธฐ, ์ธ์ฆ ๋ฐ ์ธ๊ฐ ์๋ฆฌ (0) | 2021.11.27 |
---|---|
Spring Security #2 ๋ค์ํ ์ฌ์ฉ๋ฒ - ์ต๋ช ์ฌ์ฉ์, ์ธ์ ์ ์ด, ์์ธ์ฒ๋ฆฌ (0) | 2021.11.23 |
Spring Security๋ ๋ฌด์์ผ๊น (1) | 2021.09.23 |
๋ธ๋ก๊ทธ์ ์ ๋ณด
JiwonDev
JiwonDev