이제 OAuth 2.0 설정을 위한 Spring Security 설정을 합니다.
Gradle 의존성 추가
build.gradle에 Spring Security 의존성 추가를 합니다.
implementation 'org.springframework.boot:spring-boot-starter-security'
testImplementation 'org.springframework.security:spring-security-test'
OAuthSecurityConfig
Spring Security를 활성화시키기 위한 작업을 진행합니다. 저는 Spring Boot 3.2.3 버전을 사용했기 때문에 Spring Security 6.1 버전으로 진행합니다.
@RequiredArgsConstructor
@Configuration
public class OAuthSecurityConfig {
private final OAuth2UserCustomService oAuth2UserCustomService;
private final TokenProvider tokenProvider;
private final RefreshTokenRepository refreshTokenRepository;
private final UserRepository userRepository;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
.httpBasic(AbstractHttpConfigurer::disable) // HTTP 기본 인증 비활성화 (Session 을 사용하지 않고, Rest API 방식을 사용)
.csrf(AbstractHttpConfigurer::disable) // CSRF 보호 기능 비활성화
.formLogin(AbstractHttpConfigurer::disable) // formLogin 비활성화
.logout(AbstractHttpConfigurer::disable)
.cors(configurer -> configurer.configure(http)) // CORS 활성화
.sessionManagement(configurer -> configurer // 세션관리 정책 STATELESS (세션이 있으면 쓰지도 않고, 없으면 만들지도 않는다.)
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // header 를 확인 할 custom filter 추가
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login", "/sign-up").permitAll()
.requestMatchers("/api/token").permitAll()
.requestMatchers("/", "/css/**", "/images/**", "/js/**", "/favicon.ico", "/h2-console/**").permitAll()
.anyRequest().authenticated())
.oauth2Login(configurer -> configurer
.authorizationEndpoint(config -> config // Authorization 요청과 관련된 상태 저장
.authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository()))
.successHandler(oAuth2SuccessHandler()) // 인증 성공 시 실행 할 핸들러
.userInfoEndpoint(config -> config
.userService(oAuth2UserCustomService)))
.logout(configurer -> configurer
.logoutSuccessUrl("/"))
.exceptionHandling(configurer -> configurer // "/api/**"로 요청되는 경우 401 상태코드를 반환하도록 예외 처리
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new AntPathRequestMatcher("/api/**")))
.build();
}
@Bean
public OAuth2SuccessHandler oAuth2SuccessHandler() {
return new OAuth2SuccessHandler(
tokenProvider,
refreshTokenRepository,
oAuth2AuthorizationRequestBasedOnCookieRepository(),
userRepository);
}
@Bean
public TokenAuthenticationFilter tokenAuthenticationFilter() {
return new TokenAuthenticationFilter(tokenProvider);
}
@Bean
public OAuth2AuthorizationRequestBasedOnCookieRepository oAuth2AuthorizationRequestBasedOnCookieRepository() {
return new OAuth2AuthorizationRequestBasedOnCookieRepository();
}
@Bean
public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
다른 Spring Security 설정 예제를 보면 기존 방식과 많이 달라 보이기도 합니다. Spring Boot 2.x 버전에서 주로 사용했던 방식으로 WebSecurityConfigurerAdapter를 상속받고 메서드를 오버라이딩 하여 설정하는 경우를 많이 보셨을 겁니다.
Spring Boot의 버전이 올라가면서 Spring Security 설정도 바뀌게 되었습니다.
자세한 내용은 아래 링크를 통해 봐주시면 감사하겠습니다.
Spring Security 6.1, xxx is deprecated and marked for removal
사이드 프로젝트를 진행하면서 Spring Security 6을 적용하며 만났던 문제들을 적은 내용입니다. WebSecurityConfigurerAdapter Deprecated @RequiredArgsConstructor @Configuration @EnableWebSecurity public class config extends WebSec
dev-hui.tistory.com
filterChain 메서드의 전체 코드를 보면 상당히 복잡하고 길어 보입니다. 한 줄씩 나눠 어떤 의미를 가졌는지 천천히 설명하겠습니다.
.httpBasic(AbstractHttpConfigurer::disable)
.httpBasic(AbstractHttpConfigurer::disable) // HTTP 기본 인증 비활성화 (Session 을 사용하지 않고, Rest API 방식을 사용)
해당 설정을 하지 않는다면 인증이 되지 않을 경우 Spring Security 기본 로그인 창으로 이동하게 됩니다. Session을 사용하지 않는 Rest API 방식만을 사용할 것이기에 비활성화해줍니다.
.cors(configurer -> configurer.configure(http))
.csrf(AbstractHttpConfigurer::disable)
.cors(configurer -> configurer.configure(http)) // CORS 활성화
.csrf(AbstractHttpConfigurer::disable) // CSRF 보호 기능 비활성화
저는 FE 서버와 BE 서버로 나눠 프로젝트를 진행했기에 CORS 문제를 해결하기 위해 CORS 활성화를 해줍니다.
CSRF는 사이트 간 위조 요청 공격 방법 중 하나로 Spring Security 공식 문서상 non-browser clients 만을 위한 서비스라면 CSRF를 비활성화시켜도 괜찮다고 말합니다. 따라서 Rest API 방식만 사용할 것임으로 비활성화합니다.
.sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.sessionManagement(configurer -> configurer // 세션관리 정책 STATELESS (세션이 있으면 쓰지도 않고, 없으면 만들지도 않는다.)
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
Session 관리 정책을 STATELESS로 설정합니다. Session이 존재하여도 쓰지 않으며, 만들지도 않습니다.
.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(tokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // header 를 확인 할 custom filter 추가
UsernamePassswordAuthenticationFilter가 실행되기 전 직접 작성한 custom filter인 TokenAuthenticationFilter가 먼저 실행되게 합니다.
UsernamePassswordAuthenticationFilter가 인증되지 않은 사용자의 경우 로그인 페이지로 리디렉션 시키기 때문입니다. 이러한 이유로 먼저 TokenAuthenticationFilter를 통해 JWT 토큰 검증을 마치고 인증 정보를 얻어야 합니다.
검증이 정상적으로 처리되었다면 토큰의 정보를 통해 인증 정보를 얻어 SecurityContext에 넣어주게 됩니다.
이후 UsernamePassswordAuthenticationFilter가 실행되고 SecurityContext 내에 인증 정보가 있음을 확인하고 다음 과정을 거치게 됩니다.
.authorizeHttpRequests(authorize -> authorize .requestMatchers("/login", "/sign-up").permitAll()...
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login", "/sign-up").permitAll()
.requestMatchers("/api/token").permitAll()
.requestMatchers("/", "/css/**", "/images/**", "/js/**", "/favicon.ico", "/h2-console/**").permitAll()
.anyRequest().authenticated())
authorizeHttpRequests()를 통해 요청에 대한 인증 설정을 시작하며 메서드에 연이어 requestMatchers()를 통해 url 패턴을 지정하고 접근 권한을 설정합니다.
permitAll()은 접근 권한이 별도로 필요하지 않음을 의미하며, anyRequest().authenticated()는 이 외 모든 요청들은 인증이 필요하다는 의미를 가집니다.
Security는 requestMatchers()로 지정된 url을 순차적으로 탐색하며 현재 요청과 먼저 매칭되는 규칙을 따르기 때문에 주의하셔야 합니다.
.oauth2Login(configurer -> configurer...
.oauth2Login(configurer -> configurer
.authorizationEndpoint(config -> config // Authorization 요청과 관련된 상태 저장
.authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository()))
.userInfoEndpoint(config -> config
.userService(oAuth2UserCustomService))
.successHandler(oAuth2SuccessHandler())) // 인증 성공 시 실행 할 핸들러
OAuth2 로그인 설정을 진행하는 부분입니다. 각각의 부분을 나눠 설명하겠습니다.
authorizationEndpoint
.authorizationEndpoint(config -> config // Authorization 요청과 관련된 상태 저장
.authorizationRequestRepository(oAuth2AuthorizationRequestBasedOnCookieRepository()))
OAuth2에 필요한 정보를 Session이 아닌 쿠키에 저장하여 쓸 수 있도록 인증 요청과 관련된 상태를 저장할 저장소를 설정합니다. OAuth2AuthorizationRequestBasedOnCookieRepository 클래스는 이후 과정에서 설명합니다.
userInfoEndpoint
.userInfoEndpoint(config -> config
.userService(oAuth2UserCustomService))
OAuth2 로그인 시 사용자 정보를 가져오는 엔드포인트와 사용자 서비스를 설정합니다. userService()는 OAuth2UserService 클래스를 구현한 구현체의 인자 값을 받아야 합니다.
successHandler
.successHandler(oAuth2SuccessHandler())
OAuth2 로그인이 성공할 경우 처리할 핸들러를 지정합니다. 저는 이 부분에서 Refresh Token과 Access Token을 생성하였습니다.
.logout(configurer -> configurer.logoutSuccessUrl("/"))
.logout(configurer -> configurer
.logoutSuccessUrl("/"))
로그아웃 시 리디렉션 될 url을 설정합니다.
.exceptionHandling(configurer -> configurer.defaultAuthenticationEntryPointFor...
.exceptionHandling(configurer -> configurer // "/api/**"로 요청되는 경우 401 상태코드를 반환하도록 예외 처리
.defaultAuthenticationEntryPointFor(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED), new AntPathRequestMatcher("/api/**")))
"/api"로 시작되는 url인 경우 인증 실패 시 401 상태 코드 즉 Unauthorized를 반환합니다.
지금까지 저희는 JWT 설정을 마치고 OAuth 2.0 인증 서버 등록 및 Spring Security를 사용하기 위한 기본 설정을 하였습니다. 이번 포스팅을 따라 하시면서 아직 작성되지 않는 클래스들이 많아 불편을 겪었을 거라 생각이 됩니다.
다음 포스팅을 통해 비어있는 클래스들을 하나하나 작성해 보도록 하겠습니다. 긴 글을 읽어주셔서 감사합니다.
이전 포스팅으로 이동
Spring Security + JWT + OAuth 2.0 회원 기능(5) - OAuth 2.0 인증 서버 등록
다음 포스팅으로 이동
Spring Security + JWT + OAuth 2.0 회원 기능(7) - OAuth 2.0 기능 구현
'Spring > Spring Security' 카테고리의 다른 글
Spring Security + JWT + OAuth 2.0 회원 기능(8) - 전체 테스트 (0) | 2024.03.17 |
---|---|
Spring Security + JWT + OAuth 2.0 회원 기능(7) - OAuth 2.0 기능 구현 (0) | 2024.03.12 |
Spring Security + JWT + OAuth 2.0 회원 기능(5) - OAuth 2.0 인증 서버 등록 (0) | 2024.03.11 |
Spring Security + JWT + OAuth 2.0 회원 기능(4) - OAuth 2.0 개념 (1) | 2024.03.11 |
Spring Security + JWT + OAuth 2.0 회원 기능(3) - JWT 테스트 코드 (0) | 2024.03.11 |