코드 작성 예시
@Configuration
@RequiredArgsConstructor
public class SecurityConfig {
private final JwtTokenizer jwtTokenizer;
private final JwtTokenService jwtTokenService;
private final CustomAuthorityUtils authorityUtils;
private final MemberAuthenticationExceptionHandler memberAuthenticationExceptionHandler;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
http
// 악의적인 요청인지 확인
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/**") // API요청들은 CSRF 비활성화 (JWT토큰 사용)
.ignoringRequestMatchers("/h2/**") // h2도 예외
)
// CORS 활성화
.cors(Customizer.withDefaults())
// HTTP를 무상태로 관리 (세션 사용 안함 - JWT를 쓰면 세션이 아예 필요 없음)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// 인증되지 않은 요청 처리 진입점 등록
.exceptionHandling(ex ->
ex.authenticationEntryPoint(memberAuthenticationExceptionHandler)
)
// 누가 접근할 수 있는 요청인지 확인
.authorizeHttpRequests(auth -> auth
.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll() // 프리플라이트는 모두 허용
.requestMatchers("/error").permitAll() // 에러 페이지 허용
.requestMatchers(HttpMethod.POST, MemberController.BASE_PATH_ANY).permitAll() // 회원가입은 예외
.requestMatchers(MemberController.BASE_PATH_ANY + "/emails/is-registered").permitAll() // 이메일 가입여부도 예외
.requestMatchers(MemberController.BASE_PATH_ANY + "/nicknames/**").permitAll() // 닉네임 검증은 예외
.requestMatchers(HttpMethod.PATCH,MemberController.BASE_PATH_ANY + "/passwords/reset").permitAll() // 비밀번호 변경도 허용
.requestMatchers(EmailController.BASE_PATH_ANY + "/**").permitAll() // 이메일 관련 API는 예외
.requestMatchers(AuthController.BASE_PATH_ANY + "/**").permitAll() // auth 전체 허용
.requestMatchers("/h2/**").permitAll() // h2 볼때는 예외
//.requestMatchers(HttpMethod.GET, PostController.BASE_PATH + "/**").permitAll() // 비회원도 조회는 허용
.anyRequest().authenticated() // 위에 명시하지 않은 요청은 전부 인증 필요
)
// 같은 도메인에서 iframe 허용 (h2가 iframe 사용)
.headers(headers -> headers
.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
)
// CustomFilterConfigurer 적용
.with(new CustomFilterConfigurer(), Customizer.withDefaults())
;
return http.build();
}
// CORS 관련 설정
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("<http://localhost:3000>"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PATCH", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("*"));
configuration.setAllowCredentials(true);
// 클라이언트가 토큰에 접근할 수 있도록 허용
// 그럼 브라우저에선 이거 없어도 왜 보이냐? -> Network 탭에서 보이는 건 HTTP 프로토콜 레벨에서 접근하는 것이기 때문에 안 숨겨짐
configuration.setExposedHeaders(Arrays.asList("Authorization", "Refresh"));
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
// 추가 필터를 시큐리티 필터 체인에 일괄 등록
public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer, HttpSecurity>{
@Override
public void configure(HttpSecurity builder) {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenService);
// 로그인 요청 엔드포인트 설정
jwtAuthenticationFilter.setFilterProcessesUrl(AuthController.BASE_PATH + "/login");
// 로그인 요청 후처리 로직 적용
jwtAuthenticationFilter.setAuthenticationSuccessHandler(new MemberAuthenticationSuccessHandler());
jwtAuthenticationFilter.setAuthenticationFailureHandler(new MemberAuthenticationFailureHandler());
// JWT 토큰 유효성 검증 필터 적용
JwtVerificationFilter jwtVerificationFilter = new JwtVerificationFilter(jwtTokenizer, authorityUtils);
builder
.addFilter(jwtAuthenticationFilter) // 로그인 처리(순서 중요)
.addFilterAfter(jwtVerificationFilter, JwtAuthenticationFilter.class); // 토큰 검증 (순서 중요)
}
}
}