본문 바로가기
JSP

Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(22) - 카카오 로그인 구현하기

by 김마리님 2020. 6. 19.

이전 게시글은 여기

Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(21) - ajax를 이용한 덧글 삭제 구현하기

 

Servlet 과 JSP를 이용한(모델2 형식) 블로그 만들기(21) - ajax를 이용한 덧글 삭제 구현하기

 

itstudy-mary.tistory.com

번외편처럼 카카오 로그인을 구현해보자.

먼저 우리의 페이지에서 로그인을 시도한다.

그럼 그 로그인 정보를 카카오 서버가 가져가서 이 요청이 올바른 요청인지 발급받은 키 값과 코드를 발급하는데, 그 값을 다시 본 서버로 가져와서 토큰을 요청한다. 이 토큰이 발급한 DB접근 허가 토큰이라면 그제서야 카카오 DB에 들어가서 카카오 서비스 이용 시 유저가 설정했던 값들(프로필, 섬네일, 이메일) 등에 간접접근 할 수 있다. 본 서버는 이 값들을 가져와 본 DB에 사용 가능하도록 한다.

 

먼저 모든 api가 그렇듯 카카오도 키 값을 요구한다. 키를 발급받으러 가자.

 

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

 

다음 홈페이지로 들어가서 카카오 로그인을 하면 카카오 관련 개발을 진행할 수 있다.

먼저, [내 애플리케이션]으로 접근하여 현재 만들 앱을 등록한다.

 

앱을 등록하고 들어가보면 다음과 같이 키를 발급받은 상태를 나타내는 메인이 등장한다(키들은 이후 [앱 키] 탭에서 재발급 받을수도 있다.)

 

그리고 현재 우리가 사용 중인 것은 웹이므로, 플랫폼에서 웹 도메인을 입력한다.

 

마지막으로 카카오 로그인을 사용하기 위해 로그인을 활성화 하면, 앱 키가 로그인 서비스를 제공할 준비가 된다.

 

먼저 카카오 로그인 버튼을 만들어보자.

 

 

다음 공통 가이드로 들어가서 자신이 원하는 아이콘으로 다운 받는다(전체 다운로드를 받으면 모든 아이콘을 받을 수 있긴 함..). 이 버튼을 우리 페이지에 적용해보자.

프로젝트에 이미지를 넣고, 이미지 경로를 조작해 이미지를 띄운다.

(높이가 안맞아서 높이 수정을 했음. 총 높이가 38이라 카카오 버튼도 38px로 수정)

 

 

- Login.jsp

<button type="submit" class="btn btn-primary">로그인</button>
		<img height="38px" src="/blog/image/kakao_login_button.png" />

 

버튼을 적용하면 다음과 같이 나타난다.

항상 보던 노란 버튼을 만들 수 있다!

 

이제 이 버튼에 로그인 액션을 추가하자.

먼저 이 버튼을 가지고, 로그인 인증을 구현하자.

 

먼저 카카오 로그인을 위한 인증 코드를 구현한다.

인증 코드 구현 API로 접근하는 코드는 카카오 개발에서 제공한다.

<button type="submit" class="btn btn-primary">로그인</button>
		<a href="https://kauth.kakao.com/oauth/authorize?client_id={발급 받은 클라이언트 키}&redirect_uri=http://localhost:8000/blog/oauth/kakao?cmd=callback&response_type=code"><img height="38px" src="/blog/image/kakao_login_button.png" /></a>
        

리다이렉트 해줄 uri는 우리가 코드를 받아 액션을 해줄 uri로 보낸다. 우리는 라우터로 분기할 곳으로 uri를 지정했다.

 

하지만 카카오 컨트롤러는 아직 만들지 않았으므로 만들러 가보자. 카카오 컨트롤러는 다른 컨트롤러와 만드는 방식이 동일하게, 서블릿으로 작성한다.

 

- KakaoController.java

package com.cos.blog.controller;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.cos.blog.action.Action;
import com.cos.blog.action.board.BoardDeleteProcAction;
import com.cos.blog.action.board.BoardDetailAction;
import com.cos.blog.action.board.BoardHomeAction;
import com.cos.blog.action.board.BoardSearchAction;
import com.cos.blog.action.board.BoardUpdateAction;
import com.cos.blog.action.board.BoardUpdateProcAction;
import com.cos.blog.action.board.BoardWriteAction;
import com.cos.blog.action.board.BoardWriteProcAction;
import com.cos.blog.action.kakao.KakaoJoinProcAction;
import com.cos.blog.action.kakao.KakaocallbackAction;
import com.cos.blog.action.user.UsersLoginAction;

// http://localhost:8000/blog/board
@WebServlet("/oauth/kakao")
public class KakaoController extends HttpServlet {
	private final static String TAG = "KakaoController : ";
	private static final long serialVersionUID = 1L;
       
    public KakaoController() {
        super();
    }

	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		doProcess(request, response);
	}
	
	protected void doProcess(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// http://localhost:8000/blog/user?cmd=join
		String cmd = request.getParameter("cmd");
		System.out.println(TAG+"router : "+cmd);
		Action action = router(cmd);
		action.execute(request, response);
	}
	
	public Action router(String cmd) {
		if(cmd.equals("callback")) {
			// 홈페이지로 이동
			return new KakaocallbackAction();
		}
		return null;
		
	}
	

}

다음과 같이 코드값을 받을 액션을 제작한다.

 

우리는 코드를 받아서 다시 토큰 발급 요청을 해야 한다.

토큰 발급 요청을 할 때는 어떤 파라메터가 필요할까.

 

이것도 카카오 개발이 지원해준다.

코드는 getParameter로 받고, 나머지는 이전에 인증 요청할때의 값과 같다. 이 토큰은 요청을 post로 해야한다. 즉, 폼이 없음에도 이 값을 body에 넣어서 진행해야한다.

물론 이것을 라이브러리로 진행 가능하지만, 지금은 자바에서 제공하는 httpurlconnection을 이용한다.

지금부터 파일 하나를 천천히 잘라서 진행한다.

 

- KakaocallbackAction.java

 

package com.cos.blog.action.kakao;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;

import javax.net.ssl.HttpsURLConnection;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import com.cos.blog.action.Action;
import com.cos.blog.dto.KakaoProfile;
import com.cos.blog.model.OAuthToken;
import com.cos.blog.model.Users;
import com.cos.blog.repository.UsersRepository;
import com.cos.blog.util.Script;
import com.google.gson.Gson;

public class KakaocallbackAction implements Action{
	@Override
	public void execute(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		String code = request.getParameter("code");

		//System.out.println("code :"+code);
		//Post요청, x-www-form-urlencoded
		
		String endpoint="https://kauth.kakao.com/oauth/token";
		URL url =new URL(endpoint);
		
		String bodyData="grant_type=authorization_code&";
		bodyData += "client_id={자기 앱키}&";
		bodyData += "redirect_uri=http://localhost:8000/blog/oauth/kakao?cmd=callback&";
		bodyData += "code="+code;
		
		//Stream 연결
		HttpsURLConnection conn=(HttpsURLConnection)url.openConnection();
		//http header 값 넣기
		conn.setRequestMethod("POST");
		conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
		conn.setDoOutput(true);
		//request 하기
		BufferedWriter bw=new BufferedWriter(new OutputStreamWriter(conn.getOutputStream(),"UTF-8"));
		bw.write(bodyData);
		bw.flush();
			
		BufferedReader br = new BufferedReader(
				new InputStreamReader(conn.getInputStream(), "UTF-8")
				);
		String input="";
		StringBuilder sb=new StringBuilder();
		while((input=br.readLine())!=null){
			sb.append(input);
		}
		
		System.out.println(sb.toString());
				
		//Gson으로 파싱
		Gson gson=new Gson();
		
		OAuthToken oAuthToken=gson.fromJson(sb.toString(), OAuthToken.class);

카카오 개발자의 요구상태를 보자.

body 데이터에 요구하는 형태가 x-www-form-urlencoded의 형태이다. 즉, 내부 데이터의 형태를 key-value 형태로 제공해달란 의미이다. 우리는 이에 따라 body 데이터를 form을 넣을때의 데이터 형태와 같이 데이터를 집어 넣는다.

 

이제 urlconnnection을 통해 endpoint에서 지정한 url과 현재 자바를 연결한다. 이 때, 카카오 서버는 인증을 요하기 때문에 SSL 보안 서버를 거치게 된다. 따라서 http's'urlconnection을 통해 연결해야한다.

이제 bufferwriter 을 이용해 이 서버에 내 body data를 전달한다. set 함수를 이용해 매서드와 프로퍼티를 설정해주고, 이 값을 버퍼에 작성해서 플러시 한다.

그리고 플러시한 이후의 응답을 bufferedreader을 통해 읽는다. 이 값은 string이지만 json의 형태를 가지고 있기 때문에, 이 값을 gson을 이용해 json의 형태로 파싱한다. json의 형태로 파싱받을 원형 데이터도 작성한다.

 

-OAuthToken.java

package com.cos.blog.model;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder

public class OAuthToken {

	private String access_token;
	private String token_type;
	private String refresh_token;
	private int expires_in;
	private String scope;
	private int refresh_token_expires_in;
	
}

 

일단 여기까지 하면, 카카오 로그인을 눌렀을때 자바 콘솔에 다음과 같이 데이터가 뿌려질 것이다.

이것을 JsonParser로 파싱해보면 어떤 프로퍼티를 리턴하는지 확인할 수 있다.

http://json.parser.online.fr/

 

Json Parser Online

 

json.parser.online.fr

이제 이 받은 토큰 중 access_token을 가지고 데이터베이스에 접근할 것이다.

		//profile 받기(Resource Server)
		String endpoint2="https://kapi.kakao.com/v2/user/me";
		URL url2 =new URL(endpoint2);
		
		HttpsURLConnection conn2=(HttpsURLConnection)url2.openConnection();
		
		//header 값 넣기
		conn2.setRequestProperty("Authorization", "Bearer "+oAuthToken.getAccess_token());
		conn2.setDoOutput(true);
		
		//request 하기
		BufferedReader br2=new BufferedReader(new InputStreamReader(conn2.getInputStream(),"UTF-8"));
		String input2="";
		StringBuilder sb2=new StringBuilder();
		while((input2=br2.readLine())!=null) {
			sb2.append(input2);
		}
		
		System.out.println("sb2.toString() : "+sb2.toString());
		
		Gson gson2=new Gson();
		KakaoProfile kakaoProfile=gson2.fromJson(sb2.toString(), KakaoProfile.class);
	
		System.out.println(kakaoProfile);

앞서 했던 형태와 마찬가지다. 먼저 카카오 개발자를 살펴본다.

정보를 가져오는 것은 '사용자 관리' 이다.

먼저, 이것을 사용하기 전에 우리가 사용할 정보에 대한 동의여부를 설정해야한다.

설정할 경우 클라이언트에게 다음과 같은 화면정보가 제공된다.

이것은 아까 애플리케이션 설정을 했던 내 애플리케이션 - 동의항목에서 설정 가능하다.

이제 정보에 필요한 파라메터를 확인해보자.

 

우리는 Access_Token으로 접속하므로 다음의 형태를 확인하면 된다.

GET형태나 POST형태, 어느쪽이든 상관 없으며, 요구 파라메터는 access token 뿐이다.

 

이제 처음 토큰을 요청할때와 같이, 요구하는 uri 를 endpoint2로 지정하고, 원형데이터 속에 넣었던 access를 간접참조하여 값을 참조한다. 참조한 값을 프로퍼티로 제공하면 bufferedreader의 형태로 값이 돌아온다. 

그 값의 형태 역시 json의 형태일 것이다.

이것을 parser에 붙이면 형태는 더욱 명확해진다.

이것을 다시 원형 데이터로 만들어 Gson으로 파싱받는다.

 

- KakaoProfile.java

package com.cos.blog.dto;

import lombok.Data;

@Data
public class KakaoProfile {
	private String id;
	private KakaoAccount kakao_account;
	
	@Data
	public class KakaoAccount {
		private Profile profile;
		private String email;
		private boolean has_email;
		
		@Data
		public class Profile{
			private String nickname;
			private String profile_image_url;
		}
	}
}

이제 받아온 값들을 바탕으로, 이전에 가입한 기록이 있는지 찾고 가입한 기록이 있다면 바로 session 값을 부여하여 로그인하고, 가입한 기록이 없다면 등록을 진행한다.

 

		UsersRepository usersRepository = UsersRepository.getInstance();
		Users principal=usersRepository.findByUsername(kakaoProfile.getId()+"_kakao");
		
		HttpSession session=request.getSession();
		
		if(principal!=null) { //기존 회원이면 로그인 진행
			System.out.println("로그인하기");
			session.setAttribute("principal", principal);
			Script.href("카카오 로그인 완료", "/blog/index.jsp", response);
		}else { //기존 회원이 아니면 회원 가입 후 로그인 진행
			//email값이 없으면 추가 회원정보 받으러 이동
			System.out.println("가입하기");
			if(kakaoProfile.getKakao_account().getEmail()==null || kakaoProfile.getKakao_account().getEmail().equals("")) {
				System.out.println("추가정보 기입 요망");
				request.setAttribute("kakaoProfile",kakaoProfile);
				RequestDispatcher dis = request.getRequestDispatcher("/user/oauthjoin.jsp");
				dis.forward(request, response);
			}else { //email 값이 있으면 바로 회원가입 및 로그인 진행
				System.out.println("추가정보 기입 불필요");
				String username=kakaoProfile.getId();
				username+="_kakao";
				Users user=Users.builder()
						.username(username)
						.email(kakaoProfile.getKakao_account().getEmail())
						.userProfile(kakaoProfile.getKakao_account().getProfile().getProfile_image_url())
						.build();
				usersRepository.save(user);
				session.setAttribute("principal", user);
			}
			
			Script.href("카카오 회원가입 및 로그인 완료", "/blog/index.jsp", response);
		}
		
		
		}
		
		
	}

만일 계정에 이메일이 없거나 이메일이 있더라도 동의하지 않았다면 추가적으로 메일을 입력할 수 있도록 폼으로 유도하고, 아니라면 원형데이터에서 받아온 값을 데이터베이스에 넣어 회원가입을 시킨다.

이제 그 폼을 제작해보자(별게 없다).

 

- oautherjoin.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ include file="../include/nav.jsp" %>

<div class="container">
	<h1>카카오 로그인 추가정보 구성창</h1>
	<form action="/blog/oauth/kakao?cmd=joinProc" method="post" class="was-validated">
		<div class="form-group">
			<label for="username">username : </label> 
			<input value="${kakaoProfile.id }_kakao" type="text" class="form-control" id="username" placeholder="Enter username" name="username" required readonly="readonly">
		</div>
		<div class="form-group">
			<label for="email">email : </label> 
			<input type="email" class="form-control" id="email" placeholder="Enter Email" name="email" required>
			<div class="valid-feedback">Valid.</div>
			<div class="invalid-feedback">Please fill in this field.</div>
		</div>
		
		<button type="submit" class="btn btn-primary">회원 가입 완료</button>
	</form>

</div>
<%@ include file="../include/footer.jsp" %>

이제 마지막으로 정말 로그인이 되는지 확인해보자

 

1. 메일 제공을 동의할 경우

 

 

2. 메일 제공에 동의하지 않을 경우

1) 회원가입

2) 다시 누르면

다음과 같이 회원가입에 성공하게 된다.

반응형