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

카카오 로그인 버튼 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