Kakao Developers
이미지 설명에 각 이미지 내용에 대한 위치(경로) 적어뒀으니 참고해 주세요.
1. KakaoDevelopers 접속
2. 애플리케이션 추가하기
3. 애플리케이션 추가
4.Rest API 키 확인
5. Web 플랫폼 등록
6. 사이트 도메인 설정
7. 카카오 로그인 활성화, Redirect URI 등록
8. 동의 항목 설정
사용하려는 개인 정보 동의 설정 ( email, nickname 정보 )
9. (추가) 카카오 로그인 버튼 아이콘 다운로드
Java 카카오 로그인 API 사용방법
Kakao Developers 문서
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
Kakao Developers에서 제공하는 Kakao API를 REST 방식으로 사용하는 방법을 제공해 주는 문서이다.
이 문서에서 모든 상세 방법들을 확인할 수 있다.
카카오 로그인 과정
크게 3가지 step으로 나뉜다.
- 인가 코드 받기
- 토큰 받기
- 토큰으로 사용자 정보 얻기
1) 인가 코드 받기
해당 URL로 client_id와 redirect_url을 포함하여 보내서 인가 코드를 받는다.
2) access token 받기
해당 URL로 헤더와 본문 쿼리파라미터를 세팅하여 POST 방식으로 요청하여 access token을 발급받는다.
이후 해당 accessToken을 이용하여 사용자 정보를 받아올 수 있다.
3) 사용자 정보 가져오기
해당 URL로 헤더에 Access 토큰을 포함한 필수 헤더를 설정하여 요청을 보내서 응답받은 사용자 정보 JSON을 파싱 하여 사용한다.
4) controller로 흐름 파악하기
컨트롤러의 Mapping URI는 이전에 설정한 redirect_uri이다.
맨 처음에 http://kauth.kakao.com/oauth/authorize로 로 요청을 보내면 아래의 컨트롤러로 인가코드(code)를 얻을 수 있다.
이후, 해당 code로 accessToken을 발급받고, accessToken을 이용해서 사용자 정보를 kakao로 요청할 수 있다.
Code로 알아보기
0. KakaoApi
Kakao API 관련 코드를 KakaoApi 클래스에 모아뒀다. 아래에서 여러번 사용한다.
주요 필드, 메서드는 아래와 같다.
public class KakaoApi{
@Value("${kakao.api_key}")
private String kakaoApiKey;
@Value("${kakao.redirect_uri}")
private String kakaoRedirectUri;
//인가 코드를 받아서 accessToken을 반환
public String getAccessToken(String code){...}
//accessToken을 받아서 UserInfo 반환
public HashMap<String, Object> getUserInfo(String accessToken) {...}
//accessToken을 받아서 로그아웃 시키는 메서드
public void kakaoLogout(String accessToken) {...}
}
1. loginForm
우선 Api Key와 Redirect URI의 보안을 위해,
application-private.properties 파일을 생성하여 private 값들을 관리한다.
application-private.properties
숨겨야 할 비밀 값들을 application-private.properties로 분리하여 설정한다.
kakao.api_key=xxx -> api key
kakao.redirect_uri=http://localhost:8080/login/oauth2/code/kakao -> redirect uri
application.properties
application.properties에 아래 코드를 추가하여 application-private.properties 파일의 값들을 include 할 수 있다.
spring.profiles.include=private
loginForm
kakaoApi의 필드로 private 값들을 불러다 사용한다. 방법은 아래의 KakaoApi 부분에서 확인할 수 있다.
// class KakaoApi
@Value("${kakao.api_key}")
private String kakaoApiKey;
@Value("${kakao.redirect_uri}")
private String kakaoRedirectUri;
@RequiredArgsConstructor
@Controller
public class TestController {
private final KakaoApi kakaoApi;
@GetMapping("/login")
public String loginForm(Model model){
model.addAttribute("kakaoApiKey", kakaoApi.getKakaoApiKey());
model.addAttribute("redirectUri", kakaoApi.getKakaoRedirectUri());
return "login";
}
}
login.html (thymeleaf)
'카카오 로그인' 이미지를 클릭하면 1번 인가코드 받기 url로 이동하도록 thymeleaf와 <a> 태그를 이용하여 설정한다.
<!-- kakao button -->
<div class="text-center">
<a href="https://kauth.kakao.com/oauth/authorize"
th:href="@{https://kauth.kakao.com/oauth/authorize(client_id=${kakaoApiKey}, redirect_uri=${redirectUri}, response_type='code')}">
<img src="/images/kakao_login_medium_narrow.png">
</a>
</div>
2. 로그인 페이지
이제 해당 로그인 페이지를 클릭하면 카카오 로그인 화면으로 이동한다.
익숙한 로그인 페이지이다.
여기서 로그인을 완료하면 이전에 설정해둔 redirect_uri로 kakao 측에서 응답을 보내준다.
3. Controller
이 컨트롤러의 kakaoLogin의 @RequestParam으로 code 값이 들어온다.
@RequiredArgsConstructor
@Controller
public class TestController {
private final KakaoApi kakaoApi;
@GetMapping("/login")
public String loginForm(Model model){
model.addAttribute("kakaoApiKey", kakaoApi.getKakaoApiKey());
model.addAttribute("redirectUri", kakaoApi.getKakaoRedirectUri());
return "login";
}
@RequestMapping("/login/oauth2/code/kakao")
public String kakaoLogin(@RequestParam String code){
// 1. 인가 코드 받기 (@RequestParam String code)
// 2. 토큰 받기
String accessToken = kakaoApi.getAccessToken(code);
// 3. 사용자 정보 받기
Map<String, Object> userInfo = kakaoApi.getUserInfo(accessToken);
String email = (String)userInfo.get("email");
String nickname = (String)userInfo.get("nickname");
System.out.println("email = " + email);
System.out.println("nickname = " + nickname);
System.out.println("accessToken = " + accessToken);
return "redirect:/result";
}
}
kakaoApi 클래스의 메서드들을 이용하면, 카카오 로그인 사용자 정보를 얻을 수 있다.
하나씩 알아보자.
4. KakaoApi.getAccessToken(String code)
인가 코드를 받아서 access token을 반환하는 메서드이다.
public String getAccessToken(String code) {
String accessToken = "";
String refreshToken = "";
String reqUrl = "https://kauth.kakao.com/oauth/token";
try{
URL url = new URL(reqUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
//필수 헤더 세팅
conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
conn.setDoOutput(true); //OutputStream으로 POST 데이터를 넘겨주겠다는 옵션.
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
StringBuilder sb = new StringBuilder();
//필수 쿼리 파라미터 세팅
sb.append("grant_type=authorization_code");
sb.append("&client_id=").append(kakaoApiKey);
sb.append("&redirect_uri=").append(kakaoRedirectUri);
sb.append("&code=").append(code);
bw.write(sb.toString());
bw.flush();
int responseCode = conn.getResponseCode();
log.info("[KakaoApi.getAccessToken] responseCode = {}", responseCode);
BufferedReader br;
if (responseCode >= 200 && responseCode < 300) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
}
String line = "";
StringBuilder responseSb = new StringBuilder();
while((line = br.readLine()) != null){
responseSb.append(line);
}
String result = responseSb.toString();
log.info("responseBody = {}", result);
JsonParser parser = new JsonParser();
JsonElement element = parser.parse(result);
accessToken = element.getAsJsonObject().get("access_token").getAsString();
refreshToken = element.getAsJsonObject().get("refresh_token").getAsString();
br.close();
bw.close();
}catch (Exception e){
e.printStackTrace();
}
return accessToken;
}
getAccessToken - 리팩터링
라이브러리 사용
- RestTemplate : HttpHeaders(헤더), MultiValueMap(바디) 구성
- Gson : JSON 파싱
public OAuthToken getOAuthToken(String code){
String reqUrl = "https://kauth.kakao.com/oauth/token";
RestTemplate rt = new RestTemplate();
//HttpHeader 오브젝트
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
//HttpBody 오브젝트
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", AuthConstUtil.KAKAO_API_KEY);
params.add("redirect_uri", AuthConstUtil.KAKAO_REDIRECT_URL);
params.add("code", code);
//http 바디(params)와 http 헤더(headers)를 가진 엔티티
HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
new HttpEntity<>(params, headers);
//reqUrl로 Http 요청 , POST 방식
ResponseEntity<String> response =
rt.exchange(reqUrl, HttpMethod.POST, kakaoTokenRequest, String.class);
String responseBody = response.getBody();
Gson gson = new Gson();
OAuthToken oAuthToken = gson.fromJson(responseBody, OAuthToken.class);
return oAuthToken;
}
5. KakaoApi.getUserInfo(String accessToken)
accessToken을 받아서 사용자 정보를 반환하는 메서드이다.
public HashMap<String, Object> getUserInfo(String accessToken) {
HashMap<String, Object> userInfo = new HashMap<>();
String reqUrl = "https://kapi.kakao.com/v2/user/me";
try{
URL url = new URL(reqUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
conn.setRequestProperty("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
int responseCode = conn.getResponseCode();
log.info("[KakaoApi.getUserInfo] responseCode : {}", responseCode);
BufferedReader br;
if (responseCode >= 200 && responseCode <= 300) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
}
String line = "";
StringBuilder responseSb = new StringBuilder();
while((line = br.readLine()) != null){
responseSb.append(line);
}
String result = responseSb.toString();
log.info("responseBody = {}", result);
JsonParser parser = new JsonParser();
JsonElement element = parser.parse(result);
JsonObject properties = element.getAsJsonObject().get("properties").getAsJsonObject();
JsonObject kakaoAccount = element.getAsJsonObject().get("kakao_account").getAsJsonObject();
String nickname = properties.getAsJsonObject().get("nickname").getAsString();
String email = kakaoAccount.getAsJsonObject().get("email").getAsString();
userInfo.put("nickname", nickname);
userInfo.put("email", email);
br.close();
}catch (Exception e){
e.printStackTrace();
}
return userInfo;
}
getUserInfo - 리팩터링
아래 라이브러리 사용
- RestTemplate : HttpHeaders(헤더)만 구성
- Gson - JsonParser 사용
public KakaoProfile getUserInfo(String accessToken) {
String reqUrl = "https://kapi.kakao.com/v2/user/me";
RestTemplate rt = new RestTemplate();
//HttpHeader 오브젝트
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
//http 헤더(headers)를 가진 엔티티
HttpEntity<MultiValueMap<String, String>> kakaoProfileRequest =
new HttpEntity<>(headers);
//reqUrl로 Http 요청 , POST 방식
ResponseEntity<String> response =
rt.exchange(reqUrl, HttpMethod.POST, kakaoProfileRequest, String.class);
KakaoProfile kakaoProfile = new KakaoProfile(response.getBody());
return kakaoProfile;
}
KakaoProfile 클래스, 생성자
public class KakaoProfile {
private Integer id;
private LocalDateTime connectedAt;
private String email;
private String nickname;
public KakaoProfile(String jsonResponseBody){
JsonParser parser = new JsonParser();
JsonElement element = parser.parse(jsonResponseBody);
this.id = element.getAsJsonObject().get("id").getAsInt();
String connected_at = element.getAsJsonObject().get("connected_at").getAsString();
connected_at = connected_at.substring(0, connected_at.length() - 1);
LocalDateTime connectDateTime = LocalDateTime.parse(connected_at, DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
this.connectedAt = connectDateTime;
JsonObject properties = element.getAsJsonObject().get("properties").getAsJsonObject();
this.nickname = properties.getAsJsonObject().get("nickname").getAsString();
JsonObject kakaoAccount = element.getAsJsonObject().get("kakao_account").getAsJsonObject();
this.email = kakaoAccount.getAsJsonObject().get("email").getAsString();
}
}
6. KakaoApi.kakaoLogout
accessToken을 이용해서 로그아웃 시키는 메서드이다.
public void kakaoLogout(String accessToken) {
String reqUrl = "https://kapi.kakao.com/v1/user/logout";
try{
URL url = new URL(reqUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Authorization", "Bearer " + accessToken);
int responseCode = conn.getResponseCode();
log.info("[KakaoApi.kakaoLogout] responseCode : {}", responseCode);
BufferedReader br;
if (responseCode >= 200 && responseCode <= 300) {
br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
} else {
br = new BufferedReader(new InputStreamReader(conn.getErrorStream()));
}
String line = "";
StringBuilder responseSb = new StringBuilder();
while((line = br.readLine()) != null){
responseSb.append(line);
}
String result = responseSb.toString();
log.info("kakao logout - responseBody = {}", result);
}catch (Exception e){
e.printStackTrace();
}
}
7. Controller 리마인드, 출력
@RequestMapping("/login/oauth2/code/kakao")
public String kakaoLogin(@RequestParam String code){
// 1. 인가 코드 받기 (@RequestParam String code)
// 2. 토큰 받기
String accessToken = kakaoApi.getAccessToken(code);
// 3. 사용자 정보 받기
Map<String, Object> userInfo = kakaoApi.getUserInfo(accessToken);
String email = (String)userInfo.get("email");
String nickname = (String)userInfo.get("nickname");
System.out.println("email = " + email);
System.out.println("nickname = " + nickname);
System.out.println("accessToken = " + accessToken);
return "redirect:/result";
}
위의 메서드들을 이용하여 최종적으로 Map userInfo에 값을 담아서 반환받는다.
컨트롤러에서 해당 값들을 DB에 저장한다던지 하는 로직을 수행하면 된다.
이 예제에서는 출력하는 것으로 마무리한다.
'JAVA & Spring > Spring Security' 카테고리의 다른 글
[Spring Security / JWT] Spring Security - JWT 토큰 인증/인가 (0) | 2024.01.12 |
---|---|
[Spring security] JWT 토큰이란? (0) | 2023.11.26 |
[Spring Security] 스프링 시큐리티 SecurityContext에 직접 Authentication(PrincipalDetails) 넣기 (0) | 2023.10.16 |