구글도 처음엔 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로 설정된 페이지로 요청을 보낼 때,
구글 인증 후 콜백을 처리하여 사용자 정보를 관리하고 표시
- 구글 로그인 성공 후, code를 포함하여 redirect_uri로 이동
- 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로 옮길 생각하면 지끈지꾼하다.
'Project > javachip' 카테고리의 다른 글
[2nd Project] 프로필 이미지 수정(JSP, STS) (8) | 2024.10.22 |
---|---|
[2nd Project] SQL 조인해서 테이블 연동하기, 마이페이지에 불러오기! (8) | 2024.10.18 |
[2nd Project] 스프링, JSP, Javascript로 네이버 API 로그인 구현하기 (5) | 2024.10.04 |
[2nd Project] ⁴ Servlet, JSP, AJAX 이메일 인증 기능 구현 및 오류 해결 과정 (3) | 2024.09.25 |
[2nd Project]³ DBCP 연결 및 DB 불러오기 실패 해결 과정 (2) | 2024.09.24 |