국비 팀 단위 마지막 프로젝트를 페이스북과 같은 SNS 컨셉으로 가닥을 잡았다.
타 사이트에 비해 부족한 것은 무엇일까를 고민했고, 다른 사이트에 계정을 이용해서 회원가입을 유도하는 기능이 있으면 좋을 것 같다는 생각을 했다 적용만 시킨다면 기업에서 제공하는 편리한 API 기능을 앞으로 해결해야 할 개인 프로젝트나 업무에 용이하게 쓸 귀중한 경험이 되고 초보 개발자인 나에게는 뜻깊은 일이 될 것이라고 생각했다.
막상 하려고 하니 처음 생각했던 만큼 어려운 일은 아니었다.
네이버가 대기업인만큼 네아로 적용시키는 예제가 굉장히 많았고 적용시키는 데에는 큰 무리가 없었다.
코드 적용을 하기 전 먼저 네이버 애플리케이션 등록을 해야 한다.
이 부분은 위 링크를 이용해 등록하자 꼼꼼히 기술하면 좋겠지만 이 블로그에 목적을 생각하면 그렇게까지 할 필요는 없다.
잘 만들어져 있는 레퍼런스가 있는데 굳이 졸작을 만들 필요는 없으니까.
(가지고 있는 프로젝트 코드와 위 블로그에 나온 코드를 섞어서 설명해야겠다.)
*주의
네이버 개발자 센터에서 등록한 애플리케이션 url설정이 같아야만 작동을 하고 아니면 오류가 나게 되어있음
한마디로 사용자가 지정한 url 외엔 다른 데에서 사용하지 말아라라는 뜻으로 생각해도 될 거 같다.
나는 서비스 URL과 Callback URL을 각각
login, callback으로 설정했다.
login.jsp
<a type="button " class="btn btn-success btn-block" href="${url}">네이버 로그인</a>
servlet-context.xml
<!-- NaverLoginBO Class에 대한 Bean설정 추가 -->
<beans:bean class="com.care.a01_member.naver.NaverLoginBO" id="naverLoginBO"/>
class에 있는 경로에 NaverLoginBO를 @bean으로 등록해서 naverLoginBO라는 id로 사용하겠다는 뜻이죠
NaverLoginApi.java
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
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();
}
}
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>
LoginController.java
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";
}
}
프로세서의 흐름
login 메소드는 네아로 인증 URL을 만들어서 모델에 담아 login.jsp로 보냅니다.
callback 메소드는 네이버로 로그인 성공 시 callback이 호출됩니다.
AccessToken을 발급받아 API를 사용할 수 있다.
- API를 통해 로그인을 한 사용자의 정보를 가져올 수 있다.
- 사용자의 정보는 apiResult 변수에 담겨 있습니다.
위 코드에서 nickname을 가져오듯이 하면 되겠습니다.
따라서 로그인 성공 후에 추가하고 싶은 작업들을 넣는 공간으로 활용하시면 되겠습니다.
추가로 네이버로 한번 로그인이 성공하면 웹 페이지를 닫지 않는 이상 로그아웃을 할 수 없습니다.
로그아웃을 하기 위해선 네이버 페이지에 로그아웃을 눌러야만 합니다.
나에게 도움을 준 블로그
https://bumcrush.tistory.com/151
API 이해를 위한 도움 영상
https://opentutorials.org/course/2473/16571