소셜로그인을 진행하기 위해선 클라이언트(여기서는 모바일앱)에 로그인한 사용자 계정 정보가 필요하다. 그래야 백엔드에 해당 이메일이면 이메일 등 계정 식별자로 회원가입이나 로그인처리를 진행할 수 있다.
크게 보면 다음과 같은 순서를 가진다.
정해진 표준 프로세스가 있는 것은 아니어서 각자 구현 방식에 따라 다르니 참고만.
1. 모바일앱에서 사용자 계정 정보 획득
모바일앱에서 해당 소셜로그인 공급자가 제공한 SDK를 이용하여 사용자 로그인 후 응답값을 벡엔드로 보낸다. 카카오와 네이버의 경우 AccessToken을 발급받을 수 있다. 구글의 경우, AccessToken을 바로 주지 않고 Id Token과 AuthCode라는 걸 받을 수 있다. Id Token은 OpenID Connect 프로토콜에 맞춘 JWT(Json Web Token)이다. AuthCode는 OAuth2 프로세스에 따라 AccessToken을 획득하기 위한 Authorization code를 줄인 표현인 것 같다. 구글의 문서는 여기(https://developers.google.com/identity/openid-connect/openid-connect?hl=ko#java)를 참고하면 된다.
구글이 제품이 많아서 그런지 몰라도 카카오, 네이버보다 문서 양도 방대하고 좀 덜 친절한 느낌이다. 그래서 관련해서 제공하는 모바일 SDK의 Public 메서드를 좀 더 살펴보자. 아래는 GoogleSignInAccount의 문서 캡춰. 내용을 보면 AccessToken은 없고 Id Token과 AuthCode를 가져올 수 있다.
아래는 Google의 Id Token JWT를 파싱한 항목
2. 백엔드에서 모바일앱이 전송한 정보 검증
백엔드는 클라이언트(모바일앱 등)가 준 정보는 절대 그대로 믿으면 안된다. 모바일에서 소셜로그인 후 email 등 백엔드에서 아이디로 사용할만한 정보를 획득할 수 있는데 반드시 이 전송 정보가 올바른지 검증을 따로 해야 한다. 따라서 이 정보가 아니라 카카오, 네이버의 경우 AccessToken을 이용해 사용자 정보를 조회할 수 있는데 이 정보를 백엔드에서 별도로 검증해야 한다. 카카오, 네이버는 사용자 정보를 조회하는 API를 호출해보면 된다.
구글의 경우, AccessToken이 없으므로 AuthCode를 가지고 AccessToken을 요청할수도 있는데 이러면 모바일에서 SDK 부분에서 끝나지 않고 Http Request를 통해 OAuth2 절차를 진행해야 하므로 굳이 이럴 필요는 없고, Id Token을 주기 때문에 위의 캡춰처럼 해당 Id Token에서 이메일 등 필요정보를 취하면 된다. JWT는 구글의 공개키로 검증이 가능하므로 변조가 되었는지 바로 알 수 있다.
이 엔드포인트(https://www.googleapis.com/oauth2/v3/tokeninfo?id_token=ID_TOKEN_HERE)로 쉽게 검증이 가능하다.
유효한 Id Token이라면 200 OK가, 그렇지 않다면 400 Bad Request가 리턴되고 응답 바디에 {"error_description": "Invalid Value"} 가 들어온다. 포맷은 유효하지만 다른 Client Id로 발급한 Id Token을 넣으면 401 Unauthorized가 리턴될 것이다. Id Token의 유효시간은 1시간이다.
백엔드는 클라이언트가 전송한 AccessToken을 굳이 저장하지 않는다. 괜히 사용자의 AccessToken을 가지고 있으면 보안 위협이 늘어날 뿐 특별한 이유가 없다면 백엔드 회원가입 시 사용하고 버리면 된다.
3. 백엔드 회원가입
백엔드에서는 이메일이면 이메일 등 소셜로그인에서 획득한 정보로 회원가입을 진행한다. 이 때, 비밀번호는 비워둬도 되고, UUID같은 임의의 문자열을 넣어도 된다. 직접 아이디/비밀번호로 로그인하지 못하게 막아둔다. 어차피 UUID에 Bcrypt 적용하여 저장해두면 원문이 뭔지 모르고 Brute Force로도 맞추는게 사실 상 불가능하다.
4. 로그인
소셜로그인은 비밀번호가 없다. 따라서 소셜로그인 공급자에 백엔드가 직접 검증한 이메일 같은 백엔드 식별자를 가지고 로그인을 처리한다. 백엔드에서 JWT방식의 인증을 사용한다면 여기서 JWT를 발급한다. 아니 비밀번호가 없는데 어떻게 로그인을 처리할 수 있나 싶지만 생각해보면 소셜로그인 자체가 제 3자가 해당 이메일 등 식별자의 회원을 대신 인증해주는 것이고 이 과정에서 우리는 AccessToken이나 Id Token을 얻는다. AccessToken으로 해당 회원의 말이 모두 사실인지 확인하는 절차를 위에서 거쳤고, AccessToken을 쓰지 않고 Id Token을 쓴다면 이 역시 위에서 보여주는 형식의 JWT다. 서명키가 존재하고 해당 JWT Payload가 위조되면 서명이 유효하지 않다는 걸 바로 알 수 있다. 이게 비밀번호의 일치를 확인하는 신원 증명을 대신하는 거라고 보면 된다.
5. 백엔드 JWT처리
백엔드에서 AccessToken과 RefreshToken을 발급한다면 이제 백엔드 기타 API와 Http Header에 Authorization값에 AccessToken을 싣어 보낸다. AccessToken도 지정 시간이 지나면 만료될텐데 그럼 403 Forbidden이 응답된다. 그럼 RefreshToken을 이용해 AccessToken과 RefreshToken을 갱신한다. 사용자가 오랫동안 접속하지 않아 API호출이 한동안 이뤄지지 않았다면 RefreshToken도 만료되었을 것이다. 그러면 다시 최초 로그인화면으로 사용자를 보내 다시 인증 후 백엔드로부터 JWT를 발급받도록 한다.
만료시간은 각자 서비스의 보안의 중요성과 편의성, 접속 주기 등을 고려해 결정하면 된다. JWT는 별도의 서버를 통해 DB에서 값을 비교하는 방식이 아니기 때문에 한번 발급된 JWT는 만료시킬 수 없다. 탈취되면 만료될때까지 쭉 쓰는거다. 그래서 만료 시간을 짧게 설정하는 것이기도 하다.
물론 중간에 해당 키를 한번 더 검증하거나 사용 중지 장치를 넣을 수 있지만 그러는 순간 JWT의 장점이 사라진다. JWT는 토큰 자체에만으로도 유효성 검증과 서비스에서 필요로하는 키를 내장하고 있기 때문이다.
6. 마무리
회원이 탈퇴를 하려고 할 때 소셜로그인도 끊어줘야 한다. 각 소셜 공급자 별로 회원탈퇴 시 호출해야 하는 URL을 찾아볼 수 있다. 이 때, 대부분 AccessToken이 다시 필요한데 사용자가 소셜로그인을 한번 더 실행하여 본인 증명을 해야 만료되지 않은 AccessToken을 얻고 적절한 URL 호출을 할 수 있다. 처음에 회원 가입할 때 얻었던 AccessToken을 그대로 쓰면 되지 않나 생각하는 분이 계실 수 있지만 그렇게 할 수 없다. 토큰 만료 시간이 있기 때문에 대부분의 경우 회원 가입할 때 얻은 AccessToken은 못쓴다.
'Java & Spring' 카테고리의 다른 글
mapStruct 의존성 순서 문제 해결방법과 간단한 사용법 (0) | 2023.12.11 |
---|---|
Spring에서 DTO 생성 시 Primitive Type과 Reference Type 중 어떤 것을 쓰는게 좋을까? (0) | 2023.12.10 |
댓글