[JAVA] Naver Login 구현하기(feat. spring boot)

jeygeon·2023년 1월 24일

spring boot에서 네이버 소셜 로그인을 구현하는 방법을 알아보자

가장 먼저 해야할것은 네이버 개발자페이지에서 애플리케이션을 등록하는것이다.

https://developers.naver.com/apps/#/register

애플리케이션을 등록하면서 URL입력하는 부분이 있는데 서비스URL은 본인이 개발을 진행하고 있는 URL을 입력하면 되고 Callback URL은 나중에 설명하겠지만 네이버API에서 호출하는 URL을 입력하면 된다.
(후에 저 Callback URL을 controller에서 처리하는부분을 개발하면 된다.)

애플리케이션을 등록했으니 개발을 진행하면 되는데 참고로 개발 가이드문서의 링크는 아래와 같다.

https://developers.naver.com/docs/login/devguide/devguide.md#3-4-2-%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%B0%EB%8F%99-url-%EC%83%9D%EC%84%B1%ED%95%98%EA%B8%B0

일단 화면에 클릭하면 controller로 타는 버튼을 하나 만들어놓고

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="/js/main.js" defer></script>
    <script async src="/webjars/jquery/3.6.0/jquery.min.js"></script>
</head>
<body>
    <div>
        <a href="naver-login">
            <button>네이버 로그인</button>
        </a>
    </div>
</body>
</html>
@Controller
@RequiredArgsConstructor
public class LoginController {

    private final LoginService loginService;

    @GetMapping("/naver-login")
    public void naverLogin(HttpServletRequest request, HttpServletResponse response) throws MalformedURLException, UnsupportedEncodingException, URISyntaxException {
        String url = loginService.getNaverAuthorizeUrl("authorize");
        try {
            response.sendRedirect(url);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Service 에서 아까 네이버 가이드문서에 있던대로 요청 URL을 만들어서 호출을 해주었다.
(참고로 URL을 만들때 필요한 저런 값들은 application.yaml에 따로 빼두고 필요할때마다 호출해서 사용하고 있다.)

@Service
@RequiredArgsConstructor
public class LoginService {

    private final ApplicationEnvironmentConfig envConfig;

    public String getNaverAuthorizeUrl(String type) throws URISyntaxException, MalformedURLException, UnsupportedEncodingException {

        String baseUrl = envConfig.getConfigValue("login.naver.baseUrl");
        String clientId = envConfig.getConfigValue("login.naver.clientId");
        String redirectUrl = envConfig.getConfigValue("login.naver.redirectUrl");

        UriComponents uriComponents = UriComponentsBuilder
                .fromUriString(baseUrl + "/" + type)
                .queryParam("response_type", "code")
                .queryParam("client_id", clientId)
                .queryParam("redirect_uri", URLEncoder.encode(redirectUrl, "UTF-8"))
                .queryParam("state", URLEncoder.encode("1234", "UTF-8"))
                .build();

        return uriComponents.toString();
    }
}

가이드에 보면 호출 URL예시가 나와있는데 간단하게 설명을 하자면 호출방식은 GET, POST둘다 가능하다. 나는 GET으로 호출 했는데 호출할때 변수들에 대해서 설명을 하자면 아래와 같다.

response_type : code로 고정
client_id : 애플리케이션을 등록하고 나서 받았던 값
redirect_uri : 애플리케이션을 등록할때 같이 등록했던 callback URL을 URL인코딩한 값
state : 공식문서에 보면 사이트간 요청 위조 공격을 방지하기 위한 상태 토큰값이라고 되어있는데 나는 일단 개발단계에서는 1234로 했다. 이것또한 URL인코딩이 필요하다.

여기까지 하게 되면 사용자가 화면단에서 네이버 로그인 버튼을 클릭했을때 네이버를 로그인 인증 요청 API를 호출하면서 받아오는 응답값이다.
만약 네이버에 로그인이 되어있지 않으면 로그인하는 창으로 이동이 되고 네이버에 로그인이 되어있는상태면 아래와 같이 기본정보 제공 동의 창으로 이동이 된다.

저 창에서 사용자가 동의하기를 클릭하면 애플리케이션 등록을 할때 입력했던 callback URL로 네이버에서 callback응답을 준다.

그리고 저 code필드로 이제 사용자 정보에 접근할수 있는 accessToken을 발급받고 accessToken을 다시 요청보내면 사용자 정보를 응답준다.

아래는 내가 callback을 받고 code를 요청보내서 accessToken을 발급 받은 후 다시 accessToken을 보내 사용자 정보까지 받은것을 구현한 코드이다.

@GetMapping("/oauth/login")
    public void callBack(HttpServletRequest request, HttpServletResponse response, NaverCallback callback) throws MalformedURLException, UnsupportedEncodingException, URISyntaxException, JsonProcessingException {

        if (callback.getError() != null) {
            System.out.println(callback.getError_description());
        }

        String responseToken = loginService.getNaverTokenUrl("token", callback);

        ObjectMapper mapper = new ObjectMapper();
        NaverToken token = mapper.readValue(responseToken, NaverToken.class);
        String responseUser = loginService.getNaverUserByToken(token);
        NaverRes naverUser = mapper.readValue(responseUser, NaverRes.class);

        System.out.println("naverUser.toString() : " + naverUser.toString());
        System.out.println("naverUser.getResonse().getGender() : " + naverUser.getResponse().getGender());
        System.out.println("naverUser.getResonse().getBirthyear() : " + naverUser.getResponse().getBirthyear());
        System.out.println("naverUser.getResonse().getAge() : " + naverUser.getResponse().getAge());

    }
public String getNaverTokenUrl(String type, NaverCallback callback) throws URISyntaxException, MalformedURLException, UnsupportedEncodingException {

        String baseUrl = envConfig.getConfigValue("login.naver.baseUrl");
        String clientId = envConfig.getConfigValue("login.naver.clientId");
        String clientSecret = envConfig.getConfigValue("login.naver.clientSecret");

        UriComponents uriComponents = UriComponentsBuilder
                .fromUriString(baseUrl + "/" + type)
                .queryParam("grant_type", "authorization_code")
                .queryParam("client_id", clientId)
                .queryParam("client_secret", clientSecret)
                .queryParam("code", callback.getCode())
                .queryParam("state", URLEncoder.encode(callback.getState(), "UTF-8"))
                .build();

        try {
            URL url = new URL(uriComponents.toString());
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("GET");

            int responseCode = con.getResponseCode();
            BufferedReader br;

            if(responseCode==200) { // 정상 호출
                br = new BufferedReader(new InputStreamReader(con.getInputStream()));
            } else {  // 에러 발생
                br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
            }

            String inputLine;
            StringBuffer response = new StringBuffer();
            while ((inputLine = br.readLine()) != null) {
                response.append(inputLine);
            }

            br.close();
            return response.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    public String getNaverUserByToken(NaverToken token) {

        try {
            String accessToken = token.getAccess_token();
            String tokenType = token.getToken_type();

            URL url = new URL("https://openapi.naver.com/v1/nid/me");
            HttpURLConnection con = (HttpURLConnection)url.openConnection();
            con.setRequestMethod("GET");
            con.setRequestProperty("Authorization", tokenType + " " + accessToken);

            int responseCode = con.getResponseCode();
            BufferedReader br;

            if(responseCode==200) { // 정상 호출
                br = new BufferedReader(new InputStreamReader(con.getInputStream()));
            } else {  // 에러 발생
                br = new BufferedReader(new InputStreamReader(con.getErrorStream()));
            }

            String inputLine;
            StringBuffer response = new StringBuffer();
            while ((inputLine = br.readLine()) != null) {
                response.append(inputLine);
            }

            br.close();
            return response.toString();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

결과는 아래와 같이 나온다.

일단 여기서는 사용자 정보를 가져오는 것까지만 진행을 했는데 네이버 로그인 개발 가이드문서에 보면 token발급 뿐만 아니라 갱신, 삭제 그리고 oauth를 통해 소셜로그인한 사용자에 대한 DB설계까지 어떻게 진행되어야 되는지 가이드가 잘 되어있으니 꼭 보는것이 좋을 것 같다.

다음에는 테이블 설계와 발급받은 사용자를 저장해서 실제 로그인이 되는 모습을 해봐야겠다

4개의 댓글

comment-user-thumbnail
2023년 6월 11일

혹시 ApplicationEnvironmentConfig 구성요소를 알 수 있을까요?

3개의 답글