LG U+ 융합프로젝트에서 oauth에 대해서 공부하고 이미 정리해뒀던 소셜로그인과 추천시스템을 구현하는 것을 맡았다.
이에 소셜로그인에 대한 글을 작성하며, 회고를 쓰려고한다.
일단 우리 프로젝트 팀명은 mbtiny이다. 사설 로그인을 만들게 되면, 우리를 믿지못하는 유저들의 이메일등 개인정보를 작성해야할 일이 있다. 하지만 이를 카카오톡에서 관리하면서 사용자들에게 안심을 주기 위하여 사용하였으며, oauth 스펙 중 하나인 OIDC에 대해서 구현하기로 했다.
- 문제점
1.1 Oauth 2.0의 AccesToken으로 생기는 문제점- Open ID Connect
- 적용하기
3.1 공개키 목록 조회
3.2 ID 토큰 유효성 검증
3.2.1 서명 검증전 페이로드 검증
3.2.2 서명 검증
우리 서비스는 부모가 회원가입하고 해당 부모의 자녀를 등록하고 관리하는 시스템이다.
그래서 회원가입이 필요 했고, 회원가입을 할 시 oauth 인증 과정을 수행하게 될 시, code요청을 받게되면 바로 회원가입을 처리를 하되 status를 가입전으로 구분하는 방법을 많이 쓰는 것으로 보인다.
그래서 처음엔 이렇게 하려고 하였으나, 부모가 따로 얻어올 자료가 필요없다고 생각하여 code를 받고 바로 회원가입이 되도록 처리했다.
일단 ouath2.0을 사용을 한다면 카카오톡 서버에서 발급된 AccessToken을 통해서 인증을 거쳐서 유저의 정보를 받아오게 되는데,

이를 보면, Ouath 인증을 통해서 나오는 accesstoken,refreshtoken은 oauth 리소스서버(카카오 사진, 정보)등 에 쓰이는 토큰이며, Oauth 인증 과정을 거쳐 로그인 한 뒤에 얻어오는 토큰은 우리의 애플리케이션에서 인증을 위해 토큰을 발급해주는 것이다.

이렇게 리소스 서버에 accesstoken을 가지고 유저 정보를 얻어오게 된다.
여기서 매우 중요한 부분은 Oauth는 인가!! 즉 리소스의 api를 이용할 수 있는 권한을 얻는 기술이다. 예를 들면 회사에서 제공하는 임시출입증 같은 셈이다. 리소스 서버와 유저간의 연결이 없기에, 리소스 서버 입장에서는 해당 토큰이 인증을 한 유저와 함꼐있는지 알 수가 없는 것이다. 단지 리소스 서버에 접근하는 용도로 쓰이는 것이라 만약 테스트 애플리케이션으로 다른 서버를 만들어서 AccessToken을 기존 서버로 보내도 회원가입이 가능하다는 뜻이다. 한 마디로 어떤 클라이언트에서 발급 받은 accesstoken 이라면 다른 클라이언트에서도 사용할 수 있게된다는 의미이다. 다른 클라이언트는 해당 토큰이 본인이 발급 받은 토큰인지, 외부에서 발근된 토큰인지 파악할 수 없기 때문이다.
그렇기 때문에, 카카오에서는 따로 토큰 정보보기를 요청해서


이렇게 응답값으로 넘어온 app_id가 나의 mbtiny서버의 app_id인지 이차적으로 확인하는 과정이 필요하다.
하지만 이러한 형태는 다른 oauth에서는 사용할 수는 없다. 왜냐하면 토큰 정보보기 같은 api는 카카오에서 편의상 만들어준거지, Oauth의 표준 스펙이 아니다.
OIDC는 사용작 안전하게 로그인하는데 사용할 수 있는 Oauth2.0기반의 표준 인증 프로토콜이다. 표준이라서 구글등 (네이버는 없음) OIDC를 지원하는 곳이라면 다 가능하다. OIDC의 토큰은 Jwt를 따른다.

위 id_token의 페이로드를 보면, 나의 앱키의 정보를 해당 토큰 내에서 얻을 수가있다. 그렇기에 검증된 토큰이라면 나의 mbtiny서버에서 등록한 어플리케이션으로 발급한 id_token이라는 점을 알 수가 있다.
jwt에서 정보를 얻을 수가 있는데, 이는 Jwt,io에 가서 발급된 id_token을 넣어봐도 정보를 얻어올 수 있다.
이를 어케 안전하게 인증된 정보라 알 수 있나??
RSA 암호화 방식으로 공개키를 준다. jwt 암호화 할땐 비밀키로 암호화를 하고, 그 정보를 인증되었는지 확인할 땐 공개키로 복호화를 하는 것이다.

이런식으로, 비밀키로 암호화하고 A->B로 보냈을때 A에 대한 공개키로 열면, A가 보낸 것으로 인증이 가능하다.
근데, 비밀키로 암호화하고 공개키로 이를 열어서 id_token에 있는 정보를 뺴가면 문제가 있는거 아닌가? 생각이 들었다. id_token은 원래 사용자를 식별하는 용도로 발급되며, 민감한 정보는 accesstoken으로 가져오게끔 설계를 하면된다.
해당 로그인 구현은 OIDC를 허용해 코드를 보내 토큰 발급 요청 후, idtoken을 발급 받은 후부터 알려주겠다. 다른 토큰은 굳이 필요없다고 생각이 들어서 idtoken으로 간단한 유저 정보를 가져오고 하기위해 idtoken만 얻도록 했다.
해당 프로젝트에서는 원래 webClient로 카카오 서버 API와 외부 통신을 하였으나, netflix에서 만든? Feign이라는 아주 편한 것이 있다고 해서 Feign를 사용하여 http 통신을 진행하였다.
IdToken을 받았을 때, 인증되었는지에 대해서 확인 하기위해서는 카카오 서버로부터 공개키를 받아서 이로 확인을 해야한다. 공개키를 받아오는 api는 아래와 같다.
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#oidc-find-public-key

위 사진을 보면 n,e를 받아서 공개키를 만들 수가 있다.
kid 또한 중요한데, 공개키가 하나인 것이 아니라 IdToken의 헤더정보를 보면 kid정보가 있으며, 그 kid와 동일한 키 값을 가진 공개키로 id 토큰 유효성 검증을 해야한다.
그래서 왜 공개키가 2개가 응답이 되는 건데? 라고 생각이 들었는데, 이는 서비스에서 사용하는 암호화 키를 바꾸는 작업 때문이다. 이를 "키 롤오버"라고 말하는데, 쉽게 말하자면 키를 주기적으로 새 걸로 교채하는 과정이다.
OIDC는 주기적으로 보안을 위해 공개키를 교체를 한다. 근데 키를 바꾸면 문제가 발생할 수가 있다. 예를 들어 어떤 사용자가 이전 키로 서명된 토큰을 여전히 갖고 있다면 일치하지 않으니 문제가 발생하죠. 그래서 새로운 키로 서명된 토큰이 나오기 전에, 이전키와 새키 둘다 사용가능케 해두기 위한 것이죠.
예를 들면
OIDC 서버가 기존 키 old-key로 토큰을 발급하다가, 보안을 위해 새 키 new-key로 교체하려고 한다.old-key로 발급된 토큰이 아직 유효 기간이 남아 있을 수 있죠.
그래서 서버는 old-key와 new-key 두 키를 모두 공개해서, 어떤 토큰이든 검증할 수 있게 준비해 두는 거다.

이렇게 feign 클라이언트 요청을 통해 손 쉽게 공개키를 가져올 수가 있다.
이에 대한 검증 순서는 아래와 같다.
- ID 토큰의 영역 구분자인 온점(.)을 기준으로 헤더, 페이로드, 서명을 분리
- 페이로드를 Base64 방식으로 디코딩
- 페이로드의 iss 값이 https://kauth.kakao.com와 일치하는지 확인
- 페이로드의 aud 값이 서비스 앱 키와 일치하는지 확인
- 페이로드의 exp 값이 현재 UNIX 타임스탬프(Timestamp)보다 큰 값인지 확인(ID 토큰이 만료되지 않았는지 확인)
- 페이로드의 nonce 값이 카카오 로그인 요청 시 전달한 값과 일치하는지 확인
- 서명 검증
- 헤더를 Base64 방식으로 디코딩
- OIDC: 공개키 목록 조회하기를 통해 카카오 인증 서버가 서명 시 사용하는 공개키 목록 조회
- 공개키 목록에서 헤더의 kid에 해당하는 공개키 값 확인
6번은 뺴고 구현했음.
1~5번은 한번에 처리를 하였음.

2,3,4. 디코딩,iss,aud 체크 - (jwt/util/JwtOIDCUtil.class)


로 1,2,3,4,5는 원큐에 끝난다.

일단 Header에서 kid 값을 가져온다. 공개키 목록에서 쓸 kid를 가져오는 것이다.- (jwt/util/JwtOIDCUtil.class)

공개키를 가져와서 공개키와 kid키가 같은지 검증을 한다. - KakaoOauthHelper.class

<OuathOIDCHelper.class>

<JWTOIDCUtil.calss>

앞에서 검증되지 않은 토큰에서 헤더정보와 payload정보를 가져올 수가 있었다.
Header정보에서 kid를 가져와 공개키를 어느키에 매칭시킬지 구한다.
공개키 목록에서 검증을 진행할 공개키 하나를 구한뒤, n,e를 조합해 공개키를 직접 만들어야함.
만든 공개키로 검증을 진행한뒤에 페이로드를 가져오고나서
정보를 저장하던, 필요한 정보를 카카오 리소스 서버에 더 요청하든 하면된다.
해당 코드는 어떤 분이 올리셔서 그 코드를 참조하였다.
여기서 중요한 것은 Oauth의 accsstoken은 인가를 위한 것이며, idToken은 인증을 위한 것이다.
RSA 공개키 방식으로 토큰 검증을 할 수 있는 idToken을 활용하여 실제 회원가입하기 전 idToken을 통해서 oauth에서 인증된 사용자임을 알수가 있다.
이렇듯 oidc에대해서 레퍼런스가 별로 없기에 구현하는데 시간이 쫌 걸렸다.
자세한 내용은 github를 참고하시길~~
이상!