Spring Security를 사용하는 이유
Spring Security는 Spring으로 구현한 어플리케이션에서 인증(Authentication)과 권한 부여(Authorization)을 관리하기 위한 프레임워크이다. 웹 어플리케이션과 RESTful API, MicroService 등 다양한 형태의 어플리케이션에서 적용 가능한 보안 설정을 지원한다. Spring Security의 주요 기능은 다음과 같다.
- 인증(Authentication): 사용자의 신원을 확인.
- 권한 부여(Authorization): 사용자가 특정 리소스나 기능에 접근할 수 있는지 권한 확인.
- CSRF 보호: Cross-Site Request Forgery 방지.
- 세션 관리: 로그인/로그아웃과 관련된 세션 제어
- 보안 헤더: XSS, Clickjacking 방지를 위한 보안 헤더 추가
- OAuth2 및 JWT 지원: 현대적인 인증 방식을 손쉽게 통합
Spring Security 의존성 추가 및 기본적인 설정 방법
Spring boot에서 Spring Security와 JWT를 사용하기 위해 다음과 같이 의존성을 추가한다.
// security
implementation 'org.springframework.boot:spring-boot-starter-security'
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
Spring boot에서 security 의존성을 추가하면 기본적으로 다음과 같은 설정이 활성화된다.
- 모든 요청이 인증 필요
- 기본 로그인 폼 제공
- 기본 사용자 이름(user)과 암호가 자동 생성
하지만 이런 기본설정을 그대로 사용하지는 않고, 커스텀 security 설정을 구성해서 사용한다.
보안 설정의 변경은 SecurityConfig에서 SecurityFilterChain이라는 Bean을 정의해야 한다.
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager());
JwtAuthorizationFilter jwtAuthorizationFilter = new JwtAuthorizationFilter();
http
.csrf(AbstractHttpConfigurer::disable) // CSRF 보호 비활성화 (API 환경에서 주로 사용)
.authorizeHttpRequests(
auth -> auth.requestMatchers("/api/users/register", "/api/users/login", "/swagger-ui/**", "/v3/api-docs/**").permitAll()
.requestMatchers("/api/clans/create").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin(AbstractHttpConfigurer::disable)
.logout(AbstractHttpConfigurer::disable)
.sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(jwtAuthorizationFilter, JwtAuthenticationFilter.class)
.addFilterAfter(new ClanAuthorizationFilter(jwtTokenProvider(), handlerMappings), JwtAuthorizationFilter.class);
return http.build();
}
}
SecuritFilterChain은 Spring Security의 핵심 구성 요소로 HTTP 요청을 보호하기 위한 보안 필터 체인을 설정한다. 이 체인을 통해 요청이 들어올 때 실행되는 여러가지 필터를 정의하고, 이를 통해 다양한 보안 검사를 진행한다.
주요 메소드와 설정값은 다음과 같다.
csrf(): CSRF 공격을 방지하기 위한 설정이다. REST API에서는 일반적으로 CSRF 보호를 비활성화 한다. 활성화 한다면 CSRF 토큰 없이 들어오는 요청에 대해서는 403 Forbidden으로 응답한다
authorizeHttpRequests(): 요청별 권한 설정을 한다. 아래 예시를 살펴보자.
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll() // 인증 없이 접근 가능
.requestMatchers("/api/admin/**").hasRole("ADMIN") // ROLE_ADMIN 권한 필요
.requestMatchers("/api/user/**").authenticated() // 인증된 사용자만 접근 가능
.anyRequest().denyAll() // 나머지 모든 요청 거부
);
requestMatchers("<URI>")는 만족하는 해당 URI 패턴에 대해서 다음에 오는 설정을 따른다.
.permitAll()은 누구나 접근 가능하다.
.hasRole("ADMIN")은 ROLE_ADMIN이라는 권한이 주어진 사용자만 접근 가능하다
.authenticated()는 필터체인에 등록된 보안 필터를 모두 만족하는 인증된 사용자만 접근 가능하다.
.anyRequest().denyAll()은 위 3가지 패턴을 제외한 나머지 요청에 대해서 403 Forbidden을 반환한다.
.formLogin(), .logout(): Spring Security에서 제공하는 기본 로그인 폼을 활성화한다. 커스텀 로그인 페이지를 정의할 수 도 있다. username/password를 입력받아 Spring Security에서 이를 인증하여 세션에 사용자 정보를 저장한다. JWT 방식으로 로그인을 구현하면 formLogin같은 서버 렌더링 폼은 필요하지 않다. 따라서 disable 하였다. logout도 마찬가지.
.sessionManagement(): 세션 관리 설정이다. 세션 생성 및 동작 방식을 설정할 수 있다.
- STATELESS: 모든 요청에서 새로 인증해야 하며, 세션을 생성하지 않는다 (JWT 기반 인증에서 사용)
- IF_REQUIRED: 필요할 때만 세션 생성(기본값)
- ALWAYS: 항상 세션 생성
- NEVER: Spring Security가 세션을 생성하지 않지만, 기존 세션은 사용할 수 있다.
이외에도 .cors()나 .headers() 설정이 있지만, 이 내용들은 추후 포스팅에서 따로 다루는것으로 하겠다.
또한 아래 addFilter는 이후 포스팅과 이어지는 내용이기에 다음 포스팅을 참고바랍니다!
여기까지가 Sprign Security에서 보안을 설정하는 핵심인 SecurityFilterChain의 설정방법에 대해서 알아보았다.
해당 설정값들은 어플리케이션의 성격에 따라 다르게 적용할 수 있지만 실제 서비스에서 자주 사용되는 설정을 소개했다.