728x90
반응형
다음은 스프링에서 구현해보겠습니다.
일단 yml 파일을 건드려 줍니다.
security:
oauth2:
client:
registration:
kakao:
client-id: ${CLIENT_ID}
client-secret: ${CLIENT_SECRET}
client-authentication-method: client_secret_post
client-name: kakao
authorization-grant-type: authorization_code
redirect-uri: http://localhost:8080/login/oauth2/code/kakao
scope:
- profile_nickname
- account_email
provider:
kakao:
authorization-uri: https://kauth.kakao.com/oauth/authorize
token-uri: https://kauth.kakao.com/oauth/token
user-info-uri: https://kapi.kakao.com/v2/user/me
user-name-attribute: id
client-id 가 Rest-api 키 이고
client-secret 키가 secret 키 입니다.
scope는 카카오 서버에서 받을 데이터 입니다.
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
build.gradle에 이것도 추가를 해 줍니다.
.oauth2Login(oauth2 -> oauth2
.successHandler(successHandler) // 커스텀 성공 핸들러
.userInfoEndpoint(userInfo -> userInfo
.userService(oauth2UserService) // 사용자 정보 서비스 등록
)
);
security config 에 이것을 추가 해줘야합니다.
Oauth2UserService
@Service
@RequiredArgsConstructor
@Slf4j
public class Oauth2UserService extends DefaultOAuth2UserService {
private final JwtTokenProvider jwtTokenProvider;
private final MemberRepository memberRepository;
private final TypeChange typeChange;
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("Oauth2UserService.loadUser called");
OAuth2User oAuth2User = super.loadUser(userRequest);
// 클라이언트 ID로 OAuth 공급자 확인 (카카오인지 확인)
String registrationId = userRequest.getClientRegistration().getRegistrationId();
if (!"kakao".equals(registrationId)) {
throw new OAuth2AuthenticationException("Now Only Kakao is supported");
}
// 카카오 사용자 정보 처리
Map<String, Object> attributes = oAuth2User.getAttributes();
log.info("Kakao user attributes: {}", attributes);
// 카카오 계정 정보 추출
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");
String email = (String) kakaoAccount.get("email");
String nickname = (String) ((Map<String, Object>) attributes.get("properties")).get("nickname");
// 기존 사용자 체크
Member member = memberRepository.findByEmail(email).orElse(null);
// 기존 사용자가 없으면 새로 생성
if (member == null) {
member = typeChange.memberCreateDtoToMember(new LoginSignUpReqDto(email, null, nickname), null);
memberRepository.save(member);
}
// 인증 객체 반환
return new DefaultOAuth2User(
List.of(new SimpleGrantedAuthority("ROLE_USER")),
attributes,
"id"
);
}
}
이 코드는 Spring Security에서 OAuth 2.0 로그인 처리 중 사용자 정보를 로드하고 커스터마이징하는 OAuth2UserService 구현체입니다.
주요 역할은 카카오 인증 성공 후 사용자 정보를 처리하고, DB와 동기화하거나 신규 사용자를 생성한 뒤, 인증된 사용자 객체를 반환하는 것입니다.
OAuth2User oAuth2User = super.loadUser(userRequest);
기본 구현체를 사용해 OAuth 2.0 인증 서버(카카오)에서 사용자 정보를 가져옵니다.
SuccessHandler
package growup.spring.springserver.global.common;
import growup.spring.springserver.global.config.JwtTokenProvider;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.NoArgsConstructor;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.annotations.Comment;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.Map;
import java.util.Optional;
@Slf4j
@Component
@RequiredArgsConstructor
public class OAuth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final JwtTokenProvider jwtTokenProvider;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException {
// OAuth2User 정보 가져오기
OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal();
// 사용자 정보 추출 (카카오 사용자 정보)
Optional<Map<String, Object>> kakaoAccountOpt = safeCastToMap(oAuth2User.getAttributes().get("kakao_account"));
String email = kakaoAccountOpt
.map(kakaoAccount -> (String) kakaoAccount.get("email"))
.orElseThrow(() -> new IllegalArgumentException("Kakao account data is missing"));
Optional<Map<String, Object>> propertiesOpt = safeCastToMap(oAuth2User.getAttributes().get("properties"));
String nickname = propertiesOpt
.map(properties -> (String) properties.get("nickname"))
.orElse("DefaultNickname"); // 대체값을 제공하거나 예외를 던질 수 있음
String role = "ROLE_USER"; // 기본 사용자 역할
// JWT 토큰 생성
String memberSpecification = String.format("%s:%s:%s", email, nickname, role);
String accessToken = jwtTokenProvider.createAccessToken(memberSpecification);
log.info("accessToken :{}",accessToken);
// HTTP 응답 헤더에 토큰 추가
response.addHeader("Authorization", "Bearer " + accessToken);
String targetUrl = String.format("http://localhost:3000/oauth/kakao/callback?token=%s", accessToken);
log.info("Redirecting to: {}", targetUrl);
getRedirectStrategy().sendRedirect(request, response, targetUrl);
}
@SuppressWarnings("unchecked")
private Optional<Map<String, Object>> safeCastToMap(Object obj) {
if (obj instanceof Map) {
return Optional.of((Map<String, Object>) obj);
}
return Optional.empty();
}
}
이 코드는 OAuth2 인증이 성공했을 때 실행되는 OAuth2AuthenticationSuccessHandler입니다. Spring Security가 OAuth2 로그인 과정을 처리하고 사용자가 성공적으로 인증되면, 이 핸들러가 호출되어 JWT 토큰을 생성하고, 클라이언트(프론트엔드)로 리디렉션합니다.
그러면 프론트에드에서
import React, { useEffect } from "react";
function KakaoLoginCallback() {
useEffect(() => {
const queryParams = new URLSearchParams(window.location.search);
const token = queryParams.get("token");
if (token) {
localStorage.clear();
localStorage.setItem("accessToken", token);
// 저장된 토큰 확인
console.log("Token saved in localStorage:", localStorage.getItem("accessToken"));
window.location.replace("/main");
} else {
console.error("Token is missing ");
}
}, []);
return null;
}
export default KakaoLoginCallback;
프론트에서 토큰을 받아 브라우저에 저장해주면 됩니다.
반응형
댓글