Post

[Day35] 쇼핑몰 실습 - Kakao OAuth

[Day35] 쇼핑몰 실습 - Kakao OAuth

Kakao OAuth를 통한 로그인

시퀀스

준비

카카오 개발자 센터(https://developers.kakao.com/)로 가서 내 애플리케이션에 카카오 로그인 기능을 활성화 하고 REST API 키 가져 오기

1
2
3
4
5
6
7
8
9
카카오 로그인 기능 활성화 방법
1) 내 애플리케이션 > 제품 설정 > 카카오 로그인
    : 활성화 설정 => ON
      Redirect URI => http://back_ip:8080/kakaoLoginCallback
      동의 항목 => account_email (개인 개발자 비즈 앱 전환이 되어야 동의항목으로 설정할 수 있음)
      
   
2) 내 애플리케이션 > 앱 설정 > 앱키
    : REST API 키 => 복사

BACK (백엔드)

📌 보안 설정 : secu.properties

API Key 등 중요한 정보를 secu.properties에 저장하여 관리

1
2
3
4
5
6
DB_DRIVER=com.mysql.cj.jdbc.Driver
DB_URL=jdbc:mysql://localhost:3306/ureca
DB_USER=ureca
DB_PW=ureca

KAKAO_API_KEY=b343b2cbddccf...

📌 환경 변수 읽기 : application.properties

application.properties에서 secu.properties의 KEY를 읽어 오도록 설정한다

1
2
3
4
5
6
7
8
9
10
11
12
13
spring.application.name=CoffeShop

spring.datasource.driver-class-name=${DB_DRIVER}
spring.datasource.url=${DB_URL}
spring.datasource.username=${DB_USER}
spring.datasource.password=${DB_PW}

logging.level.com.shop.cafe=debug

mybatis.type-aliases-package=com.shop.cafe.dto
mybatis.mapper-locations=mapper/*.xml

kakao.api.key=${KAKAO_API_KEY}

📌 사용자를 Kakao 인증 화면으로 리디렉트 : OAuthController.java

Kakao 로그인 버튼 클릭 시, kakao OAuth 인증 화면으로 redirect -> 사용자 동의 코드 받기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Controller
public class OAuthController {

	@Value("${kakao.api.key}")
	String KAKAO_API_KEY ;

  //  Kakao 인증 화면으로 리디렉트
	@GetMapping("kakaoLogin")
	public String kakaoLogin() {
		return "redirect:https://kauth.kakao.com/oauth/authorize?client_id=" 
		+ KAKAO_API_KEY 
				+ "&redirect_uri=http://localhost:8080/kakaoLoginCallback&response_type=code";
	}
	
  // 사용자 동의 후 Kakao에서 code를 반환 & AccessToken 출력
	@GetMapping("kakaoLoginCallback")
	public String kakaoCallback(@RequestParam String code){
    String access_Token = oAuthService.getKaKaoAccessToken(code);
    System.out.println("access_Token :" + access_Token );
    System.out.println("사용자 동의 코드:" + code);
		return null;
	}
}

📌 동의 코드로 Access Token 요청 : OAuthService.java

kakao에게 동의코드 내밀며 AccessToken 요청하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
@Service
public class OAuthService {
	@Value("${kakao.api.key}")
	String KAKAO_API_KEY ;
	
	public String getKaKaoAccessToken(String code) {
	    String access_Token = "";
	    String refresh_Token = "";
	    String reqURL = "https://kauth.kakao.com/oauth/token";

	    try {
        URL url = new URL(reqURL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        // POST 요청을 위해 기본값이 false인 setDoOutput을 true로
        conn.setRequestMethod("POST");
        conn.setDoOutput(true);

        // POST 요청에 필요로 요구하는 파라미터 스트림을 통해 전송
        BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
        StringBuilder sb = new StringBuilder();
        sb.append("grant_type=authorization_code");
        sb.append("&client_id=" + KAKAO_API_KEY );
        sb.append("&redirect_uri=http://localhost:8080/kakaoLoginCallback");
        sb.append("&code=" + code);
        bw.write(sb.toString());
        bw.flush();

        // 결과 코드가 200이라면 성공
        int responseCode = conn.getResponseCode();
        System.out.println("responseCode : " + responseCode);
        
        // 요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line = "";
        String result = "";

        while ((line = br.readLine()) != null) {
            result += line;
        }
        System.out.println("response body : " + result);

        // Jackson 라이브러리로 JSON 파싱
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(result);

        access_Token = jsonNode.get("access_token").asText();
        refresh_Token = jsonNode.get("refresh_token").asText();

        System.out.println("access_token : " + access_Token);
        System.out.println("refresh_token : " + refresh_Token);

        br.close();
        bw.close();
	    } catch (IOException e) {
        e.printStackTrace();
	    }
	    return access_Token;
	}	
}

📌 kakao에게 AccessToken 내밀며 사용자 정보 요청 : OAuthService.java

Access Token을 사용하여 Kakao에서 사용자 이메일 정보를 가져옴.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public String getKakaoUser(String token) {
    String reqURL = "https://kapi.kakao.com/v2/user/me";
    String email = "";

    try {
        URL url = new URL(reqURL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Authorization", "Bearer " + token);

        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String result = br.readLine();

        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(result);
        
        boolean hasEmail = jsonNode.get("kakao_account").get("has_email").asBoolean();
        if (hasEmail) {
            email = jsonNode.get("kakao_account").get("email").asText();
        }
        System.out.println("email : " + email);
        br.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return email;
}

📌 Access Token과 이메일 출력 : OAuthController.java

1
2
3
4
5
6
7
8
9
10
11
12
@GetMapping("kakaoLoginCallback")
public String kakaoCallback(@RequestParam String code) {
    System.out.println("사용자 동의 코드:" + code);

    String access_Token = oAuthService.getKaKaoAccessToken(code);
    System.out.println("access_Token :" + access_Token);
    
    String email = oAuthService.getKakaoUser(access_Token);
    System.out.println("email  :" + email);
    
    return null;
}

📌 Access Token과 Email을 쿠키에 저장 : OAuthController.java

OAuthController.java에서 email과 AccessToken을 쿠키로 설정해서 kakao 서버로 리턴하면 kakao에서 클라이언트로 redirect page를 보내준다. 이때 클라이언트 브라우저에 쿠키가 저장된다

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@GetMapping("kakaoLoginCallback")
public String kakaoCallback(@RequestParam String code, HttpServletResponse response) {
    String access_Token = oAuthService.getKaKaoAccessToken(code);
    String email = oAuthService.getKakaoUser(access_Token);

    if (email != null) {
        Cookie c1 = new Cookie("email", email);
        Cookie c2 = new Cookie("Authorization", access_Token);

        response.addCookie(c1);
        response.addCookie(c2);
    }
    return "redirect:http://localhost:5500/";
}

📌 로그인 정보를 DB에 저장 : OAuthService.java

  • 로그인 정보를 DB에 저장하기 위해 insertLoginInfo()라는 메소드를 추가
1
2
3
4
5
6
7
@Autowired
LoginDao loginDao;

public void insertLoginInfo(String email, String kakaoToken) throws Exception {
    Login loginInfo = new Login(email, kakaoToken, null, new Date());
    loginDao.insertToken(loginInfo);
}

백엔드 흐름

✅ secu.properties로 API Key 보안 관리
✅ application.properties에서 환경 변수 로드
✅ Kakao 로그인 화면으로 리디렉트
✅ 동의 코드(code)를 받아 Access Token 요청
✅ Access Token으로 사용자 정보(이메일) 요청
✅ 이메일과 Access Token을 쿠키에 저장 및 DB에 저장


FRONT (프론트엔드)

index.html : 카카오 로그인

http://localhost:8080/kakaoLogin으로 이동하여 OAuth 인증 요청

1
2
3
<a href="http://localhost:8080/kakaoLogin">
    <img src="img/kakao.png" width="50">
</a>

📌 로그인 요청 후 처리 : member.js

  • 카카오 로그인 성공 후 받은 Authorization 토큰을 저장
  • 세션 또는 쿠키에서 Authorization이 있으면 자동 로그인 유지
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let Authorization = sessionStorage.getItem("Authorization");
const nickname = sessionStorage.getItem("nickname");

if (Authorization && nickname) {
  axios.defaults.headers.common["Authorization"] = Authorization; 
  document.getElementById("loginSpan").innerHTML = `${nickname}  
  <button class="btn btn-danger btn-sm" id="logoutBtn">Logout</button>`;
} else {
  Authorization = getCookie("Authorization");
  email = getCookie("email");
  if (Authorization && email) {
    axios.defaults.headers.common["Authorization"] = Authorization; 
    document.getElementById("loginSpan").innerHTML = `${email}  
    <button class="btn btn-danger btn-sm" id="logoutBtn">Logout</button>`;
  }
}

📌 로그아웃 처리

  • 카카오 로그아웃 시 세션과 쿠키 초기화 후 페이지 새로고침
1
2
3
4
5
6
7
8
9
10
11
12
13
document.getElementById("loginSpan").addEventListener("click", async (event) => {
  if (event.target.id == "logoutBtn") {
    await axios.post("http://localhost:8080/logout");
    sessionStorage.removeItem("nickname");
    sessionStorage.removeItem("Authorization");
    axios.defaults.headers.common["Authorization"] = ""; 

    removeCookie("Authorization");
    removeCookie("email");

    window.location.reload();
  }
});

📌 쿠키 관련 유틸리티 함수

  • 쿠키에서 Authorization 가져오거나 삭제하는 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getCookie(cname) {
  let name = cname + "=";
  let decodedCookie = decodeURIComponent(document.cookie);
  let ca = decodedCookie.split(";");
  for (let i = 0; i < ca.length; i++) {
    let c = ca[i];
    while (c.charAt(0) == " ") {
      c = c.substring(1);
    }
    if (c.indexOf(name) == 0) {
      return c.substring(name.length, c.length);
    }
  }
  return "";
}

function removeCookie(cname) {
  document.cookie = cname + "=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}

프론트엔드 흐름

1️⃣ 사용자가 카카오 로그인 버튼 클릭 2️⃣ 백엔드(http://localhost:8080/kakaoLogin)로 이동 → OAuth 인증 요청 3️⃣ 카카오에서 인증 후, 백엔드에서 Authorization 토큰 발급 4️⃣ 클라이언트가 sessionStorage 또는 쿠키에 토큰 저장 5️⃣ 이후 페이지 새로고침 시 자동 로그인 유지 6️⃣ 로그아웃하면 세션과 쿠키 삭제 후 새로고침


프로젝트에 적용한 전체 코드 참고

Kakao OAuth


END

This post is licensed under CC BY 4.0 by the author.