핵심적인 부분만 업로드 되어 있습니다.

카카오 로그인 버튼 script :
<script>
function kakaoLogin() {
let url = `https://kauth.kakao.com/oauth/authorize?client_id=caefd05138bce05df366ee94d807a224&redirect_uri=http://localhost:8080/oauth&response_type=code`;
location.href = url;
}
</script>- 함수 정의
kakaoLogin함수는 카카오 로그인 프로세스를 시작합니다.- 함수가 호출되면 카카오 인증 서버의 URL을 생성하고
location.href를 사용하여 해당 URL로 리다이렉트합니다.
- URL 구성 요소:
- https://kauth.kakao.com/oauth/authorize: 카카오 OAuth 인증 엔드포인트입니다.
- client_id: 애플리케이션의 클라이언트 ID입니다. (예:
caefd05138bce05df366ee94d807a224) - redirect_uri: 인증 후 리다이렉트 될 애플리케이션의 URL입니다. (예:
http://localhost:8080/oauth) - response_type=code: 응답 타입을 인증 코드로 설정합니다. 카카오 서버는 인증 코드를 반환합니다.
동작 원리
- 사용자가 로그인 버튼을 클릭하면
kakaoLogin함수가 호출됩니다.
kakaoLogin함수는 카카오 인증 서버의 URL을 생성합니다.
- 브라우저는
location.href를 통해 카카오 인증 서버로 리다이렉트됩니다.
- 사용자는 카카오 로그인 페이지에서 인증을 완료합니다.
- 인증이 성공하면, 카카오 서버는 지정된
redirect_uri로 인증 코드를 포함하여 리다이렉트합니다.
이 과정에서 인증 코드는 애플리케이션 서버로 전달되며, 서버는 이 코드를 사용하여 액세스 토큰을 발급받아 사용자의 정보에 접근할 수 있게 됩니다.
Controller :
@GetMapping("/oauth")
public String oauth(@RequestParam("code") String code){
User sessionUser = userService.카카오로그인(code);
session.setAttribute("sessionUser", sessionUser);
return "redirect:/";
}- @GetMapping("/oauth"):
- 이 어노테이션은 HTTP GET 요청을
/oauth경로로 매핑합니다. 사용자가 카카오 로그인 인증 후 리다이렉트될 경로입니다.
- 메서드 시그니처:
public String oauth(@RequestParam("code") String code)@RequestParam("code"): 요청 파라미터에서 code 값을 가져옵니다. 이 code는 카카오 인증 서버가 리다이렉트할 때 URL에 포함된 인증 코드입니다.String으로, 리다이렉트 URL을 반환합니다.- 사용자 서비스 호출:
`User sessionUser = userService.카카오로그인(code);`userService.카카오로그인(code): userService를 호출하여 카카오 인증 코드를 사용해 로그인 처리를 수행합니다. 이 메서드는 code를 사용하여 카카오 서버로부터 액세스 토큰과 사용자 정보를 가져옵니다.sessionUser: 인증된 사용자 정보를 포함하는 User 객체입니다.- 세션에 사용자 정보 저장:
`session.setAttribute("sessionUser", sessionUser);`session.setAttribute("sessionUser", sessionUser): 세션에 사용자 정보를 저장합니다. 이렇게 하면 사용자는 로그인 상태를 유지할 수 있습니다.Service :
public User 카카오로그인(String code) {
// 1. 카카오 로그인 요청
UserResponse.KakaoDTO kakaoDTO = MyHttpUtil.post(code);
// 2. id token 검증
UserResponse.IdTokenDTO idTokenDTO = MyRSAUtil.verify(kakaoDTO.getIdToken());
// 3. 회원가입 유무 확인
String username = "kakao_"+idTokenDTO.getSub();
Optional<User> userOP = userRepository.findByUsername(username);
// 4. 안되어있다면 강제 회원가입
if(userOP.isEmpty()){
User user = User.builder()
.username(username)
.password(UUID.randomUUID().toString())
.provider(ProviderEnum.KAKAO)
.build();
User userPS = userRepository.save(user);
return userPS;
}
// 5. User 객체 리턴
return userOP.get();
}MyHttpUtil.post, MyRSAUtil.verify 등의 내용은 service 객체 설명 이후에 작성되어있습니다.
- 카카오 로그인 요청:
MyHttpUtil.post(code): 카카오 서버에 로그인 요청을 보내고 인증 코드를 사용하여 사용자 정보를 가져옵니다.kakaoDTO: 카카오 서버로부터 받은 응답 데이터입니다. 이 데이터에는 ID 토큰 등이 포함되어 있습니다.
UserResponse.KakaoDTO kakaoDTO = MyHttpUtil.post(code);- ID 토큰 검증:
MyRSAUtil.verify(kakaoDTO.getIdToken()): 받은 ID 토큰을 검증합니다.idTokenDTO: 검증된 ID 토큰 정보입니다. 이 정보에는 사용자의 고유 ID(sub) 등이 포함되어 있습니다.
UserResponse.IdTokenDTO idTokenDTO = MyRSAUtil.verify(kakaoDTO.getIdToken());- 회원가입 유무 확인:
username: 카카오 사용자 고유 ID를 기반으로 생성된 사용자 이름입니다.userRepository.findByUsername(username): 데이터베이스에서 해당 사용자 이름으로 등록된 사용자가 있는지 확인합니다.userOP: Optional로 반환된 사용자 객체입니다.
String username = "kakao_" + idTokenDTO.getSub();
Optional<User> userOP = userRepository.findByUsername(username);- 회원가입이 안되어 있다면 강제 회원가입:
- 사용자가 데이터베이스에 존재하지 않으면 새로운 사용자 객체를 생성합니다.
UUID.randomUUID().toString(): 무작위 패스워드를 생성합니다.userRepository.save(user): 새 사용자 객체를 데이터베이스에 저장합니다.- 저장된 사용자 객체(
userPS)를 반환합니다.
if (userOP.isEmpty()) {
User user = User.builder()
.username(username)
.password(UUID.randomUUID().toString())
.provider(ProviderEnum.KAKAO)
.build();
User userPS = userRepository.save(user);
return userPS;
}- User 객체 반환:
- 이미 데이터베이스에 존재하는 사용자 객체를 반환합니다.
return userOP.get();Repository 내용은 생략합니다.
Service 메서드 Detail
1. 반환 값으로 사용한 DTO 클래스 :
public class UserResponse {
@Data
public static class KakaoDTO {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("id_token")
private String idToken;
}
@Data
public static class IdTokenDTO {
private String sub;
private String nickname;
// 해당 예제에서 아래의 필드들은 사용하지 않으나, 관련된 모든 필드를 작성하여야 예외가 발생하지 않기 때문에 작성
private String aud;
@JsonProperty("auth_time")
private String authTime;
private String iss;
private String exp;
private String iat;
}
}클릭하면 DTO 클래스의 세부 설명을 확인할 수 있습니다.
- 정적 클래스 KakaoDTO:
public static class KakaoDTO {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("id_token")
private String idToken;
}KakaoDTO 클래스는 카카오 로그인 응답 데이터 중 access_token과 id_token 필드를 매핑합니다.@JsonProperty("access_token"): Jackson 라이브러리의 어노테이션으로, JSON 데이터의 access_token 필드를 자바 필드 accessToken에 매핑합니다.@JsonProperty("id_token"): JSON 데이터의 id_token 필드를 자바 필드 idToken에 매핑합니다.- 정적 클래스 IdTokenDTO:
public static class IdTokenDTO {
private String sub;
private String nickname;
// 해당 예제에서 아래의 필드들은 사용하지 않으나, 관련된 모든 필드를 작성하여야 예외가 발생하지 않기 때문에 작성
private String aud;
@JsonProperty("auth_time")
private String authTime;
private String iss;
private String exp;
private String iat;
}IdTokenDTO 클래스는 ID 토큰의 데이터를 매핑합니다.sub: 사용자 고유 ID입니다.nickname: 사용자의 닉네임입니다.aud, authTime, iss, exp, iat)은 예제에서는 사용되지 않지만, JSON 응답 데이터의 모든 필드를 매핑하기 위해 작성되었습니다.@JsonProperty("auth_time"): JSON 데이터의 auth_time 필드를 자바 필드 authTime에 매핑합니다.※참고
JsonProperty 어노테이션:
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("id_token")
private String idToken;- Jackson 라이브러리의
@JsonProperty어노테이션은 JSON 필드 이름과 자바 필드 이름을 매핑합니다.
- JSON 데이터의
access_token필드는accessToken필드에 매핑되고,id_token필드는idToken필드에 매핑됩니다.
2. MyHttpUtil 클래스 :
public class MyHttpUtil {
public static UserResponse.KakaoDTO post(String code){
String redirectUri = "http://localhost:8080/oauth";
String clientId = "caefd05138bce05df366ee94d807a224";
RestTemplate restTemplate = new RestTemplate();
String url = "https://kauth.kakao.com/oauth/token";
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
// 요청 바디 설정
String requestBody = """
grant_type=authorization_code&client_id=${clientId}&redirect_uri=${redirectUri}&code=${code}
""".replace("${clientId}", clientId)
.replace("${redirectUri}", redirectUri)
.replace("${code}", code);
// HttpEntity에 헤더와 바디 추가
HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
// POST 요청
ResponseEntity<UserResponse.KakaoDTO> response = restTemplate.exchange(url, HttpMethod.POST, request, UserResponse.KakaoDTO.class);
// 응답 출력
UserResponse.KakaoDTO kakaoDTO = response.getBody();
return kakaoDTO;
}
}클릭하면 MyHttpUtil 클래스 세부 설명을 확인할 수 있습니다.
- 카카오 OAuth 토큰 요청:
String redirectUri = "http://localhost:8080/oauth";
String clientId = "caefd05138bce05df366ee94d807a224";redirectUri: 카카오 인증 후 리다이렉트될 URI입니다.clientId: 애플리케이션의 클라이언트 ID입니다.- RestTemplate 객체 생성:
RestTemplate restTemplate = new RestTemplate();
String url = "https://kauth.kakao.com/oauth/token";RestTemplate: Spring에서 제공하는 HTTP 요청을 보내기 위한 객체입니다.url: 카카오 인증 서버의 토큰 요청 URL입니다.- HTTP 헤더 설정:
HttpHeaders headers = new HttpHeaders();
headers.set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");HttpHeaders: HTTP 요청 헤더를 설정하기 위한 객체입니다.Content-Type: 요청 본문의 MIME 타입을 설정합니다.- 요청 본문 설정:
String requestBody = """
grant_type=authorization_code&client_id=${clientId}&redirect_uri=${redirectUri}&code=${code}
""".replace("${clientId}", clientId)
.replace("${redirectUri}", redirectUri)
.replace("${code}", code);requestBody: POST 요청의 본문을 설정합니다. 문자열 템플릿을 사용하여 파라미터 값을 치환합니다.- HttpEntity 생성:
HttpEntity<String> request = new HttpEntity<>(requestBody, headers);HttpEntity: HTTP 요청 본문과 헤더를 포함하는 객체입니다.- POST 요청 보내기:
ResponseEntity<UserResponse.KakaoDTO> response = restTemplate.exchange(url, HttpMethod.POST, request, UserResponse.KakaoDTO.class);
restTemplate.exchange: POST 요청을 보내고 응답을 받습니다.UserResponse.KakaoDTO.class: 응답 데이터를 매핑할 DTO 클래스입니다.- 응답 처리:
UserResponse.KakaoDTO kakaoDTO = response.getBody();
return kakaoDTO;3. MyRSAUtil 클래스 :
public class MyRSAUtil {
public static UserResponse.IdTokenDTO verify(String idToken){
String n = "qGWf6RVzV2pM8YqJ6by5exoixIlTvdXDfYj2v7E6xkoYmesAjp_1IYL7rzhpUYqIkWX0P4wOwAsg-Ud8PcMHggfwUNPOcqgSk1hAIHr63zSlG8xatQb17q9LrWny2HWkUVEU30PxxHsLcuzmfhbRx8kOrNfJEirIuqSyWF_OBHeEgBgYjydd_c8vPo7IiH-pijZn4ZouPsEg7wtdIX3-0ZcXXDbFkaDaqClfqmVCLNBhg3DKYDQOoyWXrpFKUXUFuk2FTCqWaQJ0GniO4p_ppkYIf4zhlwUYfXZEhm8cBo6H2EgukntDbTgnoha8kNunTPekxWTDhE5wGAt6YpT4Yw";
String e = "AQAB";
BigInteger bin = new BigInteger(1, Base64.getUrlDecoder().decode(n));
BigInteger bie = new BigInteger(1, Base64.getUrlDecoder().decode(e));
RSAKey rsaKey = new RSAKey.Builder(Base64URL.encode(bin), Base64URL.encode(bie)).build();
try {
// 1. 파싱
SignedJWT signedJWT = SignedJWT.parse(idToken);
// 2. 검증
RSASSAVerifier verifier = new RSASSAVerifier(rsaKey.toRSAPublicKey());
if(signedJWT.verify(verifier)){
System.out.println("ID Token을 검증하였습니다");
String payload = signedJWT.getPayload().toString();
ObjectMapper om = new ObjectMapper();
UserResponse.IdTokenDTO idTokenDTO = om.readValue(payload, UserResponse.IdTokenDTO.class);
return idTokenDTO;
}else{
throw new RuntimeException("id토큰 검증 실패");
}
} catch (Exception ex) {
throw new RuntimeException(ex.getMessage());
}
}
}클릭하면 MyRSAUtil 클래스 세부 설명을 확인할 수 있습니다.
- RSA 키 구성 요소:
String n = "qGWf6RVzV2pM8YqJ6by5exoixIlTvdXDfYj2v7E6xkoYmesAjp_1IYL7rzhpUYqIkWX0P4wOwAsg-Ud8PcMHggfwUNPOcqgSk1hAIHr63zSlG8xatQb17q9LrWny2HWkUVEU30PxxHsLcuzmfhbRx8kOrNfJEirIuqSyWF_OBHeEgBgYjydd_c8vPo7IiH-pijZn4ZouPsEg7wtdIX3-0ZcXXDbFkaDaqClfqmVCLNBhg3DKYDQOoyWXrpFKUXUFuk2FTCqWaQJ0GniO4p_ppkYIf4zhlwUYfXZEhm8cBo6H2EgukntDbTgnoha8kNunTPekxWTDhE5wGAt6YpT4Yw";
String e = "AQAB";n과 e는 RSA 공개 키의 구성 요소입니다. n은 모듈러스이고, e는 공개 지수입니다.- RSA 키 생성:
BigInteger bin = new BigInteger(1, Base64.getUrlDecoder().decode(n));
BigInteger bie = new BigInteger(1, Base64.getUrlDecoder().decode(e));
RSAKey rsaKey = new RSAKey.Builder(Base64URL.encode(bin), Base64URL.encode(bie)).build();BigInteger 객체를 사용하여 n과 e를 디코딩하고, RSA 공개 키를 생성합니다.RSAKey.Builder를 사용하여 RSAKey 객체를 빌드합니다.- JWT 파싱 및 검증:
SignedJWT signedJWT = SignedJWT.parse(idToken);
RSASSAVerifier verifier = new RSASSAVerifier(rsaKey.toRSAPublicKey());SignedJWT.parse(idToken): 입력된 ID 토큰을 파싱합니다.RSASSAVerifier: RSA 공개 키를 사용하여 JWT 서명을 검증하기 위한 객체입니다.- 검증 수행:
if(signedJWT.verify(verifier)) {
System.out.println("ID Token을 검증하였습니다");
String payload = signedJWT.getPayload().toString();
ObjectMapper om = new ObjectMapper();
UserResponse.IdTokenDTO idTokenDTO = om.readValue(payload, UserResponse.IdTokenDTO.class);
return idTokenDTO;
} else {
throw new RuntimeException("id토큰 검증 실패");
}signedJWT.verify(verifier): JWT 서명을 검증합니다.ObjectMapper를 사용하여 페이로드 문자열을 IdTokenDTO 객체로 변환합니다.IdTokenDTO 객체를 반환합니다.- 예외 처리:
catch (Exception ex) {
throw new RuntimeException(ex.getMessage());
}카카오톡 로그인 후 h2-db 확인 결과

Share article