์๋ ํ์ธ์, ์ด๋ฒ ํฌ์คํ ์์๋ ์คํ๋ง๋ถํธ 3.1 ์ด์ ๋ฒ์ ๋ฐ Spring Security 6.1.5๋ฅผ ์ฌ์ฉํ๋ฉด์ ๊ฒฝํํ ๋์ ๊ณผ ํด๊ฒฐ ๊ณผ์ ์ ๊ณต์ ํฉ๋๋ค. ์ด ๊ธ์ ํนํ SecurityConfig ์ค์ ์ ์ด์ ์ ๋ง์ถ๊ณ ์์ผ๋ฉฐ, ๊ณต์ ๋ฌธ์์์ ๋ค๋ฃจ์ง ์๋ ์ธ๋ถ์ฌํญ๊ณผ ์์์น ๋ชปํ ์ค๋ฅ๋ค์ ๋ํด ๋ค๋ฃน๋๋ค.
ํ๋ก์ ํธ ๊ตฌ์ฑ ๋ฐ ์์กด์ฑ ์ถ๊ฐ
dependency ์ถ๊ฐ
- Maven project - pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
- Gradle project - build.gradle
implementation 'org.springframework.boot:spring-boot-starter-security'
์คํ๋ง ์ํ๋ฆฌํฐ ์์กด์ฑ ์ถ๊ฐ ์ ๋ฐ์ํ๋ ์ผ๋ค
์คํ๋ง ์ํ๋ฆฌํฐ๊ฐ ์ด๊ธฐํ๋๊ณ , ๊ธฐ๋ณธ ์น ๋ณด์ ๊ธฐ๋ฅ์ด ์์คํ
์ ํตํฉ๋ฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก, ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ์ด ํ์ํ๋ฉฐ, ํผ ๋ก๊ทธ์ธ๊ณผ httpBasic
๋ก๊ทธ์ธ ๋ฐฉ์์ด ์ ๊ณต๋ฉ๋๋ค. ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํ์ด์ง์ ๊ณ์ (user/๋๋ค๋ฌธ์์ด)๋ ์ ๊ณต๋ฉ๋๋ค.
- ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ์ด ๋์ด์ผ ์์์ ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
- ์ธ์ฆ ๋ฐฉ์์ ํผ ๋ก๊ทธ์ธ ๋ฐฉ์๊ณผ
httpBasic
๋ก๊ทธ์ธ ๋ฐฉ์์ ์ ๊ณตํ๋ค. - ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํ์ด์ง ์ ๊ณต (๋ก๊ทธ์ธ ํ์ด์ง ์ค์ ๋ฐ๋ก ์ํ๋ฉด ์ฌ๊ธฐ๋ก๋ง ์ด)
- ๊ธฐ๋ณธ ๊ณ์ ํ ๊ฐ ์ ๊ณต user/๋๋ค๋ฌธ์์ด๋๋ค๋ฌธ์์ด
- spring security๋ก ๋ง๋ค์ด์ง๋ ๊ณ์ ๋น๋ฐ๋ฒํธ
์ฐธ๊ณ : ๊ฐ๋ฐ๋์ค ๋งค๋ฒ ์์ฑ๋๋ ๋๋ค๋ฌธ์์ด/๊ณ์ ์ผ๋ก ๋ก๊ทธ์ธํ๋๊ฒ์ ๊น๋ค๋กญ๋ค. application.properties์ ๊ธฐ๋ณธ name/password์ค์ ์ด ๊ฐ๋ฅํ๋ค.
- (PasswordEncoder Bean ์ถ๊ฐํ๋ฉด ๊ทธ๊ฑฐ์๋ง์ถฐ์ ๋น๋ฐ๋ฒํธ ํํ๋ ๋ฐ๊ฟ์ค์ผํจ)
spring.security.user.name=user
spring.security.user.password=1234
๋ฌธ์ ์
- ๊ณ์ ์ถ๊ฐ, ๊ถํ ์ถ๊ฐ, DB ์ฐ๋ ๋ฑ
- ๊ธฐ๋ณธ์ ์ธ ๋ณด์ ๊ธฐ๋ฅ ์ธ์ ์์คํ ์์ ํ์๋ก ํ๋ ๋ ์ธ๋ถ์ ์ด๊ณ ์ถ๊ฐ์ ์ธ ๋ณด์๊ธฐ๋ฅ ํ์.
์ฌ์ฉ์ ์ ์ ๋ณด์ ๊ธฐ๋ฅ ๊ตฌํ
WebSecurityConfigurerAdapter
๋ฅผ ์ฌ์ฉํด HttpSecurity๋ฅผ ํตํ ์ธ๋ถ ๋ณด์ ๊ธฐ๋ฅ์ ์ค์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด, http.authorizeRequests()
, http.formLogin()
๋ฑ์ API๋ฅผ ํตํด ๋ณด์์ ๊ฐํํ ์ ์์ต๋๋ค.
HttpSecurity ๋ผ๋ ์ธ๋ถ์ ์ธ ๋ณด์๊ธฐ๋ฅ์ ์ค์ ํ ์ ์๋ API๋ฅผ ์ ๊ณตํ๋ ํด๋์ค๋ฅผ ์์ฑํ๋ค.
์ธ์ฆ API | ์ธ๊ฐ API |
---|---|
http.formLogin() |
http.authorizeRequests() |
http.logout() |
http.antMatchers("/admin") |
http.csrf() |
http.hasRole("USER") |
http.httpBasic() |
http.permitAll() |
http.sessionManagement() |
http.authenticated() |
http.rememberMe() |
http.fullyAuthenticated() |
http.exceptionHandling() |
http.access("hasRole('USER')") |
http.addFilter() |
http.denyAll() |
SecurityConfig ์ค์ ์์
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(request -> request
.requestMatchers(new AntPathRequestMatcher("/login")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/user/register")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/user/password")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/status")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/view/join")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/auth/join")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/home")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/dist/css/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/dist/js/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/dist/img/**")).permitAll()
.requestMatchers(new AntPathRequestMatcher("/plugins/**")).permitAll()
.anyRequest().authenticated() // ๊ทธ ์ธ ๋ชจ๋ ์์ฒญ์ ์ธ์ฆ ํ์
)
.formLogin(formLogin -> formLogin
.loginPage("/login")
.loginProcessingUrl("/login-process")
.usernameParameter("loginId")
.passwordParameter("password")
.defaultSuccessUrl("/", true)
.failureHandler(new CustomAuthenticationFailureHandler())
.permitAll()
)
.logout(Customizer.withDefaults());
return http.build();
}
@Bean // ์ด๊ฑธ ์ฃผ์ํ๋ฉด passwordEncoder๋ก ์ํธํ๊ฐ ๋์ง ์์ต๋๋ค.
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
@EnableWebSecurity
์ ๋ ธํ ์ด์ ์WebSecurityconfigurerAdapter
๋ฅผ ์์ํ๋ ์ค์ ๊ฐ์ฒด์ ๋ถํ์ฃผ๋ฉด SpringSecurityFilterChain์ ๋ฑ๋ก๋๋ค.
๐์ฐธ๊ณ : @EnableWebMvcSecurity
์คํ๋ง MVC
์์ ์น ๋ณด์์ ํ์ฑํํ๊ธฐ ์ํ ์ ๋ํ
์ด์
์ผ๋ก ํธ๋ค๋ฌ ๋ฉ์๋์์ @AuthenticationPrincipal
์ ๋
ธํ
์ด์
์ด ๋ถ์ ๋งค๊ฐ๋ณ์๋ฅผ ์ด์ฉํด ์ธ์ฆ์ฒ๋ฆฌ๋ฅผ ์ํํ๋ค. ๊ทธ๋ฆฌ๊ณ ์๋์ผ๋ก CSRF ํ ํฐ
์ ์คํ๋ง์ form binding tag library๋ฅผ ์ฌ์ฉํด ์ถ๊ฐํ๋ ๋น์ ์ค์ ํ๋ค.
์ค๋ฅํด๊ฒฐ
[Spring Security 6.1.5 ๋ฒ์ ์ค๋ฅ ๋ฌธ์ ํด๊ฒฐ
์๋ ํ์ธ์, Spring Security์ ๊ดํ ์ค๋์ ๋ธ๋ก๊ทธ ๊ธ์์๋ Spring Security ์ค์ ์ค ๋ฐ์ํ ์ ์๋ ์ผ๋ฐ์ ์ธ ์ค๋ฅ์ ์ด๋ฅผ ํด๊ฒฐํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ด์ผ๊ธฐํด๋ณด๋ ค ํฉ๋๋ค. ํนํ, AntPathRequestMatcher์ MvcReques
jakezo.tistory.com](https://jakezo.tistory.com/26)
Form Login ์ธ์ฆ
Login Flow
- Client์์ Get๋ฐฉ์์ผ๋ก Home Url์์์ ๊ทผ ์์ฒญ
- Server์์๋ ์ธ์ฆ๋ ์ฌ์ฉ์๋ง ์ ๊ทผ๊ฐ๋ฅํ๋ค๊ณ ํ๋จํด ์ธ์ฆ์ด ์๋๋ฉด ๋ก๊ทธ์ธ ํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- Client๋ ๋ก๊ทธ์ธํ์ด์ง์ username/password ์ ๋ ฅํ์ฌ Post๋ฐฉ์์ผ๋ก ์ธ์ฆ ์๋
- Server์์๋ Session ID์์ฑํ ์ธ์ฆ๊ฒฐ๊ณผ๋ฅผ ๋ด์ ์ธ์ฆ ํ ํฐ(Authentication) ์์ฑ ๋ฐ ์ ์ฅ
- Client์์ /home ์ ๊ทผ์์ฒญ ์ ์ธ์ ์ ์ ์ฅ๋ ์ธ์ฆ ํ ํฐ์ผ๋ก ์ ๊ทผ๋ฐ ์ธ์ฆ ์ ์ง
UsernamePasswordAuthenticationFilter
๋ก๊ทธ์ธ ์ธ์ฆ์ ์ฒ๋ฆฌํ๋ ํํฐ๋ก, ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์ฌ์ฉ์ ์ด๋ฆ๊ณผ ๋น๋ฐ๋ฒํธ๋ฅผ ์ธ์ฆ ๊ฐ์ฒด์ ์ ์ฅํ๊ณ , ์ธ์ฆ ๊ด๋ฆฌ์(AuthenticationManager)์๊ฒ ์ธ์ฆ์ ์์ฒญํฉ๋๋ค. ์ธ์ฆ ์ฑ๊ณต ์, SecurityContext์ ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ์ ์ฅํฉ๋๋ค.
Logout ์ฒ๋ฆฌ ๋ฐ LogoutFilter
- Flow Diagram
- Client์์ GET๋ฐฉ์์ /logout ๋ฆฌ์์ค ํธ์ถ
- Server์์
์ธ์ ๋ฌดํจํ
,์ธ์ฆํ ํฐ ์ญ์
,์ฟ ํค์ ๋ณด ์ญ์
ํ ๋ก๊ทธ์ธํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ
- Logout Flow Detail
- ์์ฒญ์ด Logout Url ์ธ์ง ํ์ธ
- ๋ง์ ๊ฒฝ์ฐ SecurityContext์์ ์ธ์ฆ๊ฐ์ฒด(Authentication)๊ฐ์ฒด๋ฅผ ๊บผ๋ด์ด
- SecurityContextLogoutHandler์์
์ธ์ ๋ฌดํจํ
,์ฟ ํค์ญ์
,clearContext()
๋ฅผํตํด SecurityContext๊ฐ์ฒด๋ฅผ ์ญ์ ํ๊ณ ์ธ์ฆ๊ฐ์ฒด๋ null๋ก ๋ง๋ ๋ค. - SimpleUrlLogoutSuccessHandler๋ฅผ ํตํด ๋ก๊ทธ์ธํ์ด์ง๋ก ๋ฆฌ๋ค์ด๋ ํธ ์ํจ๋ค.
๋ก๊ทธ์ธ ์คํจ์ ๊ตฌํ ์์