본문 바로가기

국비과정/팀프로젝트

[팀플] DongneBook_ 로그인구현 (네이버)

phyho0228@naver.com  네이버계정생성

 

 

 

로그인 방식 오픈 API 호출 예

다음은 네이버 회원 프로필을 조회하는 API를 호출하는 코드를 Java로 작성한 예입니다.

네이버 로그인해서 획득한 접근 토큰을 요청 헤더에 추가해 프로필 조회 API를 RESTful API 방식으로 호출합니다. 반환받은 결괏값은 JSON 형식으로 출력합니다.

 

 

[ 네이버 정보동의 URL 형식 ]
https://nid.naver.com/oauth2.0/authorize?client_id=***(보안주의)****&redirect_uri=http://localhost/login/naver&response_type=code 

 

 

[ LoginController ]

컨트롤러에서 위 URL의 code를 잡아서 아래로 넘어간다.

// 네이버 로그인
		@GetMapping("/login/naver")
		public String naverLogin(@RequestParam(required = false) String code, HttpSession session, Model model) {
			
			// URL에 포함된 code를 이용하여 액세스 토큰 발급
			//System.out.println(code);	
			String Naccess_Token = loginService.getNaverToken(code);
			Map<String, Object> nUser = loginService.getNaverUser(Naccess_Token); // 서비스에서 리턴한 Nmap값을 nUser로 받음.
			//System.out.println(nUser);
			
			// 네이버 로그인기록 확인
			int result = loginService.hasNaverUser(nUser); // 0 또는 1
			
			if(nUser != null) {		// 네이버 연결성공
				
				// db에 naver 계정정보 있다면 로그인진행
				if(result == 1) {
					session.setAttribute("mid", nUser.get("Nid"));
					session.setAttribute("withN", "2");	// 로그아웃시 활용
					return "redirect:/";
					
				} else {	
					// db에 naver계정정보 없다면 생성(id&email&name&phone) => subjoin에서 진행
					session.setAttribute("mid", nUser.get("Nid")); 		// Nid 세션에 저장
					session.setAttribute("withN", "2");					// 로그아웃시 활용
					
					model.addAttribute("memail", nUser.get("Nemail"));	// db에 넣을 추가정보들
					model.addAttribute("mname", nUser.get("Nname"));
					model.addAttribute("mphone", nUser.get("Nphone"));
					return "subjoin";
				}
			}else {
				return "redirect:login";
			}
		}

네이버 정보동의하면 넘어가는 URL
http://localhost/login/naver?code=pXB6I5yuFLdX88YdXO&state=null

 

[ LoginService ]

package com.book.web.login;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.lang.reflect.Type;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;

@Service
public class LoginService {

	@Autowired
	private LoginDAO loginDAO;
	
    
	// 네이버에 코드전송 & 인증토큰받기
	public String getNaverToken(String Ncode) {

		String Naccess_Token = "";
		String Nrefresh_Token = "";
		String NreqURL = "https://nid.naver.com/oauth2.0/token";

		 try{
	          URL url = new URL(NreqURL);
	          HttpURLConnection conn = (HttpURLConnection) url.openConnection();

	          //POST 요청을 위해 기본값이 false인 setDoOutput을 true로
	          conn.setRequestMethod("POST");
	          conn.setDoOutput(true);

	          //POST 요청에 필요로 요구하는 파라미터 스트림을 통해 전송
	          BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
	          StringBuilder sb = new StringBuilder();
	          sb.append("grant_type=authorization_code");
	          sb.append("&client_id=hnntl6BcuuFp5qf4vMAt"); // TODO Client ID 입력
	          sb.append("&client_secret=S2tSUIOot4"); // TODO Client Secret 입력
	          sb.append("&code=" + Ncode);
	          sb.append("&redirect_uri=http://localhost/login/naver"); // TODO 인가코드 받은 redirect_uri 입력
	          bw.write(sb.toString());
	          bw.flush();

	          //결과 코드가 200이라면 성공
	          int NresponseCode = conn.getResponseCode();
	          System.out.println("NresponseCode : " + NresponseCode);
	          
	          //요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
	          BufferedReader Nbr = new BufferedReader(new InputStreamReader(conn.getInputStream()));
	          String Nline = "";
	          String Nresult = "";

	          while ((Nline = Nbr.readLine()) != null) {
	        	  Nresult += Nline;
	          }
	          //System.out.println("Nresponse body : " + Nresult);

	          //Gson 라이브러리에 포함된 클래스로 JSON파싱 객체 생성
	          JsonParser parser = new JsonParser();
	          JsonElement element = parser.parse(Nresult);

	          Naccess_Token  = element.getAsJsonObject().get("access_token").getAsString();
	          Nrefresh_Token = element.getAsJsonObject().get("refresh_token").getAsString();

	         //System.out.println("Naccess_token : " + Naccess_Token);	
	         //System.out.println("Nrefresh_token : " + Nrefresh_Token);
	          
	          Nbr.close();
	          bw.close();
	          
	      }catch (Exception e) {
	          e.printStackTrace();
	      }

	      return Naccess_Token;
	}

      
	 // 네이버에 토큰전송 & 사용자정보받기
	   public  Map<String, Object> getNaverUser(String Naccess_token){

		   Map<String, Object> Nmap = new HashMap<>();
		   
	        String reqURL = "https://openapi.naver.com/v1/nid/me";
	        String id="";
	        String email = "";
	        String nickname = "";
	        String mobile = "";

	        //access_token을 이용하여 사용자 정보 조회
	        try {
	            URL url = new URL(reqURL);
	            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

	            conn.setRequestMethod("POST");
	            conn.setDoOutput(true);
	            conn.setRequestProperty("Authorization", "Bearer " + Naccess_token); //전송할 header 작성, access_token전송

	            //결과 코드가 200이라면 성공
	            int responseCode = conn.getResponseCode();
	            System.out.println("responseCode : " + responseCode);

	            //요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
	            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
	            String line = "";
	            String result = "";

	            while ((line = br.readLine()) != null) {
	                result += line;
	            }
	            //System.out.println("response body : " + result);
	            //response body : {"resultcode":"00","message":"success",

	            // 네이버 유저정보 받아오기
	            Gson gson = new Gson();
	            JsonObject jsonObject = gson.fromJson(result, JsonObject.class);
	            JsonElement element = jsonObject.get("response");
	            
	            // 받아올값 : id, nickname, email, mobile
	            id = element.getAsJsonObject().get("id").getAsString().substring(0, 5);
	            email = element.getAsJsonObject().get("email").getAsString();
	            nickname = element.getAsJsonObject().get("nickname").getAsString();
	            mobile = element.getAsJsonObject().get("mobile").getAsString();
	            
	            // Map에 담기
	            Type type = new TypeToken<Map<String, Object>>() {}.getType();
	            Nmap = gson.fromJson(element, type);

	            Nmap.put("Nid", id);
	          	Nmap.put("Nemail", email);
	          	Nmap.put("Nname", nickname);
	          	Nmap.put("Nphone", mobile);
	            
	            br.close();

	        } catch (Exception e) {
	            e.printStackTrace();
	        }
	        return Nmap;
	    }
    


	public int hasNaverUser(Map<String, Object> nUser) {
		return loginDAO.hasNaverUser(nUser);
	}




}

[ LoginDAO ]

package com.book.web.login;

import java.util.Map;

import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface LoginDAO {


	int hasNaverUser(Map<String, Object> nUser);

}

[ loginMapper ]

	<select id="hasNaverUser" parameterType="Map" resultType="Integer">
		SELECT count(*)  
		FROM members
		WHERE memail=#{Nemail} AND mconnect=2
	</select>

 


[ login.jsp ] 

 

script 부분

<script type="text/javascript">

function naverLogin(){
	location.href="https://nid.naver.com/oauth2.0/authorize?client_id=****보안주의****&redirect_uri=http://localhost/login/naver&response_type=code";
}

</script>

 body부분 

<body data-spy="scroll" data-target=".onpage-navigation" data-offset="60">
  <%@ include file="menu.jsp"%>
  
	<div class="Lcontainer" align="center">
		<div class="login-form" align="center">
			<form action="./Glogin" method="post" id="frm">
				<div class="form-item idBox">
					<input type="text" name="id" id="id" placeholder="아이디를 입력하세요">
				</div>
				<div class="form-item pwBox">
					<input type="password" name="pw" id="pw" placeholder="패스워드를 입력하세요">
				</div>
				<div class="saveBox">
					<div class="form-item saveIDBox">
						<input type="checkbox" id="saveID"> <label for="saveID">아이디저장</label>
					</div>
					<div class="form-item saveAllBox">
						<input type="checkbox" id="saveAll"> <label for="saveAll">자동로그인</label>
					</div>
				</div>

				<div class="form-item loginBtnBox">
					<button type="button" id="loginbtn">로그인</button>
				</div>
			</form>
		</div>

		<div class="login-with" align="center">
			<div id="kbtnBox">
				<img src="img/login/login_kakaoBtn.png" alt="kakaoBtn" id="kakao" onclick="kakaoLogin()">
			</div>
			<div id="nbtnBox">
				<img src="img/login/login_naverBtn.png" alt="naverBtn" id="naver" onclick="naverLogin()">
			</div>
		</div>
		
		<div class="forUserBox" align="center">
			<a href="./finduser">아이디 & 비밀번호 찾기 </a> |
			<span></span>
			<a href="./join"> 회원가입</a>
		</div>
	</div>
</body>

아래의 로그인 페이지에서 naver 로고 버튼을 클릭하면

아래의 네이버 로그인 창으로 이동한다.

 

네이버 아이디&비밀번호를 입력하고 로그인 버튼을 누르면 해당 계정에 대한 네이버 인증코드 요청이 된다.

                                                                                                                 이때 전송되는 url에 client_id가 포함되어 있다.

 

컨트롤러에서 url의 코드값을 code라는 이름의 변수로 잡아온다. (code형식 : t9ke0IO0SFl5aNin)

이 code를 매개변수로 서비스에 getNaverToken() 메소드 실행.

 

서비스에서 이 code를 Ncode로 받아 형식에 맞춰서 다시 네이버에 토큰요청을 보낸다.

                                 이때 client_id 와 client_secret을 함께 보낸다.

 

토큰요청에 대해 NresponseCode 가 200이면 성공. => 네이버에서는 요청값을 JSON형태로 제공해준다.

전달받은 토큰을 String 형태의 Naccess_Token & Nrefresh_Token  변수로 받아서 리턴!

                                              일단은 Naccess_Token 만 사용했다.

 

컨트롤러에서 다시 이 토큰을 매개변수로 getNaverUser() 메소드 실행

실행결과값은 네이버 사용자 정보로 Map타입의 nUser로 받는다.

서비스에서도 Naccess_Token을 매개변수로 getNaverUser() 를 실행한다.

                                                       url에 토큰을 포함시켜 네이버에 유저정보 요청

유저정보요청에 대해 responseCode 가 200이면 성공. 

Json타입으로 받은 값들을(id, email, nickname, mobile) Map에 담아 Nmap으로 리턴

 

내보낸 Nmap값을 컨트롤러에서 nUser로 받아왔다. (데이터타입은 Map)

uUser를 출력해보면 아래와 같은 형태다. (id는 앞의 5글자만 잘라서 Nid에 넣어줬다.)

{ id=MYTpCxw_Dk2TQEhnP_bIHQfync4EgPoU_ThlOL-NR8,

nickname=phyho, email=phyho0228@naver.com, mobile=010-****-****, mobile_e164=+8210********,

name=(실명), Nid=MYTpC, Nemail=phyho0228@naver.com, Nname=phyho, Nphone=010-****-****}

 

 

** nUser를 db로 보내 일치하는 회원계정(memail)이 있는지 확인할 예정 **

 

컨트롤러에서 nUser 를 매개변수로 hasNaverUser () 메소드 실행

db에 해당 email계정이 있다면(result == 1)  => (세션)로그인처리

db에 해당 email계정이 없다면(result != 1)    => 추가정보 입력후 회원가입(subjoin) & (세션)로그인처리

 

서비스 및 DAO에서도 nUser 를 매개변수로 hasNaverUser () 메소드 실행

 

mapper 에서는 memail이 일치하면서 mconnect = 2 인 계정의 count를 가져왔다.


mconnect는 db에 해당 naver계정이 없을때 즉,  subjoin에서 가입을 완료하면 설정해주는 값이다.

( 위의 subjoin과정은 Join 패키지에서 처리해줬다.)

 

처음 네이버 계정으로 로그인 하면 아래 페이지로 넘겨서 주소를 입력하도록 해줬다.

아이디, 이름, 이메일은 네이버에서 받은 사용자 정보를 넣어 수정불가하도록 만들어줬다.

                                        inputreadonly 적용해서 비활성화 해줬다. (disabled 는 form 전송이 불가능)

[ subjoin.jsp ] 

 

body부분

<body>
		<form action="/login/subjoin" method="post">
		<div class="subjoin-div" align="center">
			<div>
			
				<h1>추가정보입력</h1>
			</div>
			
			<div>
				<label for="mid">아이디</label>
				<div>
					<input type="text" name="mid" id="mid" value="${sessionScope.mid}" disabled/>
				</div>
			</div>
					<label for="mname">이름</label>
					<div>
						<div>
							<c:choose>
           						<c:when test="${mname ne null}">
           							<input type="text" name="mname" id="mname" value="${mname }" readonly/>
           						</c:when>
           						<c:otherwise>
									<input type="text" name="mname" id="mname" placeholder="이름을 입력해 주세요"/>
           						</c:otherwise>
       						</c:choose>
						</div>
					</div>
					<div>
						<label for="memeil">이메일</label>
						<div class="">
							<c:choose>
								<c:when test="${memail ne null}">
									<input type="text" name="memail" id="memail" value="${memail }" readonly/>
								</c:when>
								<c:otherwise>
									<c:choose>
										<c:when test="${sessionScope.withN ne 2}"><span>네이버 계정이 올바른지 확인필요</span></c:when>
										<c:when test="${sessionScope.withK ne 1}"><span>카카오 계정이 올바른지 확인필요</span></c:when>
										<c:otherwise>
											<input type="text" name="memail" id="memail" value="${memail }" disabled/>
 											<!-- <span>올바르지 않은 접근경로<span> -->
										</c:otherwise>
									</c:choose>
								</c:otherwise>
							</c:choose>
						</div>
						<div id="autoInfo"><span>연동된 계정의 정보가 자동으로 입력됩니다.</span></div>
					</div>
					<div>
						<div>
							<label for="maddr">주소*</label>
							<input type="text" name="maddr" id="maddr" placeholder="주소를 입력해 주세요"/>
						</div>
						<div>
							<label for="maddr1"></label>
						    <input type="text" name="maddr" id="maddr1" placeholder="상세주소를 입력해 주세요"/>
						</div>
						<input type="hidden" name="mphone" value="${mphone }"/>
					</div>
		
					<div>
						<button type="button" id="joinCancel">취소</button>
						<button type="submit" id="joinjoin">가입</button>
					</div>

		</div>
	</form>
</body>

 

script 부분

> 취소버튼 눌렀을때 logout 처리

(네이버 로그인 버튼을 누르면 컨트롤러에서 바로 세션에 id&name을 저장, 따라서 subjoin 페이지가 떴을때는 이미 session에 값이 들어가있는 상태다. 취소버튼을 누르면 /logout 으로 이동해서 session값들을 초기화 되도록 해준다. )

> 주소입력 api

<script type="text/javascript">
	$(function(){
		
		$("#joinCancel").click(function(){
			//alert("!");
			window.location.href = "/logout";
		});
		
	});
</script>
<script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
<script type="text/javascript">
window.onload = function(){
    document.getElementById("maddr").addEventListener("click", function(){ //주소입력칸을 클릭하면
        //카카오 지도 발생
        new daum.Postcode({
            oncomplete: function(data) { //선택시 입력값 세팅
                document.getElementById("maddr").value = data.address; // 주소 넣기
              /*   document.querySelector("maddr1").focus(); //상세입력 포커싱 */
            }
        }).open();
    });
}
</script>

style 부분 

<style type="text/css">

.subjoin-div{
	box-sizing : border-box;
	margin: 100px auto;
	padding: 20px;
	font-size:15px;
	background-color:#F2F2F2;
	width: 500px;
}

h1 {
    font-weight: bold; 
    font-size: 40px; 
    color: black;
}

label{
	margin: 15px 15px 15px -10px;
	padding-right: 10px;
    text-align: right;
    vertical-align: middle;
    width: 15%;
	float: left;
}

input{
    background-color:white;
    border-radius: 5px;
    width: 70%;
    height: 50px;
    padding-left: 10px;
 	border:none;
  	outline:none;
}

#mid, #mname, #maddr, #maddr1, #memail{
	margin: 0 10px 20px 5px;
}

#autoInfo{
	margin-left: 100px;
	margin-bottom: 35px;
	text-align: center;
}
#autoInfo span{
	text-align: center;
	color: tomato;
	font-size: small;
}

button{
	background-color: black;
	color: white;
	border: none;
    border-radius: 5px;
    padding: 10px 25px; 
    font-size: 16px;
    cursor: pointer;
}

</style>

 

subjoin에서 가입하기 버튼 눌렀을 떄 JoinController의 아래 로직이 실행된다.

(네이버 로그인 버튼을 누르면 LoginController에서 세션에 id&name&withN(2)을 저장)