OAuth2.0를 이용한 카카오 로그인 구현하기
- [ Backend ]/Infra
- 2023. 2. 13.
개요
사용자의 인증이 필요한 웹 사이트마다 로그인 기능을 필요로 한다면 민감한 정보에 대한 관리가 필요해지기 때문에 번거로울 수 있고, 보안에 취약해질 수 있다. 따라서 google이나 naver, kakao같은 외부 소셜 서비스를 기반으로 회원가입/로그인 할 수 있는 기능을 많이들 사용하고는 하는데, 이를 위해 접근 권한을 위임받은 것을 OAuth라고 한다.
OAuth를 사용하지 않고 소셜 서비스에 인증을 위임하기 위해서는 사용자는 소셜 서비스의 인증을 위한 수단을 서버에 위임해야 했다. 이는 소셜 서비스 외부로 ID/PW(인증)가 노출되게 되는 문제가 있다. 이렇게 서버가 사용자의 인증을 필요로 하는 이유는 소셜 서비스로의 인가를 필요로 하는 것이 목적이므로, 인증은 사용자에 직접 위임하고, 인가는 소셜 미디어가 맡도록 하는 것이 OAuth이다.
물론 장점만 있는 것은 아니라서, OAuth를 사용하는 웹서버에서 회원정보를 추가로 활용하기 위해서는 사용자 정보를 외부 소셜 서비스 정보와 연동하는 서비스를 별도 구축해야 하기 때문에 구현에 번거로움이 있기는 하다.
인증 Flow
- Resource Owner: Client가 제공하는 서비스를 이용하면서, naver, kakao등의 플랫폼에 회원정보와 리소스를 저장하고 있는 사용자를 말한다.
- Client: Authorization Server에 저장된 리소스를 이용하여 Resource Owner에게 서비스를 제공하려는 주체. 개발자가 다루는 서버에 해당한다. (이름이 client인 이유는 authorization server 입장에서 해석했기 때문에 그렇다)
- Authorization Server: Resource Owner의 회원정보를 가지고, Client를 통해서 인증하면 액세스 토큰을 발급해주는 서버. naver, kakao, google과 같은 대형 플랫폼 서버를 말한다.
1. Resource owner가 Client에 서비스 사용을 요청한다.
2. Client는 Authorization서버로 서비스 사용 요청을 받아서 리다이렉트 해준다.
3. 사용자(Resource owner)가 Authorization Server로 OAuth 로그인을 시도한다.
4. 인증 서버에서는 정상 로그인 여부 판별 후 Resource owner에 맞는 Authorization Code를 콜백해준다. 이 Authorization Code는 인증 요청 단위별로 발급되는 임시 인증코드이다.
-> 1~4: Resource owner의 인증 완료
5. Client에서 Auth서버의 자원을 사용하기 위해 받은 Authorization Code과 Client Secret key를 사용하여 액세스 토큰을 요청한다. Client Secret Key는 클라이언트가 oauth 서비스 사용을 위해 미리 발급받은 코드이다.
6. Auth서버는 전달받은 것들의 유효성을 확인 후에 Auth자원에 접근할 수 있는 권한인 액세스 토큰을 Client로 전달한다.
7,8: 이후에는 Client는 해당 토큰을 사용하여 자원을 요청하여 자원을 얻을 수 있다.
-> 1~8: 권한 부여(AccessToken 획득)
카카오 로그인 사용해보기
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
1. Resource owner가 Client에 서비스 사용을 요청,
2. Client는 Authorization서버로 인증을 위한 Authorization Code와 함께 서비스 사용 요청 전달
3. 사용자(Resource owner)가 Authorization Server로 OAuth 로그인 시도
: Resource Owner가 타 플랫폼을 이용한 로그인을 요청하면 Client는 사용자의 브라우저를 Authorization Server의 인증 페이지로 연결한다.
4. 인증 서버에서는 정상 로그인 여부 판별 후 Authrization Code 콜백(callback)
: 카카오의 경우 쿼리 파라미터형식으로 code값이 콜백된다. code값이 전달되었다는 것은 Authorization 서버에서 로그인 인증처리가 정상적으로 끝났다는 것을 의미하며, 이 code값을 사용해서 accessToken을 요청할 수 있다.
단, 카카오 인증은 되었지만 Client는 아직 Resource Owner의 정보를 받지 않아서 알지 못하는 상태이다.
5. Client쪽에서 Auth서버의 자원을 사용하기 위해 Authorization Code과 Client Secret key를 사용하여 액세스 토큰 요청
6. Auth서버는 전달받은 것들의 유효성을 확인 후에 Auth자원에 접근할 수 있는 권한인 액세스 토큰을 Client로 전달
: 카카오 개발자 페이지에 나와있는대로 필요한 정보를 담아서 POST요청으로 보내주면 된다. 여기서 redirect_uri는 developers사이트에서 카카오 로그인 탭에 설정해둔 Redirect URI를 그대로 입력해주면 된다.
//POST방식으로 key=value 데이터를 요청(카카오쪽으로)
//->Retrofit2, OkHttp, RestTemplate 등 사용가능
@GetMapping("/auth/kakao/callback") //redirect되는 주소 받아주기
@ResponseBody
public String getKakaoCallback(@RequestParam String code) {
RestTemplate rt = new RestTemplate();
//헤더정보 추가를 위한 HttpHeader 오브젝트 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
//HttpBody에 key-value값 추가
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", "-- private client key --");
params.add("redirect_uri", "http://localhost:8000/auth/kakao/callback"); //redirect 지정한 주소
params.add("code", code);
//HttpHeader와 HttpBody를 하나의 오브젝트에 담기
HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest = new HttpEntity<>(params, headers);
//오브젝트를 담아 Http POST로 요청하기
ResponseEntity<String> response = rt.exchange(
"https://kauth.kakao.com/oauth/token", //요청 url
HttpMethod.POST,
kakaoTokenRequest,
String.class //응답받을 타입
);
}
x-www-form-urlencoded형태로 보낸다고 했으므로 HttpHeaders에 명시해주고, MultiValueMap에 key-value형태로 담은 다음 HttpEntity로 HttpHeader와 HttpBody를 하나의 오브젝트로 담은 다음 RestTemplate로 요청하였다.
성공했다면 위와 같은 값들이 response에 담겨서 온다. 여기서 access_token이 resource서버로 자원을 요청할 수 있는 키이고, refresh_token이 토큰 재발급을 요청할 수 있는 토큰이다.
//json으로 오는 응답 받기
//->Gson, Json Simple, ObjectMapper등 사용가능
ObjectMapper objectMapper = new ObjectMapper();
OAuthTokenDto oauthToken = null;
try {
oauthToken = objectMapper.readValue(response.getBody(), OAuthTokenDto.class);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
: 응답은 json으로 왔으므로 dto를 이용하여 받아주었다.
7,8: 이제 받은 토큰을 사용하여 자원을 요청하면 Client는 자원을 얻을 수 있다.
: 카카오 설명 페이지에 보면 Content-type을 application/x-www-form-urlencoded로 보내라고 했으므로 앞서 과정과 유사하게 HttpHeaders와 HttpEntity를 이용하여 헤더에 access token을 넣고 RestTemplate으로 정보를 요청했다.
RestTemplate rt = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer "+oauthToken.getAccess_token());
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest = new HttpEntity<>(headers);
ResponseEntity<String> response2 = rt.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
kakaoProfileRequest,
String.class //응답받을 타입
);
요청 결과는 ResponseEntity를 이용하여 ObjectMapper로 받아주었다.
ObjectMapper objectMapper = new ObjectMapper();
KakaoProfileDto kakaoProfile;
try {
kakaoProfile = objectMapper.readValue(response.getBody(), KakaoProfileDto.class);
} catch (JsonMappingException e) {
throw new RuntimeException(e);
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
토큰 요청때와 동일하게 body를 dto로 받아주고, 해당 dto를 사용하여 원하는 서비스 로직을 작성해주면 된다.
'[ Backend ] > Infra' 카테고리의 다른 글
Spring + Kotlin + Elastic Stack으로 로깅시스템 구축해보기 (0) | 2024.01.08 |
---|---|
쿠버네티스(Kubernetes) 알아보기 (0) | 2023.08.06 |
Docker와 포트포워딩 (0) | 2023.07.17 |
도커(Docker) 알아보기 (0) | 2023.07.14 |
Github Actions를 이용한 CI/CD환경 구축 (0) | 2022.12.28 |