TECH

웹 인증 보안 실전: JWT 취약점 방어와 OAuth 2.1 마이그레이션

JWT 기반 인증에서 자주 발생하는 취약점과 방어 기법, OAuth 2.1의 변경점, PKCE 적용을 실무 관점으로 정리한다. 토큰 저장, 회전, 세션 관리 모범 사례를 담았다.

웹 인증 보안 실전: JWT 취약점 방어와 OAuth 2.1 마이그레이션

웹 인증 보안 실전: JWT 취약점 방어와 OAuth 2.1 마이그레이션

인증은 모든 웹 애플리케이션의 보안이 시작되는 지점이자 가장 흔히 공격받는 표면이다. JWT는 상태 없는 인증의 표준으로 널리 쓰이지만, 잘못 구현하면 심각한 취약점의 통로가 된다. 여기에 OAuth 2.1이 기존 2.0의 모범 사례를 표준으로 통합하며 인증 아키텍처의 기준이 재정립되고 있다. 이 글은 실무에서 반드시 알아야 할 JWT 방어 기법과 OAuth 2.1의 변경점을 구체적으로 다룬다.

JWT의 흔한 취약점과 방어

가장 악명 높은 JWT 취약점은 알고리즘 혼동 공격이다. 공격자가 토큰 헤더의 알고리즘을 none으로 바꿔 서명 검증을 우회하거나, 비대칭 키로 서명된 토큰을 대칭 알고리즘으로 검증하도록 속여 공개 키를 비밀 키로 오용하게 만든다. 방어책은 명확하다. 서버는 허용할 알고리즘을 명시적으로 고정하고, 토큰 헤더에 담긴 알고리즘 값을 절대 신뢰하지 않아야 한다.

토큰 저장 위치도 중요한 결정이다. 로컬 스토리지에 토큰을 두면 자바스크립트로 접근 가능해 크로스 사이트 스크립팅 공격에 그대로 노출된다. 반면 HttpOnly 속성이 붙은 쿠키는 스크립트 접근을 차단하지만, 이번엔 크로스 사이트 요청 위조에 대비해 SameSite 속성과 별도 토큰이 필요하다. 실전에서는 짧은 수명의 액세스 토큰과 긴 수명의 리프레시 토큰을 분리하고, 리프레시 토큰은 HttpOnly 쿠키에 보관하며 회전시키는 구조가 권장된다. 리프레시 토큰 회전은 사용될 때마다 새 토큰을 발급하고 이전 것을 무효화해, 탈취된 토큰의 재사용을 탐지하고 차단한다.

// 알고리즘을 명시적으로 고정해 혼동 공격 차단
jwt.verify(token, publicKey, {
  algorithms: ['RS256'],
  issuer: 'https://auth.example.com',
  audience: 'api.example.com',
});

OAuth 2.1의 핵심 변경점

OAuth 2.1은 완전히 새로운 프로토콜이 아니라, 수년간 축적된 보안 모범 사례를 하나의 명세로 통합한 것이다. 가장 큰 변화는 암묵적 그랜트 방식의 폐기다. 이 방식은 액세스 토큰을 URL 프래그먼트로 직접 전달해 유출 위험이 컸는데, 이제 모든 클라이언트가 인가 코드 그랜트를 사용하도록 통일됐다. 리소스 소유자 비밀번호 그랜트 역시 자격 증명을 애플리케이션에 직접 노출하는 안티패턴이라 제거됐다.

PKCE는 이제 모든 인가 코드 흐름에서 필수가 됐다. 원래 공개 클라이언트인 모바일 앱과 단일 페이지 애플리케이션을 위한 확장이었지만, 인가 코드 가로채기 공격을 막는 효과가 커서 기밀 클라이언트에도 의무화됐다. 클라이언트는 매 요청마다 난수 검증자를 생성하고 그 해시를 인가 요청에 담아, 토큰 교환 시 원본 검증자를 제시함으로써 코드가 정당한 요청자에게만 사용되도록 보장한다. 또한 리다이렉트 URI는 부분 일치가 아닌 정확한 문자열 일치로만 등록해, 오픈 리다이렉트를 통한 토큰 탈취를 원천 차단해야 한다.

세션 관리와 방어 심화

토큰 기반 인증이라도 세션 관점의 통제는 여전히 필요하다. 상태 없는 JWT의 단점은 즉시 무효화가 어렵다는 것이다. 사용자가 로그아웃하거나 계정이 탈취됐을 때 발급된 토큰을 만료 전에 취소하려면, 토큰 식별자를 관리하는 거부 목록이나 짧은 만료 시간과 리프레시 조합이 필요하다. 민감한 작업에는 재인증을 요구하고, 비정상적인 위치나 기기에서의 접근을 탐지하는 계층을 더하면 방어가 두터워진다.

방어를 심화하려면 다층 접근이 필수다. 모든 통신은 반드시 최신 TLS로 암호화하고, 보안 헤더를 설정해 브라우저 차원의 보호를 활성화한다. 로그인 시도에는 속도 제한과 계정 잠금을 적용해 무차별 대입 공격을 막고, 비밀번호는 최신 해싱 알고리즘으로 저장한다. 무엇보다 다중 인증을 도입하면 자격 증명이 유출되어도 계정 탈취를 막을 수 있다. 인증 시스템은 한 번 만들고 잊는 영역이 아니라, 새로운 공격 기법과 표준 변화에 맞춰 지속적으로 점검하고 갱신해야 하는 살아 있는 방어선이다. 정기적인 보안 감사와 의존성 취약점 스캔을 파이프라인에 내재화하는 것이 실질적인 안전의 토대가 된다.

인가와 최소 권한 원칙

인증이 사용자가 누구인지 확인하는 것이라면, 인가는 그 사용자가 무엇을 할 수 있는지 통제하는 것이다. 많은 침해 사고가 인증 자체보다 허술한 인가에서 비롯된다. 대표적인 취약점이 안전하지 않은 직접 객체 참조로, 사용자가 URL의 식별자를 바꿔 다른 사람의 데이터에 접근하는 공격이다. 방어책은 모든 리소스 접근 시 해당 사용자가 그 리소스에 대한 권한을 실제로 가졌는지 서버에서 검증하는 것이다. 프런트엔드에서 버튼을 숨기는 것만으로는 결코 충분하지 않으며, 권한 검사는 반드시 서버의 모든 진입점에서 이뤄져야 한다.

권한 모델은 최소 권한 원칙에 따라 설계해야 한다. 각 사용자와 서비스에는 업무 수행에 꼭 필요한 최소한의 권한만 부여하고, 광범위한 관리자 권한은 엄격히 제한한다. 역할 기반 접근 제어는 권한을 역할 단위로 묶어 관리 복잡도를 낮추는 검증된 방식이며, 더 세밀한 통제가 필요하면 속성 기반 접근 제어로 문맥까지 고려한 정책을 표현할 수 있다. 토큰에 담기는 권한 범위, 즉 스코프는 필요한 API에만 접근하도록 좁게 설정해, 토큰이 유출되어도 피해 범위를 제한한다.

마지막으로 모든 인증과 인가 이벤트는 감사 로그로 남겨야 한다. 로그인 성공과 실패, 권한 변경, 민감한 데이터 접근을 기록하면 이상 징후를 조기에 탐지하고 사고 발생 시 원인을 추적할 수 있다. 다만 로그에 비밀번호나 토큰 원문 같은 민감 정보가 섞이지 않도록 주의해야 한다. 이상 탐지 시스템과 연동해 비정상적인 접근 패턴을 자동으로 경보하고, 침해가 의심되면 관련 세션과 토큰을 신속히 무효화하는 대응 절차를 미리 마련해두는 것이, 방어의 마지막 안전망이 된다.