Project/javachip

[2nd Project] 구글 API 구현하기

ParkYeseul 2024. 10. 4. 20:41

 

구글도 처음엔 mvc로 시도하다가 

초기 셋팅이 오류가 있어서 다시 이클립스로 가서 작업했다.

 

그래서 파일이 비교적 단순한데 5개가 전부다.

이걸로 간단히 하다가 네이버로 오니까 머리가 터질 수 밖에!
한 번 짚고 가보다쿠

👧🎅👩‍🚀👨‍💻👩‍💻

 

 

 

 


 

 

GoogleVO

package dto;

import java.sql.Timestamp;

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

@Data  // 게터, 세터, toString, equals, hashCode 등을 자동으로 생성
@NoArgsConstructor  // 기본 생성자 생성
@AllArgsConstructor  // 모든 필드를 매개변수로 받는 생성자 생성
public class GoogleVO {

    private String googleId;      // 구글에서 제공하는 고유 ID (google_id)
    private int mIdx;             // m_member 테이블의 외래키 (m_idx)
    private String googleName;
    private Timestamp createAt;   // 가입 날짜 (create_at)
}

Google API에서 얻은 데이터를 관리하고 이를 다른 클래스나 메소드로 쉽게 전달할 수 있도록 하는 역할

 

 

 

GoogleTokenServlet

package servlet;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.sql.Timestamp;
import java.util.Properties;
import java.util.Scanner;

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 org.json.JSONObject; // JSON 라이브러리 추가

import dao.GoogleDAO;
import dao.M_MemberDAO;
import dto.GoogleVO;
import dto.M_MemberDTO;

@WebServlet("/auth/google/callback")
public class GoogleTokenServlet extends HttpServlet {

    private String CLIENT_ID;
    private String CLIENT_SECRET;
    private String REDIRECT_URI;
    private static final String TOKEN_ENDPOINT = "https://oauth2.googleapis.com/token";

    @Override
    public void init() throws ServletException {
        // config.properties 파일에서 설정을 읽어옵니다.
        Properties properties = new Properties();
        try (InputStream input = getServletContext().getResourceAsStream("/WEB-INF/classes/config.properties")) {
            if (input == null) {
                throw new IOException("Config file not found");
            }
            properties.load(input);
            CLIENT_ID = properties.getProperty("google.client.id");
            CLIENT_SECRET = properties.getProperty("google.client.secret");
            REDIRECT_URI = properties.getProperty("google.redirect.uri");
        } catch (IOException e) {
            throw new ServletException("Failed to load configuration", e);
        }
    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String code = req.getParameter("code");
        System.out.println("Google callback invoked with code: " + code);

        if (code != null) {
            try {
                // 액세스 토큰 요청
                String requestBody = "code=" + URLEncoder.encode(code, StandardCharsets.UTF_8) +
                        "&client_id=" + URLEncoder.encode(CLIENT_ID, StandardCharsets.UTF_8) +
                        "&client_secret=" + URLEncoder.encode(CLIENT_SECRET, StandardCharsets.UTF_8) +
                        "&redirect_uri=" + URLEncoder.encode(REDIRECT_URI, StandardCharsets.UTF_8) +
                        "&grant_type=authorization_code";

                URL url = new URL(TOKEN_ENDPOINT);
                HttpURLConnection connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("POST");
                connection.setDoOutput(true);
                connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

                try (OutputStream os = connection.getOutputStream()) {
                    os.write(requestBody.getBytes(StandardCharsets.UTF_8));
                    os.flush();
                }

                // 응답 처리
                int responseCode = connection.getResponseCode();
                System.out.println("Response Code: " + responseCode);

                if (responseCode == HttpURLConnection.HTTP_OK) {
                    Scanner scanner = new Scanner(connection.getInputStream());
                    String response = scanner.useDelimiter("\\A").next();
                    scanner.close();

                    // 액세스 토큰 파싱
                    String accessToken = parseAccessToken(response);

                    // 사용자 정보 요청
                    String userInfoEndpoint = "https://www.googleapis.com/oauth2/v2/userinfo?access_token=" + URLEncoder.encode(accessToken, StandardCharsets.UTF_8);
                    URL userInfoUrl = new URL(userInfoEndpoint);
                    HttpURLConnection userInfoConnection = (HttpURLConnection) userInfoUrl.openConnection();
                    userInfoConnection.setRequestMethod("GET");

                    Scanner userInfoScanner = new Scanner(userInfoConnection.getInputStream());
                    String userInfoResponse = userInfoScanner.useDelimiter("\\A").next();
                    userInfoScanner.close();

                 // 사용자 정보 요청 후, GoogleVO 객체 생성
                    GoogleVO googleVO = parseGoogleUserInfo(userInfoResponse);

                    // 사용자 등록 (m_member 테이블에 추가)
                    M_MemberDTO memberDTO = new M_MemberDTO();
                    memberDTO.setM_email(googleVO.getGoogleId()); // 구글 ID를 이메일로 사용
                    memberDTO.setM_password(""); // 비밀번호를 빈 문자열로 설정 (소셜 로그인에서는 필요 없음)
                    memberDTO.setM_nickname(googleVO.getGoogleName()); // 구글 이름 사용

                    M_MemberDAO memberDAO = new M_MemberDAO();
                    int mIdx = memberDAO.insertM_Member(memberDTO); // 사용자를 등록하고 mIdx 받아오기

                    // mIdx를 GoogleVO에 설정
                    googleVO.setMIdx(mIdx); // 반환받은 mIdx 설정

                    // DB에 사용자 정보 저장
                    GoogleDAO googleDAO = new GoogleDAO();
                    googleDAO.insertGoogleLogin(googleVO);

                    // 성공 시 메인 페이지로 리다이렉트
                    resp.sendRedirect(req.getContextPath() + "/index.jsp");

                } else {
                    // 오류가 발생할 경우 오류 메시지 출력
                    String errorResponse = new Scanner(connection.getErrorStream()).useDelimiter("\\A").next();
                    System.out.println("Error Response: " + errorResponse);
                }

            } catch (Exception e) {
                e.printStackTrace();
                resp.setContentType("text/html");
                resp.getWriter().println("<html><body><h1>오류가 발생했습니다.</h1><p>" + e.getMessage() + "</p></body></html>");
            }
        } else {
            resp.setContentType("text/html");
            resp.getWriter().println("<html><body><h1>코드 파라미터가 없습니다.</h1><p>유효하지 않은 요청입니다.</p></body></html>");
        }
    }

    private String parseAccessToken(String jsonResponse) {
        JSONObject jsonObject = new JSONObject(jsonResponse);
        return jsonObject.getString("access_token");
    }
    
    
    private GoogleVO parseGoogleUserInfo(String jsonResponse) {
        JSONObject jsonObject = new JSONObject(jsonResponse);
        GoogleVO googleVO = new GoogleVO();
        googleVO.setGoogleId(jsonObject.getString("id"));
        googleVO.setGoogleName(jsonObject.getString("name"));
        googleVO.setCreateAt(new Timestamp(System.currentTimeMillis())); // 현재 시간으로 설정
        return googleVO;

    }
}

아따 길다~

구글 인증 후 콜백 요청을 처리하는 서블릿

이 서블릿은 구글에서 제공한 인증 코드(authorization code)를 사용하여 액세스 토큰을 요청하고, 이 액세스 토큰을 통해 사용자 정보를 얻는다.

 

 

1. 사용자가 구글 로그인 페이지에서 로그인.

2. 구글이 인증 후 redirect_uri로 authorization code와 함께 요청을 보냄.

3. GoogleTokenServlet에서 이 코드를 받아 액세스 토큰을 요청함.

4. 토큰을 통해 사용자 정보를 얻고 이를 GoogleVO에 저장.

 

주요 메소드

doGet() 또는 doPost()

사용자가 로그인하면 구글에서 요청을 보내고, 이 메소드에서 콜백 요청 처리

 

getAccessToken()

구글 서버로 인증 코드를 보내서 액세스 토큰을 받는 과정을 처리

이 메소드는 HttpURLConnection을 사용하여 POST 요청을 보내고, 응답으로 받은 JSON에서 액세스 토큰을 추출

 

getUserInfo()

액세스 토큰을 사용해 사용자 정보를 가져오는 메소드

 

 

try (OutputStream os = connection.getOutputStream()) {
    os.write(requestBody.getBytes(StandardCharsets.UTF_8));
    os.flush();
}

 

 

try (OutputStream os = connection.getOutputStream())

 

try-with-resources

자원을 자동으로 닫아주는 기능을 제공

여기서 OutputStream을 사용하고 있는데, 이 자원은 사용이 끝나면 반드시 닫아주어야 함

try-with-resources 구문은 블록이 끝날 때 자동으로 자원을 해제해 주므로, 자원 누수를 방지하는 데 유용

 

OutputStream os = connection.getOutputStream():

connection 객체(주로 HttpURLConnection 객체)의 출력 스트림을 가져온다.

이 출력 스트림은 서버로 데이터를 전송하기 위해 사용

즉, 이 스트림을 사용하여 서버에 데이터를 쓸 수 있다.

 

os.write(requestBody.getBytes(StandardCharsets.UTF_8)):

requestBody.getBytes(StandardCharsets.UTF_8)

여기서 requestBody는 문자열로 된 요청 본문을 의미

이 문자열을 바이트 배열로 변환하기 위해 .getBytes(StandardCharsets.UTF_8) 메서드를 사용

 

StandardCharsets.UTF_8

이 부분은 바이트 배열을 UTF-8 인코딩을 사용해 생성하도록 지정 

 

os.write(...): OutputStream

사용하여 바이트 배열을 서버로 보냄

이 메서드는 출력 스트림에 데이터 사용.

즉, 클라이언트가 서버에 데이터를 전송하는 역할

 

os.flush():

flush() 메서드는 스트림에 있는 데이터를 강제로 모두 내보내는 역할

버퍼에 남아 있는 데이터를 전부 출력 장치나 파일로 전송

 

 

🛬OutputStream? 

프로그램에서 외부 장치나 파일로 데이터를 전송하는 데 사용

이 클래스는 데이터가 바이트 배열의 형태로 출력

 

대표적인 OutputStream의 하위 클래스

FileOutputStream

파일에 데이터를 쓰는 데 사용, 파일에 바이트 데이터를 출력할 때 사용된다

 

ByteArrayOutputStream

메모리 내에 데이터를 출력, 데이터가 바이트 배열로 저장된다.

 

BufferedOutputStream

출력을 버퍼링하여 성능을 향상시킨다.

 

ObjectOutputStream

Java 객체를 직렬화하여 출력할 때 사용

 

flush()

현재 스트림에 남아 있는 데이터를 강제로 출력, 출력이 완료되지 않은 데이터를 버퍼에서 전송

close()

스트림을 닫고, 자원을 해제, 스트림을 더 이상 사용하지 않을 때 호출

 

 

 

 

 

 

Config.properties

google.client.id=
google.client.secret=
google.redirect.uri=

 

google.client.id: 구글 API 클라이언트 ID

google.client.secret: 구글 API 클라이언트 비밀키

google.redirect.uri: 인증 후 리디렉션할 URL

 

 

 

joinmain.jsp

  // 구글 로그인 버튼 클릭 시 처리
        document.getElementById("google-login-btn").addEventListener("click", function () {
            console.log("구글 로그인 버튼이 클릭되었습니다.");

            let googleAuthUrl = "https://accounts.google.com/o/oauth2/v2/auth" +
                "?client_id=" + GOOGLE_CLIENT_ID +
                "&redirect_uri=" + GOOGLE_REDIRECT_URI +
                "&response_type=code" +
                "&scope=https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email";

            console.log("Redirecting to: " + googleAuthUrl); // 리디렉션 URL 로그
            window.location.href = googleAuthUrl;  // 구글 로그인 페이지로 이동
        });

 

 

 

googleCallback.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Google OAuth Callback</title>
</head>
<body>
    <script type="text/javascript">
        window.onload = function() {
            // URL에서 인증 코드 추출
            const params = new URLSearchParams(window.location.search);
            const code = params.get('code');

            if (code) {
                // 서버로 인증 코드 전달
                fetch('http://localhost:9090/BBOL_prjt/auth/google/token', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({ code: code })
                })
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        console.log("로그인 성공:", data);
                        // 메인 페이지로 리다이렉트
                        window.location.href = "http://localhost:9090/BBOL_prjt/index.jsp";
                    } else {
                        console.error("로그인 실패:", data);
                    }
                })
                .catch(error => {
                    console.error("서버 통신 중 오류 발생:", error);
                });
            }
        }
    </script>
</body>
</html>

구글로부터 받은 콜백 요청을 처리 

구글 로그인 과정에서 구글이 redirect_uri로 설정된 페이지로 요청을 보낼 때,

구글 인증 후 콜백을 처리하여 사용자 정보를 관리하고 표시

 

  1. 구글 로그인 성공 후, code를 포함하여 redirect_uri로 이동
  2. googleCallback.jsp에서는 JavaScript 또는 AJAX를 사용하여 서버로 인증 코드를 전달하거나, 사용자 정보를 화면에 표시

 

 

 

🛳오류이야기

아니나 다를까 400, 401오류를 줄기차게 만났다.

엑세스 차단됨..

너무 마음 아픈 코드 열심히 적어서 짠 했는데 

계속 차단 되고 아무리 구글링해서 

잘 못된게 없어서 또 답답한 가슴을 잡고 소리를 질렀다.

 

그럼에도 붙잡고 다시 코드를 하나하나 보기 시작했다. 진짜 보기 싫을 만큼 했는데

어떡해 해야지/////

@Override

public void init() throws ServletException {

// config.properties 파일에서 설정을 읽어옵니다.

Properties properties = new Properties();

try (InputStream input = getServletContext().getResourceAsStream("/WEB-INF/classes/config.properties")) {

if (input == null) {

throw new IOException("Config file not found");

}

 

참 간단한 오류였는데 config.properties경로를 안잡아줘서 저기에 적은 내 client_id와 시크릿이랑 url을 못 불러왔다.

경로 문제는 간단한 것 같으면서도 참 찾기 어렵다. 

절대 간단하지 않다!!!! 그치만 내가 더 꼼꼼 했다면.. 이런 일은 안 생겼겠지!

 

난이도 쉬운 순서대로 구글 >카카오 > 네이버 

내가 구글을 서블릿에 다 구현해서 쉽게 느껴지는 것 같긴하나 

mvc로 옮길 생각하면 지끈지꾼하다.