모바일 앱 백엔드를 개발하면서 눈이 많이 뜨였다. 설계자의 시점에서 프로젝트를 바라볼 필요가 있기에 많은 노력과 고생이 들어갔다. 아직 대학교 4학년이지만 어느정도 프로젝트를 거시적으로 바라보며 이 작업이 왜 필요하고 어떻게 설계하면 좋을지에 대한 감각이 생긴것 같아서 행복한 요즘이다. 이번 시간엔 카카오 OAuth 2.0 + Spring Security + JWT + Redis를 조합하여 인증 시스템을 설계한 과정을 포스팅해볼 것이다. 각각의 기술을 선택한 이유와 세부적으로 필터체인을 설계한 과정을 공유할 생각이다. 왜 JWT일까? 전통적인 웹 애플리케이션에선 세션 방식이 일반적이다. 로그인할 땐 브라우저가 자동으로 쿠키 전송 → 서버가 세션 저장소에서 조회 → 사용자 정보 확인 이후의 ..
Backend/Security
JWT 기반 인증을 쓰다 보면 “DB에서는 이미 사용자가 삭제됐는데, 앱에서는 여전히 로그인 상태로 남아있는” 이상한 상황을 경험할 때가 있다. 나 역시 최근 이런 문제를 직접 겪었다. 로그아웃도 안 한 상태에서 DB에서 바로 사용자 데이터를 삭제했더니 앱을 껐다 켜도 여전히 로그인 상태로 유지됐다. 로그아웃을 해도 잘 안되고 결국 앱 캐시를 지우니까 그제야 초기화되었다. 처음엔 단순한 캐시 문제인 줄 알았는데 파악해보니까 원인은 훨씬 근본적이었다. 바로 JWT의 무상태 구조와 블랙리스트의 동작 방식 때문이다. JWT의 본질: 서버는 아무것도 기억하지 않는다. JWT는 서버가 세션을 저장하지 않는다. 토큰 안에 사용자 정보, 만료 시간, 서명이 모두 들어있기 때문이다. 서버는 요청마다 서명이 유효한가..
JWT는 로그인한 사용자를 인증하기 위해 가장 널리 쓰이는 방식이다. 서버가 세션을 직접 저장하지 않아도 되기 때문에 무상태이면서도 서명 검증만으로 사용자를 확인할 수 있다. 근데 단순한 만큼 치명적인 약점이 있다. 서버가 한 번 발급한 토큰을 강제로 무효화할 수 없다는 점이다. 서버가 잃어버린 통제권 JWT는 한 번 발급되면 그 안에 exp(만료시간)이 고정된다. 이 말은 서버가 그 토큰을 회수하거나 취소할 방법이 없다는 뜻이다. 사용자가 로그아웃 해도 기존 토큰은 유효하고 비밀번호 변경해도 이전 토큰으로 접근 가능하고 토큰이 유출되면 서버는 막을 방법이 없다. 결국에 서버가 다시 상태를 관리하는 방법을 찾아봐야 한다. 그게 바로 블랙리스트 구조이다. 블랙리스트란? JWT는 기본적으로 sta..
이전 시간에 duck dns 연결하는 법까진 포스팅했다. duckdns만으론 가끔 서버가 뻑갈 수 있기 때문에 dynu같은 무료 ddns를 하나 더 붙이면 좋을 것 같다. CICD 작업을 해서 HTTP 연결까지 한 상황을 가정하고 시작하겠다. 사람들이 왜 복잡하다고 했는진 모르겠는데 생각보다 너무 쉬워서 당황했다...내가 뭘 놓치고 있는건가 로컬에선 docker-compose로 app과 db를 띄워서 구동 성공하고 cicd 작업까지 완료해서 habiglow.duckdns.org 사이트가 동작하는 것까지 확인한 상황! 일단 여기서 rds만 생성해놓고 db를 생성하지 않아서 계속 오류가 생기는 사람들이 있던데 rds는 서버라고 생각하고 그 안에 db를 꼭 생성해야 한다는 것을 잊지 말자. postgres..
스프링 시큐리티를 공부하면서 몇 달 전에 프로젝트를 간단하게 했다. 인증된 사용자들끼리 웹소켓을 활용한 채팅 기능을 추가 해보고 싶어서 다시 들어갔는데 빨간색 투성이가 나를 맞이했다. 도대체 이게 왜 안되는거지 하고 찾아보다가 gradle을 다시 리로드하니까 라이브러리들이 다시 정상적으로 import 되었다. 찾아보니 windows에선 종종 겪는 현상이라고 한다. 원인은 IDE와 Gradle 간의 캐시 이슈라는데 자세히는 모르겠다.. 그냥 Gradle 다시 빌드하면 되는 문제이다. 코드를 오랜만에 다시 보니 불편한 요소들이 꽤나 있어서 리팩토링을 하였다. Spring Security 6.x 베스트 프랙티스 적용null 안정성 확보성능 최적화보안 강화유지보수성 향상관점에서 생각해봤다. UserD..
스프링 시큐리티로 로그인 로직을 구현하던 중에 애초에 회원가입할 때 계정의 권한을 미리 설정한 관리자 키와 맞는 경우 관리자 권한으로 가입할 수 있게 하는 게 어떨까 생각되어 구현해보았다. [회원가입 (/join)] │ POST ▼ /joinProc (컨트롤러 처리) │ ├─ adminKey 일치 → ROLE_ADMIN └─ adminKey 불일치/미입력 → ROLE_USER │[로그인 (/login)] │ POST ▼ /loginProc (Spring-Security 필터 자동처리) │ ├─ ROLE_ADMIN → /admin ..
오늘은 네이버, 카카오, 구글 소셜 로그인을 구현한 과정을 공유하러고 한다. 이번 게시물엔 로그인 창이 정상적으로 뜨는 것까지 구현하고 미가입 회원 회원가입 처리와 db 저장 로직을 다룰 것이다. 로그인은 총 4가지 이다. 1. 일반 로그인/회원가입 (세션 기반)사용자가 이메일과 비밀번호로 직접 회원가입하고 로그인할 수 있는 전통적인 방식이다. 세션을 통해 로그인 상태를 유지하며, 서버 측에서 세션 정보를 관리한다. 2. 소셜 로그인 (Spring Security OAuth2)구글 (Google): 가장 범용적으로 사용되는 소셜 로그인카카오 (Kakao): 한국 사용자들이 선호하는 플랫폼네이버 (Naver): 국내 사용자 접근성을 위한 선택3. 역할 기반 대시보드 (USER/ADMIN)로그인한 사용자의 ..
이번 시간엔 마지막 시간이다. 먼저 CORS에 대해 알아보자. CORS 설정 CORS란 프론트엔드 서버와 백엔드 서버를 따로 띄워서 브라우저 단에서 테스트하면 CORS 문제 때문에 데이터가 날아오지 않거나 데이터를 부어줄 수 없는 문제가 생긴다. 리액트랑 스프링을 같이 짜면 주로 겪게 되는 문제이다. 우리의 클라이언트가 웹 브라우저를 통해서 사이트에 접속하게 되면 프론트 서버에서 react나 vue를 응답해준다. 보통 프론트엔드 서버는 포트번호 3000번을 띄워서 테스트를 하게 되고 응답받은 페이지에서 특정한 내부 데이터를 API 서버한테 호출하게 되면 API 데이터는 8080포트에서 응답이 된다. 이렇게 되면 두 개의 서버 포트 번호가 다르므로 웹 브라우저 단에서 교체 출처 리소스 공유를 금지시키..