[java/jsp] 네이버 아이디로 로그인 구현하기

4 분 소요

1. 네이버 아이디로 로그인 링크만들기

  • 먼저 http://localhost:9999/HN-OpenBanking/auth/naver.do 을 클릭하면 팝업창으로 넘어가게 만들었다.
<div class="col-6 col-12-small">

  <h3>다른 서비스로 로그인</h3>
    <a href="http://localhost:9999/HN-OpenBanking/auth/naver.do" 
      onclick="window.open(this.href, '_blank', 'width=500px,height=800px,toolbars=no,scrollbars=no'); return false;">
      <img src="images/naver_login.png" style="width: 50%">
    </a>
    <br>
    <a href="#"><img src="images/naver_login.png" style="width: 50%"></a>
    <br>

</div>
  • 실행 화면

image

  • 코드 원문 : https://github.com/hennylee/kopo-05-web/blob/main/code/HN-OpenBanking/WebContent/login/login.jsp

2. beans.properties 등록

/auth/naver.do=kr.co.hn.controller.NaverLoginController

3. NaverLoginController 생성

package kr.co.hn.controller;

import java.math.BigInteger;
import java.net.URLEncoder;
import java.security.SecureRandom;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;


// api명세 : https://developers.naver.com/docs/login/api/api.md
// 코드 출처 : https://devtansan-s-tocking.tistory.com/16

public class NaverLoginController implements Controller{

	public final static String CLIENT_ID = "h5b3O54T4arCVZ7EKFSR";
	public final static String CLIENT_SECRET = "itSPnyhzQ6";
	public final static String CALLBACK_URL = "http://localhost:9999/HN-OpenBanking/auth/naver/callback.do";
	
	
	@Override
	public String handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		HttpSession session = request.getSession();
		
		String clientId = CLIENT_ID;//애플리케이션 클라이언트 아이디값";
	    String redirectURI = URLEncoder.encode(CALLBACK_URL, "UTF-8");
	    
	    
	    // CSRF 방지를 위한 상태 토큰 생성 코드
	    SecureRandom random = new SecureRandom();
	    String state = new BigInteger(130, random).toString();
	    
	    String apiURL = "https://nid.naver.com/oauth2.0/authorize?response_type=code";
	    apiURL += "&client_id=" + clientId;
	    apiURL += "&redirect_uri=" + redirectURI;
	    apiURL += "&state=" + state;
	    
	    // 상태 토큰은 추후 검증을 위해 세션에 저장되어야 한다.
	    session.setAttribute("state", state);
		
		return "sendRedirect:" + apiURL;
	}

}
  • 가장 먼저 개발자 사이트에서 등록한 client_id, redirect_uri와 생성한 state값(상태토큰)을 가지고 아래 URL로 넘어가도록 해준다.

  • 이때, 반환되는 String이 sendRedirect:로 시작하는데, FrontController 부분에서 sendRedirect:가 붙으면 앞에 reqeust.getContextPath를 붙이지 않고 바로 해당 url로 이동되도록 설정했다.

  • FrontController 코드 : https://github.com/hennylee/kopo-05-web/blob/main/code/HN-OpenBanking/src/kr/co/hn/FrontControllerServlet.java

image

  • 상태 토큰은 다음 단계에서도 사용하기 위해 세션에 저장해둔다.

  • 이동되는 apiURL :

apiURL : https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=h5b3O54T4arCVZ7EKFSR&redirect_uri=http%3A%2F%2Flocalhost%3A9999%2FHN-OpenBanking%2Fauth%2Fnaver%2Fcallback.do&state=428633826614157219736944190684946690114

  • 이 URL로 접속해보면 아래 화면이 뜬다.

image

  • 이 단계에서 로그인을 해주면, http://localhost:9999/HN-OpenBanking/auth/naver/callback.do?code=1pVegeEhXTq4zjDp4856&state=42863382661415721973694419068494669011434 와 같은 URL로 이동된다.

  • 개발자 사이트에 등록해둔 callback URL임을 알 수 있다.

  • 그리고 파라미터로 code, state 가 넘어온다. 이 url을 컨트롤러에서 처리하기 위해서 bean.properties에 해당 url을 등록한다.

4. beans.properties 등록

/auth/naver/callback.do=kr.co.hn.controller.NaverCallbackController

5. AccessToken토큰을 받아서 네이버 사용자 프로필을 조회하는 컨트롤러

  • 이 단계에서는 토큰을 받아서 사용자 프로필을 조회한 후, DB에 저장하는 단계이다.

  • 사용자 프로필은 JSON 형태로 반환되기 때문에 JSON을 파싱해줄 SimplyJSON 라이브러리를 활용했다.

  • 네이버로부터 받아온 id를 기반으로 이미 존재하는 회원이면 로그인 페이지로 전환시키고 존재하지 않는 회원이라면 회원가입 페이지로 넘겨줄 수 있도록 url을 달리 저장했다.

  • url전환은 현재 열려있는 팝업창을 닫고 진행해야 하기 때문에 script만 존재하는 naverJoin.jsp 로 페이지를 먼저 foward시켰다.

package kr.co.hn.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;

import kr.co.hn.dao.LoginDAO;
import kr.co.hn.dao.MemberDAO;
import kr.co.hn.vo.MemberVO;

// api 명세 : https://developers.naver.com/docs/login/profile/profile.md
// 코드 출처 : https://devtansan-s-tocking.tistory.com/17?category=811719

public class NaverCallbackController implements Controller {

	@Override
	public String handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
		
		String msg = "";
		String url = "";

		HttpSession session = request.getSession();
		String code = request.getParameter("code");
		String state = (String) session.getAttribute("state");
		
    
    // 1. 아래 만들어둔 getAccessToken함수를 통해 AccessToken 받기
		JSONObject jsonObj = getAccessToken(code, state);
		
    
    // 2. 받아온 AccessToken이 있다면?
		if (jsonObj != null) {
			
      // 3. 아래 만들어둔 getAuthInfo 메소드를 사용해서 사용자 프로필 정보를 받아온다.
			JSONObject resObj = getAuthInfo(jsonObj);
			
      // 4. 받아온 정보가 있다면?
			if (resObj != null) {
				
        // 5-1. 사용자 프로필 정보를 변수에 저장한다.
				String naverCode = (String)resObj.get("id");
				//String nickname = (String)resObj.get("nickname");
				String name = (String)resObj.get("name");
				String email = (String)resObj.get("email");
				String tel = (String)resObj.get("mobile");
				
				String[] emails = email.split("@");
				String[] tels = (tel.split("-"));
				
        // 5-2. vo객체에 받아온 정보를 담는다.
				MemberVO member = new MemberVO();
				member.setId(naverCode);
				member.setPassword(naverCode);
				member.setEmailId(emails[0]);
				member.setEmailDomain(emails[1]);
				member.setName(name);
				member.setTel1(tels[0]);
				member.setTel2(tels[1]);
				member.setTel3(tels[2]);
				member.setType("U");
				
        // 5-3. 받아온 객체가 회원에 존재하는지 확인한다.
				LoginDAO dao = new LoginDAO();
				MemberVO user = dao.login(member);
				
				// 5-4. 존재하는 회원이 없으면 => 회원가입으로 진입
				if(user == null) {
					url = request.getContextPath() + "/naverJoinForm.do";
					msg = "join";
				}
				// 5.5 존재하는 회원이 있으면 => 로그인으로 진입
				else {
					url = request.getContextPath() + "/loginProcess.do";
					msg = "login";
				}
				
        // 6. 필요한 정보 공유영역에 등록하기
				session.setAttribute("member", member);
				request.setAttribute("msg", msg);
				request.setAttribute("url", url);
				
			}
		}
		
    // 7. 페이지 이동시키기
		return "/member/naverJoin.jsp";
	}
	
	
  // (1) handleRequest에서 사용할 AccessToken을 JSONObject로 받아오는 메소드
  private JSONObject getAccessToken(String code, String state) {

		StringBuilder apiURL = new StringBuilder("https://nid.naver.com/oauth2.0/token?grant_type=authorization_code");

		apiURL.append("&client_id=" + NaverLoginController.CLIENT_ID);
		apiURL.append("&client_secret=" + NaverLoginController.CLIENT_SECRET);
		apiURL.append("&code=" + code);
		apiURL.append("&state=" + state);

		try {

			URL url = new URL(apiURL.toString());
			HttpURLConnection http = (HttpURLConnection) url.openConnection();
			http.setRequestMethod("GET");

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

			// 정상 호출이라면?
			if (responseCode == 200) {
				br = new BufferedReader(new InputStreamReader(http.getInputStream()));
			}
			// 에러가 발생했다면?
			else {
				br = new BufferedReader(new InputStreamReader(http.getErrorStream()));
			}

			String inputLine;
			StringBuffer res = new StringBuffer();

			while ((inputLine = br.readLine()) != null) {
				res.append(inputLine);
			}

			br.close();


			JSONParser parsing = new JSONParser();
			Object obj = parsing.parse(res.toString());
			JSONObject jsonObj = (JSONObject) obj;

			return jsonObj;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;

	}
	
	// (2) 사용자 프로필정보를 JSONObject로 받아오는 메소드
	private JSONObject getAuthInfo(JSONObject jsonObj) throws IOException, ParseException {
		
		String access_token = (String) jsonObj.get("access_token");
		String refresh_token = (String) jsonObj.get("refresh_token");

		String apiURL = "https://openapi.naver.com/v1/nid/me";
		String header = "Bearer " + access_token; // Bearer 다음에 공백 추가
		try {

			URL url = new URL(apiURL);
		
			HttpURLConnection http = (HttpURLConnection) url.openConnection();
			http.setRequestMethod("GET");
			http.setRequestProperty("Authorization", header);
	
			int responseCode = http.getResponseCode();
	
			BufferedReader br;
	
			// 정상호출
			if (responseCode == 200) {
				br = new BufferedReader(new InputStreamReader(http.getInputStream()));
			} else {
				br = new BufferedReader(new InputStreamReader(http.getErrorStream()));
			}
	
			String inputLine;
	
			StringBuffer res = new StringBuffer();
			
			while ((inputLine = br.readLine()) != null) {
				res.append(inputLine);
			}
			br.close();
	
			JSONParser parsing = new JSONParser();
			Object obj = parsing.parse(res.toString());
			JSONObject jsonObj2 = (JSONObject)obj;
			JSONObject resObj = (JSONObject)jsonObj2.get("response");
			
			return resObj;
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	
}
  • 코드 원문 : https://github.com/hennylee/kopo-05-web/blob/main/code/HN-OpenBanking/src/kr/co/hn/controller/NaverCallbackController.java

6. /member/naverJoin.jsp

  • 열려있던 팝업창을 닫고, 이미 존재하는 회원이면 로그인 페이지로 전환시키고 존재하지 않는 회원이라면 회원가입 페이지로 넘겨주도록 수행시켰다.
<%@page import="kr.co.hn.vo.MemberVO"%>
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<script>
	self.close();
	opener.document.location.href="${url}"
</script>
  • 코드원문 : https://github.com/hennylee/kopo-05-web/blob/main/code/HN-OpenBanking/WebContent/member/naverJoin.jsp