공모전을 진행하면서
카카오 API를 이용하면 가산점이 있었기에 😛
만들고 있는 기능 중 회원가입하는 부분이 있었고
카카오는 많은 사용자들이 이용중인 플랫폼이기에 사용자 측면에서 편의성을 주고자
카카오 로그인 기능을 추가하게 되었다.
당시 api 서버 환경은
jdk 17
spring 6.0.8
spring boot 3.0.6
jpa
security
oauth2
시큐리티를 사용하고 있으니
oauth2를 이용하여 로그인까지 연결 시키고 싶었는데
이 방법대로는 내가 원하는 대로 흘러가지 않아서 결국에는 api식을로 개발했던 것 같네 지금 코드를 보니 😇
시간이 더 많았으면 스프링 환경을 더 봐서 코드를 깔끔하게 할 수 있었을 것 같은데
조금 아쉬움이 남네
진행상황
1.카카오 디벨로퍼에서 카카오 로그인 프로세스 이해하기
2. oauth2 개념 이해하기
3. api vs security oauth2
이슈 & 고민 및 해결
프론트에서 카카오 로그인 시 백엔드로 통신하고 인증/인가
확인 후 jwt 토큰 내려주는 프로세스로 개발하고 싶었는데
api 서버에 oauth2까지 같이 사용하고 있다보니
필터에 불필요 예외처리 코드가 너무 늘어나는 것 같아서
카카오 로그인 api로 분리하게 되었다. 😥
참고 레퍼런스
1. 카카오 로그인 이해하기
https://developers.kakao.com/docs/latest/ko/kakaologin/common
2. 카카오 로그인 설정하기
https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite
3. 인증&인가
4. 사용한 코드 github
https://github.com/DongJu-Na/Nadry
TO DO List
1. 스프링 환경 공부
2. 네이버,구글,github login 기능 추가해보기
Example Code
카카오 로그인 api 예제 코드
1. 컨트롤러
package com.nadeul.ndj.controller;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.nadeul.ndj.dto.ApiResponse;
import com.nadeul.ndj.dto.KakaoDto;
import com.nadeul.ndj.dto.MemberDto;
import com.nadeul.ndj.service.KakaoApiService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@Tag(name = "KakaoService", description = "카카오 API")
@RequestMapping("/api/v1/kakao")
@RequiredArgsConstructor
@RestController
public class KakaoApiController<T> {
private final KakaoApiService<T> service;
@PostMapping("/authenticate")
@Operation(summary = "카카오 로그인", description = "카카오 계정으로 로그인")
public ResponseEntity<ApiResponse<MemberDto>> kakaoAuthenticate(@RequestBody KakaoDto request) {
return ResponseEntity.ok(service.kakaoAuthenticate(request));
}
}
2. 서비스
@value 어노테이션 부분에는
카카오에서 발급받은 클라이언트 id와 설정해놓은 redirect url 할당해서 사용하면 된다.
package com.nadeul.ndj.service;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.nadeul.ndj.dto.ApiResponse;
import com.nadeul.ndj.dto.KakaoDto;
import com.nadeul.ndj.dto.MemberDto;
import com.nadeul.ndj.entity.Member;
import com.nadeul.ndj.entity.Token;
import com.nadeul.ndj.enums.ApiResponseEnum;
import com.nadeul.ndj.model.Role;
import com.nadeul.ndj.model.TokenType;
import com.nadeul.ndj.repository.MemberRepository;
import com.nadeul.ndj.repository.TokenRepository;
import lombok.RequiredArgsConstructor;
@Service
@RequiredArgsConstructor
public class KakaoApiService<T> {
private final MemberRepository memberRepository;
private final TokenRepository tokenRepository;
private final JwtService jwtService;
@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
private String CLIENT_ID;
@Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
private String REDIRECT_URI;
@SuppressWarnings("unchecked")
public ApiResponse<MemberDto> kakaoAuthenticate(KakaoDto request) {
String kakaoAccessToken = getKaKaoAccessToken(request.getCode());
HashMap<String, Object> kakaoInfo = getUserKakaoInfo(kakaoAccessToken);
String email = (String) kakaoInfo.get("email");
Optional<Member> existingMember = memberRepository.findByEmail(email);
if(existingMember.isPresent()) {
Member member = Member.builder()
.memId(existingMember.get().getMemId())
.name(kakaoInfo.get("name").toString())
.email(kakaoInfo.get("email").toString())
.profileUrl(kakaoInfo.get("profileUrl").toString())
.build();
var savedUser = memberRepository.save(member);
String jwtToken = jwtService.generateToken(existingMember.get());
String refreshToken = jwtService.generateRefreshToken(existingMember.get());
List<Token> validUserTokens = tokenRepository.findAllValidTokenByUser(existingMember.get().getMemId());
validUserTokens.forEach(token -> {
token.setExpired(true);
token.setRevoked(true);
});
tokenRepository.saveAll(validUserTokens);
Token token = Token.builder()
.member(existingMember.get())
.token(jwtToken)
.tokenType(TokenType.BEARER)
.expired(false)
.revoked(false)
.build();
tokenRepository.save(token);
MemberDto memberDto = new MemberDto();
memberDto.setMemId(existingMember.get().getMemId());
memberDto.setName(existingMember.get().getName());
memberDto.setEmail(existingMember.get().getEmail());
memberDto.setPassword(existingMember.get().getPassword());
memberDto.setBirthDay(existingMember.get().getBirthDay());
memberDto.setRole(existingMember.get().getRole());
memberDto.setProfileUrl(existingMember.get().getProfileUrl());
return ApiResponse.successResponse(ApiResponseEnum.SUCCESS,memberDto,jwtToken,refreshToken);
}else {
Member member = Member.builder()
.name(kakaoInfo.get("name").toString())
.email(kakaoInfo.get("email").toString())
.password(null)
.role(Role.USER)
.birthDay(null)
.profileUrl(kakaoInfo.get("profileUrl").toString())
.build();
var savedUser = memberRepository.save(member);
var jwtToken = jwtService.generateToken(member);
var refreshToken = jwtService.generateRefreshToken(member);
MemberDto memberDto = new MemberDto();
memberDto.setMemId(member.getMemId());
memberDto.setName(member.getName());
memberDto.setEmail(member.getEmail());
memberDto.setPassword(member.getPassword());
memberDto.setBirthDay(member.getBirthDay());
memberDto.setRole(member.getRole());
memberDto.setProfileUrl(member.getProfileUrl());
return ApiResponse.successResponse(ApiResponseEnum.SUCCESS,memberDto,jwtToken,refreshToken);
}
}
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();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
StringBuilder sb = new StringBuilder();
sb.append("grant_type=authorization_code");
sb.append("&client_id=" + CLIENT_ID);
sb.append("&redirect_uri=" + REDIRECT_URI);
sb.append("&code=" + code);
bw.write(sb.toString());
bw.flush();
int responseCode = conn.getResponseCode();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = "";
String result = "";
while ((line = br.readLine()) != null) {
result += line;
}
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(result);
access_Token = rootNode.get("access_token").asText();
refresh_Token = rootNode.get("refresh_token").asText();
br.close();
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
return access_Token;
}
public HashMap<String, Object> getUserKakaoInfo(String access_Token) {
HashMap<String, Object> userInfo = new HashMap<String, Object>();
String reqURL = "https://kapi.kakao.com/v2/user/me";
try {
URL url = new URL(reqURL);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + access_Token);
int responseCode = conn.getResponseCode();
BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = "";
String result = "";
while ((line = br.readLine()) != null) {
result += line;
}
ObjectMapper objectMapper = new ObjectMapper();
JsonNode rootNode = objectMapper.readTree(result.toString());
String id = rootNode.path("id").asText();
JsonNode propertiesNode = rootNode.path("properties");
JsonNode kakaoAccountNode = rootNode.path("kakao_account");
String nickname = propertiesNode.path("nickname").asText();
if (kakaoAccountNode.has("email")) {
String email = kakaoAccountNode.path("email").asText();
userInfo.put("email", email);
}
userInfo.put("name", nickname);
userInfo.put("id", id);
userInfo.put("profileUrl", propertiesNode.path("thumbnail_image").asText());
userInfo.put("role", Role.USER);
} catch (IOException e) {
e.printStackTrace();
}
return userInfo;
}
}
'Develop > Back-End' 카테고리의 다른 글
[Java] 모던 자바(Modern JAVA) 란 무엇인가!!!!😒 (feat. 새롭게 추가된 기능들) (105) | 2024.03.24 |
---|---|
스프링 배치(Spring Batch) 시작하기 !😭 (131) | 2024.02.03 |
Spring Boot 환경에서 Appium을 통해 모바일 환경 테스트 하기 + 플러그인 (09.19 수정) (66) | 2023.09.17 |
Selenium 을 이용하여 특정 URL 요청과 응답 값 모니터링 하는 기능 만들기 (77) | 2023.09.07 |
타임리프(Thymeleaf) 본격적으로 사용하기 ( +@ 타임리프 벤치마크 성능 비교) (59) | 2023.08.31 |