์ถ์ฒ : ๋ด์ผ๋ฐฐ์์บ ํ
์คํ๋ง ์ํ๋ฆฌํฐ ์ ์ฉ๋ฐฉ๋ฒ
// ์คํ๋ง ์ํ๋ฆฌํฐ
implementation 'org.springframework.boot:spring-boot-starter-security'
Config ํจํค์ง์ WebSecurityConfig ํด๋์ค ์์ฑ
//์คํ๋ง ๋ถํธ 2.7 ์ด์
package com.sparta.springsecurity.config;
import org.springframework.context.annotation.Bean;
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.SecurityFilterChain;
@Configuration
@EnableWebSecurity // ์คํ๋ง Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
public class WebSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF ์ค์
http.csrf().disable();
http.authorizeRequests().anyRequest().authenticated();
// ๋ก๊ทธ์ธ ์ฌ์ฉ
http.formLogin();
return http.build();
}
}
CSRF : ์ฌ์ดํธ ๊ฐ ์์ฒญ ์์กฐ, ๊ณต๊ฒฉ์๊ฐ ์ธ์ฆ๋ ๋ธ๋ผ์ฐ์ ์ ์ ์ฅ๋ ์ฟ ํค์ ์ธ์ ์ ๋ณด๋ฅผ ํ์ฉํ์ฌ ์น ์๋ฒ์ ์ฌ์ฉ์๊ฐ ์๋ํ์ง ์์ ์์ฒญ์ ์ ๋ฌํ๋ ๊ฒ
CSRF ์ค์ ์ ํด๋ ๊ฒฝ์ฐ CSRF ํ ํฐ์ด ์๋ ๊ฒฝ์ฐ์๋ง html๋ก ์์ฒญ์ ๋ณด๋ผ ์ ์๋ค.
์ฟ ํค ๊ธฐ๋ฐ์ ์ทจ์ฝ์ ์ ์ด์ฉํ ๊ณต๊ฒฉ์ด๊ธฐ ๋๋ฌธ์ REST๋ฐฉ์์ API์์๋ ์๋ฐฉํ ์ ์๋ค.
Spring Security์ ์ฃผ์ ์ปดํฌ๋ํธ
1) Filter
์์ฒญ์ด ๋ค์ด์ฌ ๊ฒฝ์ฐ ServletFilterChain์ ์๋ ๊ตฌ์ฑํ ํ ์์ฒญ์ ~Chain์์ ๊ฒ์ฌํ๊ฒ ํ๋ค ~ FilterChain์ด๋ ์ฌ๋ฌ Filter์ ๋ฌถ์.
Filter๋, ํฐ์บฃ๊ณผ ๊ฐ์ ์น ์ปจํ ์ด๋์์ ๊ด๋ฆฌ๋๋ ์๋ธ๋ฆฟ์ ๊ธฐ์ ์ด๋ค. Filter๋ ํด๋ผ์ด์ธํธ์ ์์ฒญ์ด ์ ๋ฌ๋๊ธฐ ์ ํ์ URL ํจํด์ ๋ง๋ ๋ชจ๋ ์์ฒญ์ ํํฐ๋ง์ ํด์ค๋ค. -> CSRF, XSS ๋ฑ์ ๋ณด์ ๊ฒ์ฌ๋ฅผ ํตํด ์ฌ๋ฐ๋ฅธ ์์ฒญ์ด ์๋ ๊ฒฝ์ฐ, ๊ทธ ์์ฒญ์ ์ฐจ๋จํ๋ค. == Spring Security๋ Filter๋ฅผ ์ฌ์ฉํ์ฌ ์ธ์ฆ/์ธ๊ฐ๋ฅผ ๊ตฌํํ๊ณ ์๋ค.
2) ๊ธฐ๋ณธ Filter : AbstractAuthenticationProcessingFilter -> ์ฌ์ฉ์์ credential์ ์ธ์ฆ
- FIlterChain์์ DelegatingFilterProxy : ๋ค๋ฅธ beanFilter์๊ฒ
3) UsernamePasswordAuthenticationFilter
๊ธฐ๋ณธ Filter๋ฅผ ์์
Form ํ์ Login์ ์ฌ์ฉํ ๋ username๊ณผ password๋ฅผ ํ์ธํ์ฌ ์ธ์ฆ
-> ์ธ์ฆ์ ์คํจํ๋ค๋ฉด ๊ธฐ๋ณธ ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ๋ฐํํ๋ค
-> UsernamePasswordAuthenticationFilter๋ฅผ ํตํด ์ธ์ฆ๋ชฉ์ ์ UsenamePasswordAuthenticationToken์ ์์ฑํ๊ณ ,
์ธ์ฆ ๊ณผ์ ์ AuthenticaionManager๋ฅผ ๊ตฌํํ ๊ฐ์ฒด์ ์์
4) SecrityContextHolder : ์ธ์ฆ์ ์๋ํ ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ค.
~ SecruityContext๋ Authentication ๊ฐ์ฒด๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
// ์์์ฝ๋
SecurityContext context = SecurityContextHolder.createEmptyContext();
Authentication authentication = new UsernamePasswordAuthenticationToken(principal, credentials, authorities);
context.setAuthentication(authentication);
SecurityContextHolder.setContext(context);
5) Authentication : ํ์ฌ ์ธ์ฆ๋ ์ฌ์ฉ์๋ฅผ ๋ํ๋ด๋ฉฐ SecurityContext์์ ๊ฐ์ ธ์ฌ ์ ์๊ณ ๋ค์ 3๊ฐ์ง์ ์ ๋ณด(ํ๋, ์ธ์คํด์ค)๋ฅผ ๊ฐ์ง๋ค.
Principal : ์ฌ์ฉ์๋ฅผ ์๋ณํ๋ค. Username/ Password ๋ฐฉ์์ผ๋ก ์ธ์ฆํ ๋ ๋ณดํต UserDetails ์ธ์คํด์ค๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
Credential : ์ฃผ๋ก ๋น๋ฐ๋ฒํธ, ๋๋ถ๋ถ ์ฌ์ฉ์ ์ธ์ฆ์ ์ฌ์ฉํ ๋ค์ ๋น์ด์๋ ์ํ๋ก ๋น์ด๋ค.
Authorities : ์ฌ์ฉ์์๊ฒ ๋ถ์ฌํ ๊ถํ์ GrantedAuthority๋ก ์ถ์ํํ์ฌ ์ฌ์ฉํ๋ค.
<UserDetails>
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
UserRoleEnum role = user.getRole();
String authority = role.getAuthority();
System.out.println("authority = " + authority);
SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
Collection<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(simpleGrantedAuthority);
return authorities;
}
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
UsernamePasswordAuthenticationToken์ Authenticaion์ implementsํ AbstractAuthenticationToken์ ํ์ ํด๋์ค๋ก, ์ธ์ฆ ๊ฐ์ฒด๋ฅผ ๋ง๋๋ ๋ฐ ์ฌ์ฉ๋๋ค.
UserDetailService๋ username/password ์ธ์ฆ๋ฐฉ์์ ์ฌ์ฉํ ๋ ์ฌ์ฉ์๋ฅผ ์กฐํํ๊ณ ๊ฒ์ฆํ ํ UserDetails๋ฅผ ๋ฐํํ๋ค.
Customํ์ฌ ๋น์ผ๋ก ์ฌ์ฉ ๊ฐ๋ฅํ๋ค. ->
UserDetailService์์ ๋ฐํํ, ๊ฒ์ฆ๋ UserDetails๋
UsernamePasswordAuthenticationToken ํ์ ์ Authenticaion์ ๋ง๋๋๋ฐ ์ฌ์ฉ๋๋ฉฐ ๊ทธ ์ธ์ฆ ๊ฐ์ฒด๋ SecurityContextHoleder์ ์ ์ฅ๋๋ค. Customํ์ฌ ์ฌ์ฉ ๊ฐ๋ฅํ๋ค.
WebSecurityConfig
// ์คํ๋ง ๋ถํธ 2.7.6์ผ๋ก ์ค์ ํ ๊ฒ : 3.0.1์์๋ ์๋ํ์ง ์๋ ์ฝ๋
package com.sparta.springsecurity.config;
import org.springframework.context.annotation.Bean;
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.SecurityFilterChain;
@Configuration
@EnableWebSecurity // ์คํ๋ง Security ์ง์์ ๊ฐ๋ฅํ๊ฒ ํจ
public class WebSecurityConfig {
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
// h2-console ์ฌ์ฉ ๋ฐ resources ์ ๊ทผ ํ์ฉ ์ค์
return (web) -> web.ignoring()
.requestMatchers(PathRequest.toH2Console())
.requestMatchers(PathRequest.toStaticResources().atCommonLocations());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// CSRF ์ค์
http.csrf().disable();
http.authorizeRequests()
.antMatchers("/h2-console/**").permitAll()
.antMatchers("/css/**").permitAll()
.antMatchers("/js/**").permitAll()
.antMatchers("/images/**").permitAll()
.requestMatchers(PathRequest.toStaticResources().atCommonLocations()).permitAll()
.anyRequest().authenticated();
// ๋ก๊ทธ์ธ ์ฌ์ฉ
http.formLogin();
return http.build();
}
}
* ์คํ๋ง ์ํ๋ฆฌํฐ์ ์ธ์ฆ : Session๋ฐฉ์์ ์ฌ์ฉ
UserService ์์์ loadUserByUsername ๋ฉ์๋๋ฅผ ํธ์ถํ ๊ฐ์ UserDetails ํ์
์ ๊ฐ์ฒด๋ฅผ ์์ฑํ ๋ค ํ ๋นํ๋ค.
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
๊ทธ ๊ฐ์ฒด์ ์ธ์คํด์ค๋ฅผ ํ์ฉํ์ฌ Authentication ํ์
์ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค.
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, userDetails.getPassword(), userDetails.getAuthorities());
Custom ๋ก๊ทธ์ธ ํ์ด์ง๋ฅผ ์ฌ์ฉํ๊ธฐ๋ก ์คํ๋ง์ ํต์งํ๋ฉด, ์คํ๋ง์ ๋ํดํธ ๋น๋ฐ๋ฒํธ๋ฅผ ์์ฑํ์ง
์๊ณ , ๊ธฐ์กด์ UserDetails, UserDetailsService๋ฅผ ์ฌ์ฉํ ์ธ์ฆ ๋ํ ์งํํ์ง ์๋๋ค.
-> DB์ ํ์๊ฐ์ ์ ์ฑ๊ณตํ User ์ ๋ณด๋ฅผ ์ ์ฅ ๋ฐ ๋ก๊ทธ์ธ ํ ๋ User ๊ฐ์ฒด๋ฅผ ํธ์ถํ์ฌ UserDetails ์ ๋ณด๋ฅผ ๋ง๋ค๊ณ , UserDetails ์ ๋ณด๋ฅผ ํ์ฉํ ํํฐ๋ฅผ ์ฌ์ฉํด ์ฌ์ฉ์ ์ธ์ฆ ๊ณผ์ ์ ๊ฑฐ์น๋ ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํด์ผ ํ๋ค.
๋น๋ฐ๋ฒํธ๋ ์ ๋ ฅ ๊ฐ ๊ทธ๋๋ก DB์ ์ ์ฅ๋์ด์ ์๋๋ค. ~
์ํธํ๋ฅผ ๊ฑฐ์ณ DB์ ์ ์ฅํด์ผ ํด์ปค๊ฐ DB๋ฅผ ํธ์ด๋ ๊ณ์ ์ ๋ณด๋ฅผ ๋ชจ๋ ํ๋ํ ์ ์๊ธฐ ๋๋ฌธ์
์ ์ํ ๋จ๋ฐฉํฅ ํจ์๋ก ๋น๋ฐ๋ฒํธ๋ฅผ ์ ์ฅํ๋ ๊ฒ์ด ์ถ์ธ : ์คํ๋ง๋ ์ฌ์ฉํ๋ ํจ์
-> ์๋ฐฉํฅ ์ํธ ์๊ณ ๋ฆฌ์ฆ & ๋จ๋ฐฉํฅ ์ํธ ์๊ณ ๋ฆฌ์ฆ
// ์ฌ์ฉ์์
// ๋น๋ฐ๋ฒํธ ํ์ธ
if(!passwordEncoder.matches("์ฌ์ฉ์๊ฐ ์
๋ ฅํ ๋น๋ฐ๋ฒํธ", "์ ์ฅ๋ ๋น๋ฐ๋ฒํธ")) {
throw new IllegalAccessError("๋น๋ฐ๋ฒํธ๊ฐ ์ผ์นํ์ง ์์ต๋๋ค.");
}
- boolean matches(CharSequence rawPassword, String encodedPassword);
- rawPassword : ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ๋น๋ฐ๋ฒํธ
- encodedPassword : ์ํธํ๋์ด DB ์ ์ ์ฅ๋ ๋น๋ฐ๋ฒํธ
1. ์ฌ์ฉ์๋ ํ์๊ฐ์ ์ ์งํ
2. ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ ์ฅํ ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ์ํธํํ์ฌ ์ ์ฅ
3. ์ฌ์ฉ์๋ ๋ก๊ทธ์ธ์ ์งํ
4. ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์ ๋ณด๋ฅผ ํตํด ์ ์ฅ๋ ์ํธํ๋ ๋น๋ฐ๋ฒํธ๋ฅผ ๊ฐ์ ธ์ ์ฌ์ฉ์๊ฐ ์ ๋ ฅํ ์ํธ์ ๋น๊ต
5. ์ฌ์ฉ์ ์ธ์ฆ์ด ์ฑ๊ณตํ๋ฉด ์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ์ฌ์ฉํ์ฌ JWT ํ ํฐ์ ์์ฑํ์ฌ Header์ ์ถ๊ฐํ์ฌ ๋ฐํํ๊ณ ์ฌ์ฉ์๋ ์ด๋ฅผ ์ฟ ํค์ ์ฅ์์ ์ ์ฅ
6. ์ฌ์ฉ์๋ ๊ฒ์๊ธ ์์ฑ๊ณผ ๊ฐ์ ์์ฒญ์ ์งํํ ๋ ๋ฐ๊ธ๋ฐ์ JWT ํ ํฐ์ ๊ฐ์ด ๋ณด๋ด๊ณ ์๋ฒ๋ ์ด๋ฅผ ๋น ๋ฅด๊ฒ ์ธ์ฆ ํ๊ณ ์ฌ์ฉ์์ ์์ฒญ์ ์ํ
+ Custom Filter ์ถ๊ฐ
// Custom Filter ๋ฑ๋กํ๊ธฐ
http.addFilterBefore(new CustomSecurityFilter(userDetailsService, passwordEncoder()), UsernamePasswordAuthenticationFilter.class);
// ๊ฐ์ฒด ํ๋ผ๋ฏธํฐ ์์๋๋ก : principal, credentials, authorities
+ ๋ก๊ทธ์ธ Post ๋ฉ์๋ : UserController + @AuthenticationPrincipal : ์ธ์ฆ ๊ฐ์ฒด์ principal ์ธ์คํด์ค์ ๊ฐ์ ๊ฐ์ ธ์จ๋ค.
@Secured
: ๊ถํ ์ค์ ๋ฐฉ๋ฒ
-> ์ปจํธ๋กค๋ฌ์ ํด๋น ๋ฉ์๋ ์์ ์ด๋ ธํ ์ด์ ํ์
// (๊ด๋ฆฌ์์ฉ) ๋ฑ๋ก๋ ๋ชจ๋ ์ํ ๋ชฉ๋ก ์กฐํ
// MySelectShop ์์
@Secured("ROLE_ADMIN")
@GetMapping("/api/admin/products")
public List<Product> getAllProducts() {
return productService.getAllProducts();
}
์ ๊ทผ ์ ํ ํ์ด์ง ์ค์
1) TestController ์์ฑ
2) UserController์ "/forbidden" Post ๋ฉ์๋ ์์ฑ
3) WebSecurityConfig
// ์ ๊ทผ ์ ํ ํ์ด์ง ์ด๋ ์ค์
http.exceptionHandling().accessDeniedPage("/api/user/forbidden");
4) TestApi์ @Secured๋ก ๊ถํ ์ค์
401, 403 Error ExceptionHandling
1) SecurityExceptionDto ์์ฑ : ์ค๋ฅ ๋ฐ์์ ์ฝ๋์ ๋ฉ์ธ์ง๋ฅผ ์ปค์คํ ํ์ฌ ์ฌ์ฉํ๊ฒ ๋ค.
2) CustomAccessDeniedHandler ์์ฑ
3) CustomAuthenticationEntryPoint ์์ฑ
4) WebSecurityConfig
// ์ ๊ทผ ์ ํ ํ์ด์ง ์ด๋ ์ค์
// http.exceptionHandling().accessDeniedPage("/api/user/forbidden");
// 401 Error ์ฒ๋ฆฌ, Authorization ์ฆ, ์ธ์ฆ๊ณผ์ ์์ ์คํจํ ์ ์ฒ๋ฆฌ
http.exceptionHandling().authenticationEntryPoint(customAuthenticationEntryPoint);
// 403 Error ์ฒ๋ฆฌ, ์ธ์ฆ๊ณผ๋ ๋ณ๊ฐ๋ก ์ถ๊ฐ์ ์ธ ๊ถํ์ด ์ถฉ์กฑ๋์ง ์๋ ๊ฒฝ์ฐ
http.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler);
403 (Forbidden) ํด๋ผ์ด์ธํธ ์ค๋ฅ ์ํ. ์๋ฒ์ ์์ฒญ์ด ์ ๋ฌ๋์์ง๋ง, ๊ถํ ๋๋ฌธ์ ๊ฑฐ์ ๋จ.