아래 그림들은 카카오 소셜 로그인의 흐름을 정리한 그림이다. 인터넷에 나와있는 각종 그림들을 보고 내 식대로 정리한 것이다.
또한 이런 흐름은 하나만 정답이 아니다.
나는 다음 게시글에서 많은 참고를 했다.
Spring Security의 OAuth를 이용하지 않는 경우
이 흐름은 8번 과정에서 리다이렉트가 프론트엔드로 요청돼 9번 과정이 프론트엔드로 연결되는 흐름이다.
다음 프로젝트가 위와 같은 흐름으로 인증과정이 작성되었다.
https://github.com/OOTW-hongik/ootw-backend
설명
- 먼저 사용자가 로그인 버튼을 누른다. 프론트엔드의 "/login" 에 요청이 간다.
- 프론트엔드에서 백엔드 서버의 endpoint : "/oauth/kakao" 로 리다이렉트하라는 응답을 보낸다.
- 사용자는 즉시 백엔드 서버의 endpoint : "/oauth/kakao" 로 리다이렉트된다. 보통 사용자는 백엔드 서버와 직접적으로 통신하는 일이 거의 없다. 하지만 이 경우만 직접적으로 통신한다.
- 백엔드는 카카오에 등록된 내 어플리케이션의 client_id를 가지고 있다. 카카오 서버의 내 어플리케이션 소셜 로그인 페이지로 리다이렉트하도록 응답한다.
- 사용자는 즉시 카카오 인가 서버의 endpoint : "oauth/authorize" 로 리다이렉트 되고, 쿼리 파라미터로 client_id가 전달된다. 마찬가지로 보통 사용자는 카카오 서버와 직접적으로 통신하는 일이 거의 없다. 하지만 이 경우만 직접적으로 통신한다.
- 5번에 대한 응답으로 카카오 서버가 내 어플리케이션 소셜 로그인 페이지를 응답한다.
- 사용자는 로그인 페이지에 자신의 카카오 계정 정보를 입력해 로그인을 요청한다.
- 계정 정보가 맞다면, 카카오 서버는 미리 등록된 redirect URI로 리다이렉트 시킨다. 이 때, 쿼리 파라미터로 authorization code가 전달되도록 한다.
- 사용자는 즉시 프론트엔드의 endpoint : "/oauth/redirected/kakao" 로 리다이렉트된다. 쿼리 파라미터로 "code=..." 의 형식으로 authorization code가 전달된다.
- 프론트엔드는 백엔드의 endpoint : "/oauth/login/kakao" 로 Get 요청을 보낸다. 이 때 쿼리 파라미터로 authorization code를 그대로 보낸다.
- 백엔드는 이제 실제 카카오 리소스 서버에 요청을 위한 Access Token을 받아오기 위한 Post요청을 한다.
이 때, 형식에 맞춰 보내야 하는데, 여기엔 grant_type, client_id, code 등이 있다. - 카카오 인가 서버는 카카오 리소스 서버에 요청을 할 수 있는 Access Token 등을 응답으로 넘겨준다.
백엔드에서는 Access Token으로 유효기간 등을 설정해 JWT를 생성한다. 이 때, 만약 회원이 아니라면 회원정보를 가져오는 요청을 추가로 해야 한다. 회원이라면 13, 14번을 건너뛰고 15번으로 가게 된다.
참고 : 카카오 서버에서 넘어오는 Access Token 은 JWT가 아니다. 그냥 평문이다. - 회원정보를 가져오기 위해 카카오 리소스 서버의 endpoint : "/v2/user/me" 에 Get 요청을 보낸다. 이 때, Access Token을 당연히 같이 보내야 하는데 Header 중 authorization에 bearer 타입으로 담아서 보낸다.
- 카카오 리소스 서버는 id와 사용자가 정보 제공 동의한 정보들을 응답해준다. 백엔드는 이 정보로 회원가입을 진행한다.
- 백엔드는 프론트엔드에 JWT를 응답으로 보내주며 로그인 처리를 완료한다.
이 중 12 ~ 15번 과정의 코드이다.
public String login(OauthServerType oauthServerType, String authCode) throws JsonProcessingException {
Member member = oauthMemberClientComposite.fetch(oauthServerType, authCode);
Member saved = memberRepository.findByOauthId(member.getOauthId())
.orElseGet(() -> memberRepository.save(member));
String jws = JwtUtils.createToken(saved);
Map<String, String> map = new HashMap<>();
map.put("jwt", jws);
return new ObjectMapper().writeValueAsString(map);
}
외부 통신을 통해 Member 를 불러온다. 해당 멤버가 있으면 그 멤버를 불러오고, 없다면 회원가입시킨 후 불러온다. 그 후 그 멤버를 통해 JWT를 생성하고 그 Access Token 을 응답해준다.
(자세한 코드는 위의 프로젝트를 참고)
Spring Security의 OAuth 이용
위의 방법은 Spring Security를 사용하지 않고 직접 통신하는 코드를 짜면서 인증 및 인가를 했다. 하지만 Spring Security 에 OAuth 를 직접적으로 지원해준다.
하지만, 조심해야 할 것은 이러면 8번 과정에서 리다이렉트가 무조건 백엔드로 연결되어 9번 과정에서 백엔드로 연결된다. 따라서 14번에서 프론트엔드의 특정 주소로 Access Token 과 함께 리다이렉트 시키면서 발급해줘야 한다.
다음 프로젝트가 위와 같은 흐름으로 인증과정이 작성되었다.
https://github.com/BookitList/BookitList_backend
주의해야 할 점은 Spring Security 의 OAuth는 기본적으로 세션 방식을 사용하기 때문에 JWT를 사용할 경우 이 옵션을 꺼줘야 한다. 이 경우 SecurityConfiguration 클래스의 일부분이다.
http
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.oauth2Login(oauth2 -> oauth2
.userInfoEndpoint((userInfo) -> userInfo.userService(customOAuth2UserService))
.successHandler(oAuth2AuthenticationSuccessHandler));
- sessionManagement 메소드로 세션 방식을 사용하지 않는 것을 설정해야 한다.
- 또한 13번 과정 이후에 카카오 리소스 서버에서 가져온 정보들로 멤버를 가입시키거나 업데이트 시키는 등의 로직을 userInfoEndpoint 메소드를 통해 작성할 수 있다.
- 13번 이후까지 모두 끝났으면 successHandler 메소드를 통해 어떤 식으로 인가된 토큰 등을 발급할 것인지 작성할 수 있다. 우리는 리다이렉트 시키는 코드를 작성하면 되는 것이다.
OIDC는 언제?
위에 언급한 것들 중, 카카오 인가 서버에서 가져온 Access Token 은 JWT가 아니라는 언급을 했다. 여기서 문제가 하나 발생한다.
만약 Access Token 이 JWT라면 양쪽 모두 같은 시크릿 값을 가지고 있어야 하기 때문에 상대방을 믿을 수 있다. 다시 말해, Trudy는 서비스가 가진 시크릿을 모르기 때문에 JWT를 생성할 수 없다.
즉, JWT가 아닌 Access Token은 악의를 가진 사람(Trudy)이 요청을 중간에 가로채 카카오 인가 서버인 척 Access Token 을 응답할 수도 있다. (검증 과정이 없는 평문이기 때문에!)
반면 OIDC는 JWT이다. 따라서 Trudy가 존재할 수 없다.
이해가 되지 않는다면 다음 게시글을 참고하자.
https://morenow.tistory.com/56
따라서 OIDC를 사용하는 것을 권장한다.
OIDC란?
그럼 OIDC는 무엇일까?
OIDC를 사용하면 위의 그림들 중에서 카카오 인가 서버의 Access Token 을 받아오는 대신, Id Token 을 받아온다.
Id Token 는 내가 원하는 사용자의 정보를 모두 담고 있는, JWT이다. 흐름은 다음과 같다.
- 카카오 서버에 Id Token을 요청해 받아온다.
- Id Token을 검증해 송신 측이 카카오 서버라는 것을 확인한다.
- ID Token 안의 정보가 내가 원하는 정보(사용자 이름, 이메일 등 개인정보)이므로 내용을 그대로 쓰면 된다.
여기에서는 다음과 같은 2가지 이점이 있다.
통신 횟수 감소
사용자의 정보를 모두 담고 있는 Id Token 을 이미 받았기 때문에 카카오 리소스 서버와 다시 통신할 필요가 없다. 즉, 통신 횟수가 절반으로 줄어드는 것이다.
보안 강화
카카오와 서비스 모두 같은 시크릿 값을 가지고 Id Token을 생성 및 파싱해야 하기 때문에 Id Token 의 생성자를 신뢰할 수 있게 된다. 이전에 설명한 문제를 해결한다.
OIDC 는 OAuth 에 기반한 표준 인증 프로토콜이다. 하지만 제공하지 않는 소셜 로그인 제공자도 많다. 대표적으로 네이버 소셜 로그인 시 OIDC를 제공하지 않는다.
'Spring Boot' 카테고리의 다른 글
Spring의 새로운 동기식 HTTP Client, RestClient (0) | 2024.03.09 |
---|---|
JWT 총정리 (2) - Access Token, Refresh Token (1) | 2024.03.08 |
JWT 총정리 (1) - JWT의 원리와 흐름 (1) | 2023.10.09 |
Git in IntelliJ - 인텔리제이에서 Git의 모든 것 (2) (0) | 2023.02.03 |
Git in IntelliJ - 인텔리제이에서 Git의 모든 것 (1) (0) | 2023.01.27 |