안녕하세요. J4J입니다.
이번 포스팅은 naver 로그인 spring을 활용하여 구현하는 방법에 대해 적어보는 시간을 가져보려고 합니다.
들어가기에 앞서
동일한 방식의 다른 서비스 로그인을 활용하는 방법에 대해 궁금하신 분들은 다음을 참고해 주시면 됩니다.
[Next] Kakao 로그인 Spring을 활용하여 구현하기
[Next] Apple 로그인 Spring을 활용하여 구현하기
Next만을 이용하여 naver 로그인을 구현하는 방법에 대해 궁금하신 분들은 다음을 참고해 주시면 됩니다.
애플리케이션 등록
naver를 이용하여 로그인을 구현하기 위해서는 먼저 사용하려는 서비스 애플리케이션 정보를 등록해줘야 합니다.
다음 절차들을 통해 애플리케이션을 등록해보겠습니다.
[ 1. naver developer 접속 ]
[ 2. 애플리케이션 등록하기 ]
[ 3. 애플리케이션 정보 등록 ]
애플리케이션 정보를 등록해줘야 하는 항목은 총 3개입니다.
애플리케이션 이름은 사용하려는 서비스에 맞게 자유롭게 입력해 주시면 됩니다.
사용 API는 사용자가 로그인을 했을 때 개발자가 활용할 수 있는 정보들이니 필요한 정보들에 대해 필수 or 추가 (선택) 을 선택해 주시면 됩니다.
여기서 선택한 정보들은 테스트할 때는 문제가 없지만 나중에 서비스 검수가 들어갈 때 실제 사용되고 있는 정보들인지를 증빙해야 되니 참고하시면 될 것 같습니다.
그리고 바로 아래를 보면 로그인 서비스 환경을 등록해주셔야 합니다.
Next를 이용해 개발되기 때문에 PC 웹을 선택하여 서비스 URL에는 도메인, Callback URL에는 네이버 로그인이 이루어진 뒤 redirect가 될 서비스 페이지 url을 등록해 주시면 됩니다.
참고사항으로 애플리케이션을 등록할 때 서비스 URL을 하나만 입력할 수 있습니다.
하지만 개발하다 보면 운영서버, 개발서버, 로컬서버가 기본적으로 사용되고, 만약 운영서버를 서비스 URL에 등록했다면 개발 및 로컬에서 테스트가 불가해집니다.
이럴 때 보완할 수 있는 방법 중 하나로 아래와 같이 Mobile 웹으로 하나 더 등록하여 최대 2개의 도메인까지 활용한다면 유동적으로 업무 처리를 해줄 수 있습니다.
[ 4. 애플리케이션 키 확인 ]
애플리케이션 등록을 완료하면 다음과 같이 Client ID, Client Secret 값을 확인할 수 있습니다.
이후 소스코드를 작성할 때 사용되니 참고해 주시면 됩니다.
[ 5. 멤버관리 ]
네이버 로그인 검수요청이 완료되지 않은 애플리케이션은 다음과 같이 개발 상태가 개발 중으로 나오는 것을 볼 수 있습니다.
개발 상태가 개발 중인 경우에는 해당 애플리케이션 키 값을 이용하여 로그인을 구현할 때 애플리케이션을 등록한 사용자만 정상 로그인이 가능합니다.
만약 개발자 외 테스터 계정 등록이 필요하시다면 멤버관리 탭으로 넘어가 테스터 ID를 등록해 주셔야 정상 로그인이 가능하니 해당 부분도 참고해 주시길 바랍니다.
Next
애플리케이션 설정이 모두 완료되었다면 next 코드를 작성해 보겠습니다.
먼저 로그인 버튼이 보이는 페이지는 다음과 같이 코드를 작성해 볼 수 있습니다.
참고사항으로 네이버에서는 서비스 검수를 할 때 추천하는 로그인 버튼 UI를 구성해야 되는 조건이 있습니다.
관련 내용은 로그인 버튼 사용 가이드를 통해 확인할 수 있고, 저는 테스트이기 때문에 해당 조건을 충족시키지는 않았습니다.
import Head from 'next/head';
const Index = () => {
/**
* handle
*/
const handle = {
clickNaverLogin: () => {
const clientId = '_3tRIBHGjSjo_rm44DWf'; // 앱 키 중 Client ID
const redirectUri = 'http://localhost:8088/naver/login'; // 등록한 Callback URL
location.href = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}`;
},
};
return (
<>
<Head>
<title>naver login</title>
</Head>
<div>
<button onClick={handle.clickNaverLogin}>네이버 로그인</button>
</div>
</>
);
};
export default Index;
그리고 로그인이 완료된 후 redirect 되는 페이지는 다음과 같이 코드를 작성해 볼 수 있습니다.
import axios from 'axios';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
interface NaverLogin {
code: string;
}
const Login = () => {
/**
* router
*/
const router = useRouter();
/**
* useEffect
*/
useEffect(() => {
if (router.isReady) {
const code = router.query.code as string; // 네이버에서 query문자열로 넘겨준 로그인 code값 추출
console.log(`code= ${code}`);
login(code);
}
}, [router.isReady]);
/**
* login
*/
const login = async (code: string) => {
const naverLogin: NaverLogin = {
code,
};
const res = await axios.post('http://localhost:8080/api/naver/login', naverLogin); // 스프링 API서버에 code값을 담아 로그인 요청
if (res.data) {
console.log(res.data);
}
};
return <></>;
};
export default Login;
SpringBoot
이번에는 spring 코드를 작성해 보겠습니다.
spring에서 로그인을 위해 controller를 구성하는데 controller는 다음과 같은 동작을 수행합니다.
- code값을 이용하여 naver token값 확인
- naver token을 이용하여 로그인 사용자 정보 추출
코드는 다음과 같이 작성해 볼 수 있습니다.
package com.naver.login.controller;
import com.naver.login.dto.NaverLogin;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.reactive.function.client.WebClient;
import java.util.HashMap;
import java.util.Map;
@RestController
@CrossOrigin("*")
public class NaverLoginController {
private String naverUrl = "https://nid.naver.com";
private String naverApiUrl = "https://openapi.naver.com";
private String clientId = "_3tRIBHGjSjo_rm44DWf"; // 앱 키 중 Client ID
private String clientSecretKey = "CvccjkLNwW"; // 앱 키 중 Client Secret
@PostMapping("/api/naver/login")
public ResponseEntity<Object> naverLogin(@RequestBody NaverLogin naverLogin) {
/**
* code값을 이용하여 token정보 가져오기
*/
// webClient 설정
WebClient kakaoWebClient =
WebClient.builder()
.baseUrl(naverUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
// token api 호출
Map<String, Object> tokenResponse =
kakaoWebClient
.post()
.uri(uriBuilder -> uriBuilder
.path("/oauth2.0/token")
.queryParam("grant_type", "authorization_code")
.queryParam("client_id", clientId)
.queryParam("client_secret", clientSecretKey)
.queryParam("code", naverLogin.getCode())
.build())
.retrieve()
.bodyToMono(Map.class)
.block();
String accessToken = (String) tokenResponse.get("access_token");
/**
* accessToken으로 로그인 사용자가 동의한 정보 확인하기
*/
// webClient 설정
WebClient kakaoApiWebClient =
WebClient.builder()
.baseUrl(naverApiUrl)
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
.build();
// info api 설정
Map<String, Object> infoResponse =
kakaoApiWebClient
.post()
.uri(uriBuilder -> uriBuilder
.path("/v1/nid/me")
.build())
.header("Authorization", "Bearer " + accessToken)
.retrieve()
.bodyToMono(Map.class)
.block();
Map<String, String> infoResponseMap = (Map<String, String>) infoResponse.get("response");
Map<String, String> responseMap = new HashMap<>();
// 이메일 주소 담기
if (StringUtils.hasText(infoResponseMap.get("email"))) {
responseMap.put("email", infoResponseMap.get("email"));
}
// 회원이름 정보 담기
if (StringUtils.hasText(infoResponseMap.get("name"))) {
responseMap.put("name", infoResponseMap.get("name"));
}
// 연락처 이메일 주소 담기
if (StringUtils.hasText(infoResponseMap.get("mobile_e164"))) {
responseMap.put("phoneEmail", infoResponseMap.get("mobile_e164"));
}
// 별명 정보 담기
if (StringUtils.hasText(infoResponseMap.get("nickname"))) {
responseMap.put("nickname", infoResponseMap.get("nickname"));
}
// 프로필 사진 정보 담기
if (StringUtils.hasText(infoResponseMap.get("profile_image"))) {
responseMap.put("profileImageUrl", infoResponseMap.get("profile_image"));
}
// 성별 정보 담기
if (StringUtils.hasText(infoResponseMap.get("gender"))) {
responseMap.put("gender", infoResponseMap.get("gender"));
}
// 생일 정보 담기
if (StringUtils.hasText(infoResponseMap.get("birthday"))) {
responseMap.put("birthday", infoResponseMap.get("birthday"));
}
// 연령대 정보 담기
if (StringUtils.hasText(infoResponseMap.get("age"))) {
responseMap.put("ageRange", infoResponseMap.get("age"));
}
// 출생연도 정보 담기
if (StringUtils.hasText(infoResponseMap.get("birthyear"))) {
responseMap.put("birthyear", infoResponseMap.get("birthyear"));
}
// 휴대전화번호 정보 담기
if (StringUtils.hasText(infoResponseMap.get("mobile"))) {
responseMap.put("phone", infoResponseMap.get("mobile"));
}
// 결과 반환
return ResponseEntity.ok(responseMap);
}
}
테스트
모든 코드 작성이 완료되었다면 서버를 실행하여 Client에 접속해 보겠습니다.
저는 다음과 같은 버튼을 확인할 수 있으며 버튼을 클릭하면 최초 로그인을 하는 경우에만 정보 제공 동의 화면이 나오는 것을 볼 수 있습니다.
동의하기 버튼을 누르면 로그인이 되어 redirect url로 등록해 둔 경로로 페이지가 이동되며 다음과 같은 로그가 출력되는 것을 볼 수 있습니다.
첫 번째 로그는 네이버 로그인이 성공적으로 수행되었을 때 네이버에서 query문자열에 담아준 code값입니다.
두 번째 로그인 code값을 spring API 서버에 전달한 뒤 로그인한 사용자 정보를 추출하여 client에 다시 전달해 준 정보입니다.
이상으로 naver 로그인 spring을 활용하여 구현에 대해 간단하게 알아보는 시간이었습니다.
읽어주셔서 감사합니다.
'SPA > Next' 카테고리의 다른 글
[Next] Next13의 새로운 기능 알아보기 (2) - Data Fetching (0) | 2023.06.04 |
---|---|
[Next] Next13의 새로운 기능 알아보기 (1) - Routing과 RSC (0) | 2023.05.29 |
[Next] Kakao 로그인 Spring을 활용하여 구현하기 (0) | 2023.03.26 |
[Next] Apple 로그인 Spring을 활용하여 구현하기 (0) | 2023.03.20 |
[Next] Data Fetching에 대해 알아보기 (4) - useQuery (0) | 2023.03.05 |
댓글