공부/Spring

[Spring] 네이버 로그인 API(네아로)을 사용하여 로그인을 하고 세션 저장 해보자!

bumcrush 2019. 5. 10. 23:27
반응형

[Spring] 네이버 로그인 API(네아로)을 사용하여 로그인을 하고 세션 저장 해보자!

-

 

하..  요즘 소셜 로그인 API 공부하고 있는데,

너무 어렵네요..

documentation보고 어떻게 개발 들 하시는지 대단하십니다..

저는 예제가 없으면 손도 못 대겠습니다 ㅠㅠ

 

어쨰튼 여기저기 돌아다니며 얻은 짧은 지식으로

오늘은 Spring으로 네이버 아이디 로그인을 하는 법을 포스팅 하겠습니다.

Spring naver login api 


1. 네이버 애플리케이션 등록

우선 네이버 홈페이지에 들어가서 애플리케이션 등록을 해줍시다!

 

https://developers.naver.com/

 

NAVER Developers

네이버 오픈 API들을 활용해 개발자들이 다양한 애플리케이션을 개발할 수 있도록 API 가이드와 SDK를 제공합니다. 제공중인 오픈 API에는 네이버 로그인, 검색, 단축URL, 캡차를 비롯 기계번역, 음성인식, 음성합성 등이 있습니다.

developers.naver.com

 

naver Developers 홈페이지

naver Devlopers 홈페이지 접속 후 로그인 > Application > 애플리케이션 등록 클릭!

 

 

 

애플리케이션 이름을 입력합니다.

저는 'naver_login'으로 입력하겠습니다.

 

사용 API의 드롭다운 버튼을 누르고 '네아로' 눌러서

권한 체크 박스를 모두 체크해 주세요!

 

 

 

이어서 '환경 추가 드롭다운' 버튼을 누르고 'PC웹'을 누릅니다.

그리고 나면 서비스URL네아로Callback URL 추가하라고 나옵니다.

 

서비스 URL은 '네이버 아이디로 로그인하는 버튼'이 있는 페이지의 주소를 입력하시면 되고,

Callback URL은 네이버 아이디로 로그인한 뒤 다시 돌아 올 페이지의 주소를 입력해야합니다.

 

모르시겠으면 저랑 똑같이 입력하세요! (나중에도 수정할 수 있습니다.)

이 주소는 뒤에 코드에도 쓰이니 메모장에 저장해주세요~

 

다 입력하셨으면 '등록하기' 버튼 클릭!

 

 

'등록하기'를 누르시면  옆에 '내 애플리케이션' 보시면

아까 등록했던 'naver_login' 애플리케이션이 만들어 진 것을 알 수 있습니다.

누르시면

 

client IDClient Seceret이 있는데,

네이버로 부터 API를 사용할 수 있는 Access Token을 받아 오기 위한 것이라고 알 아 둡시다.

이는 타인에게 노출이 되는 순간 보안 상 위험이 크니 절대 유출되지 않게 조심하세요!

 

client ID와 Client Seceret도 나중에 코드에 쓰이니 메모장에 저장해 둡시다.

 

자! 이제 사전 준비는 끝났습니다. 

이제 사용자로부터 로그인을 받고,

사용자의 정보를 가지고 와서 세션에 닉네임을 저장하는 것을 해 보겠습니다.

spring 소셜 로그인 / naver login api

네이버 아이디로 로그인 하기  / Spring 네아로 / spring 네이버 로그인 api / 네이버 로그인 api / spring 소셜 로그인

 

 


2. 코드 

 

전체적인 구조

전체적인 프로젝트 구조입니다.

Spring Legacy project > Spring MVC Project 로 생성한 프로젝트이며

입력해줘야하는 부분에 노란색으로 칠해 놨습니다.

(되도록 패키지,클래스명 똑같이 해주세요!)

 

 * POM.XML

		<!-- naver -->
		<dependency>
			<groupId>com.github.scribejava</groupId>
			<artifactId>scribejava-core</artifactId>
			<version>2.8.1</version>
		</dependency>

		<!-- 제이슨 파싱 -->
		<dependency>
		    <groupId>com.googlecode.json-simple</groupId>
		    <artifactId>json-simple</artifactId>
		    <version>1.1.1</version>
		</dependency>
		
		<!-- jstl -->
		<dependency>
			<groupId>javax.servlet</groupId>
			<artifactId>jstl</artifactId>
			<version>1.2</version>
		</dependency>

필요한 라이브러리들을 maven에 등록 시켜줍니다.

 

 

* index.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>NAVER LOGIN TEST</title>
</head>
<body>
	<h1>메인 페이지 입니다.</h1>
	<hr>	
	<br>
		<a href="login">로그인 하러 가기 </a>
	<hr>

</body>
</html>

- 메인 페이지 입니다! 

 

 

* login.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>NAVER LOGIN TEST</title>
</head>
<body>
<h1>Login Form</h1>
<hr>	
<br>

<center>
	<c:choose>
		<c:when test="${sessionId != null}">
			<h2> 네이버 아이디 로그인 성공하셨습니다!! </h2>
			<h3>'${sessionId}' 님 환영합니다! </h3>
            	<h3><a href="logout">로그아웃</a></h3>

		</c:when>
		<c:otherwise>
			<form action="login.userdo" method="post" name="frm"  style="width:470px;">
				<h2>로그인</h2>
				  <input type="text" name="id" id="id" class="w3-input w3-border" placeholder="아이디" value="${id}"> <br>
				  <input type="password" id="pwd" name="pwd" class="w3-input w3-border" placeholder="비밀번호" >	<br>
				<input type="submit" value="로그인" onclick="#"> <br>
			</form>
			<br>
			
			<!-- 네이버 로그인 창으로 이동 -->
			<div id="naver_id_login" style="text-align:center"><a href="${url}">
			<img width="223" src="https://developers.naver.com/doc/review_201802/CK_bEFnWMeEBjXpQ5o8N_20180202_7aot50.png"/></a></div>
			<br>
		
		</c:otherwise>
	</c:choose>
	
</center>
</body>
</html>

- index.jsp에서 '로그인 하러 가기' 누르면 오게 되는 뷰단

- 네이버 로그인 버튼이 있는 창

- jstl을 사용하여 세션이 있으면(로그인이 되어있으면) ~~님 환영합니다! 등등 나오고,

 세션이 없으면 로그인 폼과 네아로 버튼이 나타나게 한다.

 

 

 

* servlet-context.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/mvc"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:beans="http://www.springframework.org/schema/beans"
	xmlns:context="http://www.springframework.org/schema/context"
	xsi:schemaLocation="http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

	<!-- DispatcherServlet Context: defines this servlet's request-processing infrastructure -->
	
	<!-- Enables the Spring MVC @Controller programming model -->
	<annotation-driven />

	<!-- Handles HTTP GET requests for /resources/** by efficiently serving up static resources in the ${webappRoot}/resources directory -->
	<resources mapping="/resources/**" location="/resources/" />

	<!-- Resolves views selected for rendering by @Controllers to .jsp resources in the /WEB-INF/views directory -->
	<beans:bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
		<beans:property name="prefix" value="/WEB-INF/views/" />
		<beans:property name="suffix" value=".jsp" />
	</beans:bean>
	
	<context:component-scan base-package="com.test.tst" />
	
	<!-- NaverLoginBO Class에 대한 Bean설정 추가 -->
	<beans:bean id="naverLoginBO" class="com.test.tst.NaverLoginBO" />
	
</beans:beans>

 

 

* NaverLoginApi.java

package com.test.tst;

import com.github.scribejava.core.builder.api.DefaultApi20;

public class NaverLoginApi extends DefaultApi20{

    protected NaverLoginApi(){
    }

    private static class InstanceHolder{
        private static final NaverLoginApi INSTANCE = new NaverLoginApi();
    }


    public static NaverLoginApi instance(){
        return InstanceHolder.INSTANCE;
    }

    @Override
    public String getAccessTokenEndpoint() {
        return "https://nid.naver.com/oauth2.0/token?grant_type=authorization_code";
    }                   

    @Override
    protected String getAuthorizationBaseUrl() {
        return "https://nid.naver.com/oauth2.0/authorize";
    }   
}

 

* NaverLoginBO.java

package com.test.tst;

import java.io.IOException;
import java.util.UUID;

import javax.servlet.http.HttpSession;

import org.springframework.util.StringUtils;

import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.model.OAuth2AccessToken;
import com.github.scribejava.core.model.OAuthRequest;
import com.github.scribejava.core.model.Response;
import com.github.scribejava.core.model.Verb;
import com.github.scribejava.core.oauth.OAuth20Service;

public class NaverLoginBO {
	 	/* 인증 요청문을 구성하는 파라미터 */
		//client_id: 애플리케이션 등록 후 발급받은 클라이언트 아이디
		//response_type: 인증 과정에 대한 구분값. code로 값이 고정돼 있습니다.
		//redirect_uri: 네이버 로그인 인증의 결과를 전달받을 콜백 URL(URL 인코딩). 애플리케이션을 등록할 때 Callback URL에 설정한 정보입니다.
		//state: 애플리케이션이 생성한 상태 토큰
		private final static String CLIENT_ID = "클라이언트 id";
	    private final static String CLIENT_SECRET = "클라이언트 secret";
	    private final static String REDIRECT_URI = "콜백 주소";
	    private final static String SESSION_STATE = "oauth_state";
	    /* 프로필 조회 API URL */
	    private final static String PROFILE_API_URL = "https://openapi.naver.com/v1/nid/me";
	    
	    /* 네이버 아이디로 인증  URL 생성  Method */
	    public String getAuthorizationUrl(HttpSession session) {

	        /* 세션 유효성 검증을 위하여 난수를 생성 */
	        String state = generateRandomString();
	        /* 생성한 난수 값을 session에 저장 */
	        setSession(session,state);        

	        /* Scribe에서 제공하는 인증 URL 생성 기능을 이용하여 네아로 인증 URL 생성 */
	        OAuth20Service oauthService = new ServiceBuilder()                                                   
	                .apiKey(CLIENT_ID)
	                .apiSecret(CLIENT_SECRET)
	                .callback(REDIRECT_URI)
	                .state(state) //앞서 생성한 난수값을 인증 URL생성시 사용함
	                .build(NaverLoginApi.instance());

	        return oauthService.getAuthorizationUrl();
	    }

	    /* 네이버아이디로 Callback 처리 및  AccessToken 획득 Method */
	    public OAuth2AccessToken getAccessToken(HttpSession session, String code, String state) throws IOException{

	        /* Callback으로 전달받은 세선검증용 난수값과 세션에 저장되어있는 값이 일치하는지 확인 */
	        String sessionState = getSession(session);
	        if(StringUtils.pathEquals(sessionState, state)){

	            OAuth20Service oauthService = new ServiceBuilder()
	                    .apiKey(CLIENT_ID)
	                    .apiSecret(CLIENT_SECRET)
	                    .callback(REDIRECT_URI)
	                    .state(state)
	                    .build(NaverLoginApi.instance());

	            /* Scribe에서 제공하는 AccessToken 획득 기능으로 네아로 Access Token을 획득 */
	            OAuth2AccessToken accessToken = oauthService.getAccessToken(code);
	            return accessToken;
	        }
	        return null;
	    }

	    /* 세션 유효성 검증을 위한 난수 생성기 */
	    private String generateRandomString() {
	        return UUID.randomUUID().toString();
	    }

	    /* http session에 데이터 저장 */
	    private void setSession(HttpSession session,String state){
	        session.setAttribute(SESSION_STATE, state);     
	    }

	    /* http session에서 데이터 가져오기 */ 
	    private String getSession(HttpSession session){
	        return (String) session.getAttribute(SESSION_STATE);
	    }
	    /* Access Token을 이용하여 네이버 사용자 프로필 API를 호출 */
	    public String getUserProfile(OAuth2AccessToken oauthToken) throws IOException{

	        OAuth20Service oauthService =new ServiceBuilder()
	                .apiKey(CLIENT_ID)
	                .apiSecret(CLIENT_SECRET)
	                .callback(REDIRECT_URI).build(NaverLoginApi.instance());

	            OAuthRequest request = new OAuthRequest(Verb.GET, PROFILE_API_URL, oauthService);
	        oauthService.signRequest(oauthToken, request);
	        Response response = request.send();
	        return response.getBody();
	    }

}

 - 여기서 아까 메모장에 저장해두라고 했던 client id, client secret, Callback URL 을 입력해줍니다!

 

 

* LoginController.java

package com.test.tst;

import java.io.IOException;

import javax.servlet.http.HttpSession;

import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import com.github.scribejava.core.model.OAuth2AccessToken;

/**
 * Handles requests for the application home page.
 */
@Controller
public class LoginController {

	/* NaverLoginBO */
	private NaverLoginBO naverLoginBO;
	private String apiResult = null;
	
	@Autowired
	private void setNaverLoginBO(NaverLoginBO naverLoginBO) {
		this.naverLoginBO = naverLoginBO;
	}

	//로그인 첫 화면 요청 메소드
	@RequestMapping(value = "/login", method = { RequestMethod.GET, RequestMethod.POST })
	public String login(Model model, HttpSession session) {
		
		/* 네이버아이디로 인증 URL을 생성하기 위하여 naverLoginBO클래스의 getAuthorizationUrl메소드 호출 */
		String naverAuthUrl = naverLoginBO.getAuthorizationUrl(session);
		
		//https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=sE***************&
		//redirect_uri=http%3A%2F%2F211.63.89.90%3A8090%2Flogin_project%2Fcallback&state=e68c269c-5ba9-4c31-85da-54c16c658125
		System.out.println("네이버:" + naverAuthUrl);
		
		//네이버 
		model.addAttribute("url", naverAuthUrl);

		return "login";
	}

	//네이버 로그인 성공시 callback호출 메소드
	@RequestMapping(value = "/callback", method = { RequestMethod.GET, RequestMethod.POST })
	public String callback(Model model, @RequestParam String code, @RequestParam String state, HttpSession session) throws IOException, ParseException {
		
		System.out.println("여기는 callback");
		OAuth2AccessToken oauthToken;
        oauthToken = naverLoginBO.getAccessToken(session, code, state);

        //1. 로그인 사용자 정보를 읽어온다.
		apiResult = naverLoginBO.getUserProfile(oauthToken);  //String형식의 json데이터
		
		/** apiResult json 구조
		{"resultcode":"00",
		 "message":"success",
		 "response":{"id":"33666449","nickname":"shinn****","age":"20-29","gender":"M","email":"sh@naver.com","name":"\uc2e0\ubc94\ud638"}}
		**/
		
		//2. String형식인 apiResult를 json형태로 바꿈
		JSONParser parser = new JSONParser();
		Object obj = parser.parse(apiResult);
		JSONObject jsonObj = (JSONObject) obj;
		
		//3. 데이터 파싱 
		//Top레벨 단계 _response 파싱
		JSONObject response_obj = (JSONObject)jsonObj.get("response");
		//response의 nickname값 파싱
		String nickname = (String)response_obj.get("nickname");

		System.out.println(nickname);
		
		//4.파싱 닉네임 세션으로 저장
		session.setAttribute("sessionId",nickname); //세션 생성
		
		model.addAttribute("result", apiResult);
	     
		return "login";
	}
	
	//로그아웃
	@RequestMapping(value = "/logout", method = { RequestMethod.GET, RequestMethod.POST })
	public String logout(HttpSession session)throws IOException {
			System.out.println("여기는 logout");
			session.invalidate();

	        
			return "redirect:index.jsp";
		}
	
}

 - 컨트롤러 부분입니다.

 - 크게 3부분 으로 나눠져 있습니다.

(1) /login 

- index.jsp에서 '로그인하러 가기' 눌렀을 때 호출

- 네아로 인증URL을 만들어서 모델에 담아 login.jsp로 보낸다.

 

(2) /callback

- 우리 아까 네이버 개발자 홈페이지에서 callback redirect url을 생각해 보자!

http://localhost:8080/tst/callback

- 그렇다 login.jsp에서 네이버로 로그인을 성공하면  '/callback' 이 호출된다!

- '/callback' 에서는 AccessToken을 발급받아 API를 사용할 수 있다.

- API를 통해 로그인을 한 사용자의 정보를 가져올 수 있다.

 

- 사용자의 정보는 apiResult 변수에 담겨져 있는데

  apiResult 변수를 출력해 보면 아래와 같이 Json형식으로 되어있다.

{"resultcode":"00",
"message":"success",
"response":{"id":"33666449","nickname":"shinn****","age":"2029","gender":"M","email":"shin@naver.com","name":"\uc2e0\ubc94\ud638"}} 

- 이 부분을 Json-simple 라이브러리를 통해 파싱해서 변수로 저장했다.

- 나는 "response"의 "nickname"을 파싱해서 "sessionId"라는 세션으로 저장했다.

- 이 'sessionId' 는 login.jsp에서 쓰인다.

 

 

(3) /logout 

- 네이버 API는 로그인은 지원하는데, 로그아웃은 지원을 하지않는다.

- 이것 때문에 나는 나만의 세션을 만들어서 로그아웃 요청이 들어올 때 그냥 내 세션을 invalidate 시키고  index.jsp로 리다이렉트 시켰다.

- 완벽한 로그아웃을 하려면 네이버 홈페이지로 접속해서 로그아웃 버튼을 눌러야한다!

 

 

자! 이제 모든 준비는 끝났다.

잘 작동되는지 확인을 위해 index.jsp를 실행해보자!

   naver login api


3. 실행 화면

** 캡쳐 화면에 url 주소 유의 하면서 보기!

 

3-1.

index.jsp

메인화면입니다. 로그인 하러 가기 클릭!

 

 

3-2.

로그인 전 login.jsp

로그인 페이지 ,

 

아 ! 지금 주소를 보면 ' http://localhost:8080/tst/login '  으로 되어있는데,

이 주소가 바로 아까 네이버 개발자 홈페이지에 등록한 서비스 URL 주소와 같아야 한다!

 

혹시 다르다면 본인 주소를 복사해서 개발자 홈페이지 들어가서 수정한다!

 

로그인 form은 기능 안만들었으니 무시하고

밑에 '네이버 아이디로 로그인' 버튼 클릭한다!

 

3-3.

 

네이버 아이디로 로그인 버튼을 누르면

짜잔! 네이버 로그인 창이 뜨고 

우리가 네이버 개발자 홈페이지에서 입력한 애플리케이션 이름인 'naver_login 서비스를 이용하 실 수 있습니다.' 

라는 글자를 볼 수 있다.

뭔가 설렌다..

 

3-4.

개인정보 동의

동의하기 버튼 클릭!

 

3-5.

로그인 성공시 login.jsp

로그인에 성공을 하면 /callback 이 호출되면서

다시 login.jsp로 돌아오고 우리의 닉네임을 보여준다.

( 이 중간 과정에서 controller가 파싱하고 세션 만들고 열심히 일 하겠지만)

 

이때 url도 확인!

' http://localhost:8080/tst/callback ' (뒤에 ?code=~~~ 무시) 인걸 알 수 있따.

이 주소도 네이버 개발자 홈페이지에서 입력한 callback URL 과 똑같아야 하니,

다르다면 본인의 주소를 복사하여 네이버 개발자 홈페이지에서 수정하자! (뒤에 code는 안 붙여도 된다.)

 

3-6.

로그아웃 했을 때

 

마지막으로 로그아웃을 하면 다시 메인 페이지로 돌아오는 것을 확인 할 수 있다.

 

 


더보기

네아로 API 참고했던 블로그

(정리가 잘되어있음)

https://m.blog.naver.com/PostView.nhn?blogId=sam_sist&logNo=220969407741&targetKeyword=&targetRecommendationCode=1

 

API 인증 방식, 원리, 개념 궁금하다면  

( 구글 API이긴 하지만 API 인증 돌아가는 방식, 원리를 알 수 있는 강의)

https://opentutorials.org/course/2473/16571

 

 

 

 

 

반응형