728x90
개요
JWT 공부를 시작했을 때 JWT를 왜 사용하는지를 한동안 고민에 잠겼고 결론에 대해서 정리하고 싶어서 작성하게 되었다.
JWT의 STATELESS 상태에 대한 집착
@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {
private final CustomUserDetailsService customUserDetailsService; // 기본 세션 로그인
private final CustomOauth2UserService customOAuth2UserService; // OAuth2 등록
// AuthenticationManager가 인자로 받을 AuthenticationConfiguraion 객체 생성자 주입
private final AuthenticationConfiguration authenticationConfiguration;
private final JWTUtil jwtUtil; //JWTUtil 주입
// AuthenticationManager Bean 등록
// AuthenticationConfiguration 인자를 또 받기 때문에 주입
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests(authorize -> authorize
.requestMatchers("/login", "/", "/join").permitAll()
.requestMatchers("/admin").hasAuthority("ADMIN")
.anyRequest().authenticated()
)
.formLogin(form -> form.disable())
.httpBasic(auth -> auth.disable())
.sessionManagement(session -> session // jwt 방식에서는 session --> STATELESS
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.userDetailsService(customUserDetailsService)
.csrf(csrf -> csrf.disable())
// --> 우리는 formLogin 을 disable 해놨기 때문에 UsernamePasswordAuthenticationFilter 을 직접 커스텀 해야한다.
.addFilterBefore(new JWTFilter(jwtUtil), LoginFilter.class)
.addFilterAt(new LoginFilter(authenticationManager(authenticationConfiguration), jwtUtil), UsernamePasswordAuthenticationFilter.class)
.cors(httpSecurityCorsConfigurer -> httpSecurityCorsConfigurer.configurationSource(configurationSource())
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
public CorsConfigurationSource configurationSource(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowedMethods(List.of("GET","POST","DELETE"));
source.registerCorsConfiguration("/**",config);
return source;
}
}
- 위의 시큐리티 JWT config를 구성한 코드를 보면
- SessionCreationPolicy.STATELESS 상태가 등장하는 것을 볼 수 있다.
- 그래서 나는 이 STATELESS 상태에 초점이 맞추어 생각을 했다.
--> JWT 구현을 위해 STATELESS 상태가 필요하지만 STATELESS에 초점을 잡고 생각을 하니 문제가 많아졌다.
--> 아래를 읽다보면 왜 문제인지 알 수 잇을 것이다.
문제 상황
- 일단 기본적으로 JWT를 사용하면 토큰 탈취의 문제로 기존 Access 토큰과 함께 Refresh 토큰의 도입을 이야기한다.
- 따라서 Refresh, Access라는 2가지의 토큰을 발급해주는데, Refresh 토큰 요청는 Access 토큰 만료보다 주기 자체가 길기 때문에 탈취 당할 확률은 낮다고는 하지만 어찌되었든 탈취 당할 수 있다.
- 그래서 이 탈취 당할 확률을 방지하기 위해 서버측에서 어떠한 방법을 구현해야 하는데 이 부분에 대해서 많은 고민을 했다.
레디스, 데이터베이스의 도입
- 만약, 토큰이 탈취 되었을때 서버의 제어권과 로그아웃 문제 등 을 생각해보자.
- 일단 토큰이 탈취되면 토큰의 만료 기간 까지 서버측은 고통을 받을 것이다.
- 따라서 아예 서버를 꺼버리거나 서버 비밀키를 변경하는 상황까지 가는 불상사가 발생할 것이다.
- 혹은 프론트 서버측 로그아웃을 구현할 수도 있는데 이때 이미 해커가 토큰을 복제 했다면 해커는 그 토큰을 가지고 계속 서버에 접속할 수 있기 때문에 여전히 문제가 있다.
- 그래서 서버측에서 이러한 문제에 대해서 구현해줘야 하는데 이를 위해 서버측 Redis와 같은 저장소에 발급한 Refresh 토큰을 저장해서 로그아웃을 하거나 토큰이 탈취 당했을 때 블랙리스트를 도입하여 블랙리스트에 저장한다는 구현들이 많았다.
- 그래서 로그아웃 상태거나 탈취된 토큰은 Redis 서버에서 제거하여 앞으로 Access 토큰 재발급이 불가능하도록 설정하는 것이었다.
하지만 모순...
- 위처럼 서버에서 레디스같은 저장소를 도입하는 것을 이야기 했지만 나는 그것은 모순이라고 생각했다.
- Refresh들을 저장하기 위한 Redis를 도입해버리면 사실상 세션 클러스터링을 작업하고 세션 방식을 사용하는 것이 좋지 않을까? 라는 생각을 하게 된 것이다.
- 왜냐면 나는 jwt config 를 작성하면서 앞 단에서 세션 STATELESS 작업을 했는데, 뒷단인 다른 곳에서 상태 저장이 생겨버리는 것이 아닌가 라는 생각이 든 것이다.
- 그래서 탈취를 막으면서도 Redis를 도입하지 않을 방법에 대해서 고민을 했지만.....
- 시원한 해답은 얻지를 못했다. 아래처럼 계속 둘레에 빠져 한 곳만 바로보고 있던 것이다.
STATELESS → 그런데 Redis → 그럼 차라리 세션 → 왜 JWT를 사용했지?
결론 : JWT를 왜 사용하는가?
우리는 우리가 할려는 일에 대해서 목표가 무엇인지 판단해야 한다.
우리가 JWT의 목적을 확인하지 않고 구현에만 열중한다면 무엇을 하는지도 모르는 것에 불과하다. 따라서 JWT의 STATELESS한 상태에만 목적을 두는 것이 아닌 JWT가 왜 필요한지를 생각했고 해답을 찾았다.
JWT를 사용한 이유
- 모바일 앱
- JWT가 사용된 주 이유는 결국 모바일 앱의 등장이다.
- 모바일 앱의 특성상 주로 JWT 방식으로 인증/인가를 진행한다.
- 그렇기 때문에 결국 세션 STATLESS는 앱의 등장으로 인해 부수적인 효과인 것이다.
- 장시간 로그인과 세션
- 장기간 동안 로그인 상태를 유지하려고 세션 설정을 하면 서버 측 부하가 많이 가기 때문에 JWT 방식을 이용하는 것도 한 방법이다.
위의 이런 이유때문에 jwt가 만들어졌고 JWT의 목적이 STATELESS가 아니기 때문에 나중에 로그아웃에 대해서 레디스나 디비에 리프레시 토큰을 저장하는 로직을 추가하는 것이 올바르다고 판단을 내렸다.
728x90
'Spring > Security' 카테고리의 다른 글
Spring Security + OAuth2 + Session 를 사용한 로그인, 로그아웃 구현 - google (0) | 2024.08.08 |
---|---|
Spring Security + jwt 토큰 (with 쿠키) :: http-only 설정방법 (0) | 2024.08.02 |
Spring Security + OAuth2 + Session 를 사용한 로그인, 로그아웃 구현 - Kakao, Naver (0) | 2024.07.26 |
Spring : OAuth2 로그인 방식 (0) | 2024.07.26 |
Spring Security + Session 를 사용한 회원가입, 로그인, 로그아웃 구현 (0) | 2024.07.26 |