728x90
반응형
SMALL
이번에는 Spring Security를 활성화하고, 여러 다양한 설정을 하는 방법을 알아보는 시간을 가져보자.
@EnableWebSecurity, SecurityFilterChain
우선, 설정을 하려면 이 애노테이션이 필요하다. 그래서 완성된 코드를 먼저 보자면 다음과 같다.
package cwchoiit.springsecurity.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.httpBasic(AbstractHttpConfigurer::disable) // HTTP Basic authentication disabled
.csrf(AbstractHttpConfigurer::disable) // CSRF protection disabled
.rememberMe(configurer -> configurer.tokenValiditySeconds(86400)) // 1 day
.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/home", "/h2-console/**", "/css/**", "/js/**", "/images/**", "/login", "/user/signup").permitAll()
.requestMatchers("/note").hasRole("USER")
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST, "/notice").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/notice").hasRole("ADMIN")
.anyRequest().authenticated())
.headers(headers -> headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)) // H2-Console iframe 정상 작동 (Origin 비교 후 같으면 그 iframe 요청은 허용)
.formLogin(formLogin -> formLogin
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll())
.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
.logoutSuccessUrl("/login"));
return http.build();
}
}
- @EnableWebSecurity 애노테이션을 붙여서 스프링 애플리케이션 전반적으로 Spring Security가 활성화되도록 한다.
- 이전 포스팅에서 배웠던 여러 필터들을 사용하기 위해선, SecurityFilterChain을 빈으로 등록해야 한다. 그래서 나의 경우, @Configuration 애노테이션을 붙인 Config 클래스를 하나 따로 만들고 이 파일에서 작업을 했다.
- 여기서부터 개발자가 원하는대로 필터를 적용하고 설정을 할 수 있다. 하나씩 살펴보자.
http.httpBasic(AbstractHttpConfigurer::disable)
- BasicAuthenticationFilter를 비활성화하는 코드이다. 사용하지 않는 필터들은 명시적으로 disable로 비활성화 시키면 좋다.
http.csrf(AbstractHttpConfigurer::disable)
- CsrfFilter를 비활성화하는 코드이다. 물론 활성화하면 보안적으로 더 도움이 될 것이지만, 이렇게 비활성화할 수도 있다.
http.rememberMe(configurer -> configurer.tokenValiditySeconds(86400))
- RememberMeAuthenticationFilter를 활성화하고, 해당 토큰의 유효기간을 1일로 설정했다. 앞으로 유저가 로그인 시 RememberMe를 체크하고 로그인하면 로그인 유지가 1일 정도로 매우 길어질 것이다.
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests
.requestMatchers("/home", "/h2-console/**", "/css/**", "/js/**", "/images/**", "/login", "/user/signup").permitAll()
.requestMatchers("/note").hasRole("USER")
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST, "/notice").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/notice").hasRole("ADMIN")
.anyRequest().authenticated())
- 인가를 설정한다. 즉, AuthorizationFilter가 활성화되고 여기서 설정한 값들을 토대로 현재 요청에 대한 인가가 정상적인지 판단하게 된다.
- 이 부분도 하나씩 살펴보자.
authorizeRequests.requestMatchers("/home", "/h2-console/**", "/css/**", "/js/**", "/images/**", "/login", "/user/signup").permitAll()
- 인가 처리를 할 필요없는 요청도 반드시 있을것이다. 정적 파일이나, 로그인 화면, 회원가입 화면 홈 화면 등 말이다. 그런 경우에는 인가를 할 필요없도록 인가가 필요없는 경로들에 대해 .permitAll()을 설정해서 임의의 사용자 모두 허용하도록 설정한다.
authorizeRequests
.requestMatchers("/note").hasRole("USER")
.requestMatchers("/admin").hasRole("ADMIN")
.requestMatchers(HttpMethod.POST, "/notice").hasRole("ADMIN")
.requestMatchers(HttpMethod.DELETE, "/notice").hasRole("ADMIN")
- 위 코드처럼, 특정 요청에 대한 모든 Method에 대해 인가를 지정할 수도 있고 위 코드의 아래 두줄 처럼 특정 요청의 특정 Method만 인가를 지정할 수도 있다.
- hasRole(...)을 사용해서 특정 권한을 가진 사용자만 허용한다. 여기서 `ROLE_`는 생략됐기 때문에 `ROLE_USER`로 작성하면 안되고 그냥 `USER`로 작성하면 된다. 이미 prefix로 `ROLE_`가 내부적으로 있다.
authorizeRequests.anyRequest().authenticated()
- 그 외의 요청은 인증만 처리한다는 의미이다. (인가가 아니다, 인증만!)
http.headers(headers ->
headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin))
- 이 부분은 스프링 부트를 사용하면 아주 편리한 점이 H2 데이터베이스를 매우 손쉽게 사용할 수 있다는 점이다. H2 데이터베이스 의존성만 내려받으면 자동으로 H2 데이터베이스를 실행시켜 주는데, 그때 H2 콘솔을 브라우저에서 접근할 수 있다. 근데 이 경우, H2 콘솔에서 iframe을 사용하기 때문에 기본적으로 iframe을 Spring Security가 허용하지 않는다. 그러나, 같은 origin인 경우에는 Iframe을 허용하겠다는 의미가 된다. 여기서는 H2 콘솔을 허용한다고 생각하면 된다.
http.formLogin(formLogin -> formLogin
.loginPage("/login")
.defaultSuccessUrl("/home")
.permitAll())
- 로그인 폼을 사용해서 로그인 절차를 거치게 한다. 이건 UsernamePasswordAuthenticationFilter를 사용한다는 의미와 같다. 그래서 로그인 페이지를 직접 만들었다면, 해당 경로를 위처럼 지정해주면 되고, 안 만들어도 기본으로 `/login` 경로로 스프링 시큐리티가 만들어준다. 그리고 가장 좋은 점 하나는 로그인 처리를 위한 POST 요청을 처리하는 과정을 자동으로 해주기 때문에 우리는 아예 아무것도 하지 않아도 되거나, 로그인 페이지를 직접 서비스의 디자인에 맞춰 만들고 싶은 개발자는 로그인 페이지만 만들면 된다.
- 로그인에 성공했다면 리다이렉트 할 경로도 지정해준다.
- 이 로그인 페이지는 당연히 모두가 접근할 수 있어야 하므로 permitAll()을 사용한다.
http.logout(logout -> logout
.logoutRequestMatcher(new AntPathRequestMatcher("/user/logout"))
.logoutSuccessUrl("/login"));
- 로그아웃도 마찬가지로 등록만 하면 스프링 시큐리티가 알아서 다 처리해준다. 그래서 위처럼 로그아웃의 경로만 만들어주면 된다. 그럼 알아서 로그아웃 관련 모든 처리를 해줄 것이다.
return http.build();
- 모든 설정이 끝났으니, 이제 이 HttpSecurity 객체를 반환하면 끝이다.
근데 여기서 재밌는 것을 봤다. 아래 두 코드를 보자.
new AntPathRequestMatcher("/user/logout")
.requestMatchers("/note")
저 AntPathRequestMatcher는 무엇이고, requestMatchers는 무엇일까?
AntPathRequestMatcher뿐 아니라, RegexRequestMatcher도 있고, MvcRequestMatcher있다.
- RegexRequestMatcher: 정규 표현식으로 매칭한다.
- MvcRequestMatcher: 스프링 MVC에서 사용하는 패턴을 기반으로 경로를 매칭한다. 그러니까 다음 코드를 보자.
new MvcRequestMatcher(mvcHandlerMappingIntrospector, "/user/{id}")
이렇게 PathVariable과 같은 MVC 스타일의 URL 처리를 지원한다. 저기서 mvcHandlerMappingIntrospector는 따로 만든게 아니라 스프링이 만들어 둔 것이고 가져다가 사용하면 된다.
- AntPathRequestMatcher: Ant 스타일 경로 패턴을 사용하여 매칭한다. 그러니까, URL 경로에서 일부의 와일드카드(*, **)도 사용할수가 있다.
근데, 이게 결국은 다 requestMatchers로 통합이 된다.
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers(HttpMethod.GET, "/api/public/**").permitAll();
- 이렇게 와일드 카드도 사용이 가능하고, 저 위에 예시처럼 명확하게 `/home`, `/note` 이렇게 사용도 할 수 있다. 또한, 인가가 필요없는 모든 경로를 다 정의해서 한번에 permitAll()을 할수도 있다. 그러니까 결국 이게 만능이라는 얘기다.
결론은 이 requestMatchers를 사용하면 된다.
728x90
반응형
LIST
'Spring Security' 카테고리의 다른 글
Spring Security Part.4 - 커스텀 필터 등록 (0) | 2024.10.06 |
---|---|
Spring Security Part.3 - 테스트 코드 작성 (0) | 2024.10.06 |
Spring Security Part.1 - 내부 구조 파악, Spring Security가 사용하는 여러 필터들 (0) | 2024.10.03 |