본문 바로가기
개발

[Spring] Spring Security, OAuth 2.0, JWT로 카카오 로그인 구현하기

by 주주병 2024. 11. 22.
728x90
반응형

카카오 OAuth란?

웹페이지나 앱을 사용할 때 카카오 로그인을 이용해본 적이 있을 겁니다. 이러한 편리한 기능은 어떻게 구현하는 걸까요?

바로 카카오가 제공하는 OAuth 시스템을 이용하면 쉽게 구현할 수 있습니다.

 

사전설정

 

카카오계정

 

accounts.kakao.com

위의 링크를 클릭해서 카카오 디벨롭퍼에 들어가서 애플리케이션을 생성해 주셔야 합니다.

 

만드셨으면 이렇게 카카오 로그인을 ON 상태로 만들어 주시면 됩니다.

또한 아래처럼 메뉴바에서 앱 키 항목에 들어가셔야 합니다.

1. 앱 키 -> REST API 키에서 키를 복사한 뒤 따로 저장해 놓습니다.

 

 

2. 플랫폼 -> Web에 도메인을 등록해줘야 합니다. 저는 로컬로 테스트했기에 위의 도메인을 등록해 줬습니다.

 

3. 카카오 로그인 클릭 -> Redirect URI을 등록해줘야 합니다.

원하시는 URI를 입력하셔도 좋은데 여기서는 http://localhost:8080/auth/login/kakao로 하겠습니다.

 

4. 동의항목에 들어오셔서 닉네임, 프로필 사진, 이메일을 필수 동의를 해주셔야 3가지 정보를 우리가 카카오에서 받아올 수 있습니다.

 

 

카카오 로그인 인증 과정

카카오 로그인 과정

 

사용자에게 카카오 로그인 화면 보여주기

사용자에게 해당 화면을 보여주려면 어떻게 해야할까요? 


      
https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${clientId}&redirect_uri=${redirectUri}

https://kauth.kakao.com/oauth/authorize의 링크로 GET 요청을 보내면 됩니다.

그렇지만 중요한 거는 쿼리 파라미터에 우리가 위에서 발급한 REST API KEY를 clientId에 REDIRECT_URI를 redirect_uri에 넣어주는 것입니다..

 

인가 코드 받기

사용자가 로그인에 성공하면 어떻게 될까요?

카카오는 해당 사용자의 로그인이 성공한 경우 우리가 쿼리 파라미터에 넣어준 REDIRECT_URI로 인가 코드를 보내줍니다.


      
@RestController
@RequiredArgsConstructor
public class AuthKakaoController {
private final KakaoAuthService kakaoAuthService;
@GetMapping("/auth/login/kakao")
public ResponseEntity<?> kakaoLogin(@RequestParam("code") String authorizationCode) {
// 카카오 OAuth를 통해 로그인 처리 및 JWT 토큰 생성
TokenResponseDto token = kakaoAuthService.loginWithKakao(authorizationCode);
// JWT 토큰 반환
return ResponseEntity.ok(token);
}
}

 

 

따라서 이렇게 REDIRECT_URI를 캐치하는 컨트롤러를 만들어 줘야 합니다.

카카오는 인가 코드를 위의 사진처럼 code라는 쿼리 파라미터로 전달해 줍니다.

 

액세스 토큰 받기

인가 코드를 받았다면 우리는 이 인가 코드를 이용해서 카카오에 post 요청을 통해서 access Token을 가져와야 합니다.

 

 

위의 사진에 나와있는 URL에 헤더와 본문에 아래와 같은 정보를 담아서 POST 요청을 보내면 토큰을 받을 수 있습니다.

위처럼 카카오는 인가 코드를 통해 특정 url로 POST 요청을 보내면 액세스 토큰을 보내줍니다.

그렇다면 어떻게 백엔드 부분에서 POST 요청을 보낼 수 있을까요?

 

아래의 코드를 보시면 스프링 프레임워크에서는 백엔드에서 HTTP 요청을 보내고 응답을 변환할 수 있는 기능을 제공합니다.

바로 restTemplate이라고 합니다.


      
@Component
public class KakaoUtil {
@Value("${spring.kakao.auth.client}")
private String clientId;
@Value("${spring.kakao.auth.redirect}")
private String redirectUri;
@Value("${spring.kakao.auth.token-url}")
private String tokenUrl;
@Value("${spring.kakao.auth.user-url}")
private String userUrl;
private final RestTemplate restTemplate = new RestTemplate();
public KakaoTokenResponse getAccessToken(String authorizationCode) {
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", "authorization_code");
body.add("client_id", clientId);
body.add("redirect_uri", redirectUri);
body.add("code", authorizationCode);
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(body, headers);
ResponseEntity<KakaoTokenResponse> response = restTemplate.postForEntity(tokenUrl, request, KakaoTokenResponse.class);
return response.getBody();
}
public KakaoUserResponse getUserInfo(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Void> request = new HttpEntity<>(headers);
ResponseEntity<KakaoUserResponse> response = restTemplate.exchange(userUrl, HttpMethod.GET, request, KakaoUserResponse.class);
return response.getBody();
}
}

 

이렇게 토큰 URL에 요청을 보내고 응답을 아래의 DTO로 받아올 수 있습니다.


      
@Data
public class KakaoTokenResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("expires_in")
private Long expiresIn;
@JsonProperty("refresh_token_expires_in")
private Long refreshTokenExpiresIn;
private String scope;
}
@JsonProperty은 Jackson 라이브러리에서 제공하는 애너테이션으로, JSON 데이터와 Java 객체 간의 직렬화(Serialize)와 역직렬화(Deserialize) 시 특정 JSON 속성과 Java 객체의 필드 이름을 매핑하는 데 사용됩니다.

 

아래의 그림은 카카오가 보내주는 응답 데이터입니다.

 

 

사용자 로그인 처리

이제 거의 다 왔습니다. 인가 코드도 받았고 액세스 토큰도 받았습니다.

이제 해야할 것은 이 액세스 토큰을 바탕으로 카카오에 사용자의 정보를 얻는 것입니다.

 

 

해당 URL에 액세스 토큰을 담아서 요청을 하면 카카오는 아래와 같은 정보를 전달해 줍니다.

 

그러면 우리는 또 요청을 보내고 응답에 해당하는 DTO로 받아주기만 하면 되겠죠?

이 부분은 위에 있는 KakaoUtils에서 이 부분을 사용 하시면 됩니다.


      
public KakaoUserResponse getUserInfo(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(accessToken);
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<Void> request = new HttpEntity<>(headers);
ResponseEntity<KakaoUserResponse> response = restTemplate.exchange(userUrl, HttpMethod.GET, request, KakaoUserResponse.class);
return response.getBody();
}

 

UserResponse는 아래의 형식으로 만들어 봤습니다.


      
@Data
public class KakaoUserResponse {
private Long id;
@JsonProperty("connected_at")
private String connectedAt;
@JsonProperty("kakao_account")
private KakaoAccount kakaoAccount;
@Data
public static class KakaoAccount {
private String email;
@JsonProperty("profile")
private Profile profile;
@Data
public static class Profile {
private String nickname;
@JsonProperty("thumbnail_image_url")
private String thumbnailImageUrl;
@JsonProperty("profile_image_url")
private String profileImageUrl;
}
}
}

 

 

백엔드에서의 처리

위처럼 DTO를 만들어서 응답을 받으면 우리는 해당 사용자의 카카오 정보를 다 받아온 것입니다.

이제 추가로 무엇을 해줘야 할까요? 바로 백엔드에서의 해당 멤버를 Repository에 처리를 해줘야 합니다.

 


      
public interface KakaoAuthService {
TokenResponseDto loginWithKakao(String authorizationCode);
}
@Service
@RequiredArgsConstructor
public class KakaoAuthServiceImpl implements KakaoAuthService {
private final KakaoUtil kakaoUtil;
private final MemberService memberService;
private final JwtUtil jwtUtil;
private final RefreshTokenService refreshTokenService;
@Override
public TokenResponseDto loginWithKakao(String authorizationCode) {
// Step 1: 카카오에서 Access Token 가져오기
KakaoTokenResponse tokenResponse = kakaoUtil.getAccessToken(authorizationCode);
// Step 2: Access Token으로 사용자 정보 가져오기
KakaoUserResponse userInfo = kakaoUtil.getUserInfo(tokenResponse.getAccessToken());
// Step 3: 사용자 정보 데이터베이스 처리
Member member = memberService.findByKakaoId(String.valueOf(userInfo.getId()));
if (member == null) {
member = memberService.saveNewMember(
String.valueOf(userInfo.getId()),
userInfo.getKakaoAccount().getEmail(),
userInfo.getKakaoAccount().getProfile().getNickname(),
userInfo.getKakaoAccount().getProfile().getProfileImageUrl()
);
}
String emailLocalPart = member.getEmail().split("@")[0];
// Step 5: JWT 토큰 생성
String accessToken = jwtUtil.generateAccessToken(emailLocalPart);
String refreshToken = refreshTokenService.generateAndStoreRefreshToken(emailLocalPart);
return new TokenResponseDto(accessToken, refreshToken);
}
}

 

위의 코드처럼 우리의 Repository를 이용해서 로그인 처리를 해줘야 합니다.

저는 Spring Security를 사용했기에 따로 액세스 토큰과 리프레쉬 토큰을 만들어서 반환해 줬습니다.

 

위의 과정들을 순서대로 다 했을 경우,

이렇게 저만의 액세스 토큰과 리프레쉬 토큰을 받는 결과를 볼 수 있습니다.

728x90
반응형