Nonce를 이용한 OIDC 보안 향상 구현 ✨

YJ KIM·2025년 4월 2일
0

OIDC 채택 이유

로그인/회원가입 처리를 구현하면서

  1. OAuth2.0을 이용하기
  2. OIDC를 이용하기

두 가지 선택이 있었다.
뭘 사용할까 하다가, 2번을 선택하게 되었다.

[OIDC를 선택한 이유]
OAuth2.0은 인가 프로토콜이지 인증 프로토콜이 아니기 때문에 굳이 OAtuh2.0을 사용할 필요가 없다고 생각했다. 2번 요쳥해야 하기도 했고, OAuth2.0을 이용하면 AT를 서버로 보낼 때 PKCE 처리까지 해야했기 때문에 굳이 사용하지 않았다. 결론적으로 이 상황에서는 OIDC를 쓰는 게 옳다고 생각했다.

구조도

결론적으로 로그인/회원가입 flow는 위의 구조도와 같이 구현했다.

보안 고려사항

✅ Nonce 발급

현재 구조는 OAuth 2.0이 아닌 OpenID Connect (OIDC) 기반이기 때문에, OAuth의 보안 확장인 PKCE(Proof Key for Code Exchange)는 적용하지 않았다.

대신 OIDC에서 중요한 고려 포인트는 다음과 같았다:

"ID Token을 들고 로그인하려는 사용자가, 정말 그 ID Token의 주인인가?"

만약 공격자가 누군가의 ID Token을 탈취하고 로그인 요청을 보낸다면, 서버는 해당 ID Token의 서명만 검증하고 로그인 처리를 해버릴 수 있다.

그 결과, Access Token(AT)과 Refresh Token(RT)가 발급되면서
Replay Attack(재사용 공격)이 성공하게 된다.

이를 방지하기 위해 Nonce를 도입했다.

클라이언트는 로그인을 시작하기 전에 서버로부터 nonce를 발급받는다.
이 nonce는 ID Token 발급 요청에 함께 포함되고, 이후 ID Token의 payload에도 담겨 반환된다.

클라이언트는 이 ID Token을 서버에 전달하고, 서버는 Redis에 저장해두었던 nonce와 비교하여 요청-응답 일치 여부를 확인한다.

검증 결과:

  • nonce가 일치하면 → 로그인 성공 후 nonce 삭제
  • nonce가 일치하지 않으면 → 로그인 실패 및 에러 코드 반환

이렇게 하여 탈취된 ID Token의 재사용을 방지하고, 실제 로그인 요청이 내가 요청한 로그인에 대한 응답임을 확인할 수 있다.

✅ ID Token은 서명된 JWT로 그대로 서버에 전달됨

클라이언트는 로그인 후 ID Token을 직접 복호화하거나 검증하지 않는다. 대신, 서버에서 직접 ID Token의 서명을 검증한다.

이는 "ID Token을 들고온 사용자가 정말 인증된 사용자인가?"를 공식적인 서명 검증 절차로 판단하기 위함이다.

또한, 서버는 IDP(카카오, 구글 등)에서 제공하는 공개키(JWK)를 직접 요청하여 가져오기 때문에, 클라이언트가 복잡한 서명 검증 로직이나 공개키 관리 로직을 갖출 필요가 없다.

결과적으로 클라이언트를 더 단순하게 구현할 수 있고, 중앙화된 서버에서 일괄적으로 검증을 처리함으로써 유지보수성을 높일 수 있었다.

개인적으로 클라이언트에서 요청 시에, 공개키를 또 가져오고 하는 로직보다는 서버에서 저장해놓고 사용하는 게 불필요한 네트워크 요청을 줄일 수 있다는 판단을 했다.


틀린 부분이 있다면, 지적해주시면 너무 감사하겠습니다 🥺

profile
모르면 쓰지 말고 쓸 거면 알고 쓰자

0개의 댓글