어제 받았던 사용자 정보를 jsonschema2pojo에 가져가서

미리보기를 하고

복사해서 새 클래스를 생성한다
KakaoProfile.java
@Data
@Generated("jsonschema2pojo")
public class KakaoProfile {
public Long id;
public String connectedAt;
public Properties properties;
public KakaoAccount kakaoAccount;
@Data
class Properties {
public String nickname;
public String profileImage;
public String thumbnailImage;
}
@Data
class KakaoAccount {
public Boolean profileNicknameNeedsAgreement;
public Boolean profileImageNeedsAgreement;
public Profile profile;
@Data
class Profile {
public String nickname;
public String thumbnailImageUrl;
public String profileImageUrl;
public Boolean isDefaultImage;
}
}
}
KakaoController.java
@Controller
@RequestMapping("/kakao")
public class KakaoController {
@ResponseBody
@GetMapping("/callback")
public String kakaoCallback(String code) { //data를 리턴해주는 컨트롤러 함수
//생략----------
//Gson, Json Simple, ObjectMapper 라이브러리
ObjectMapper objectMapper2 = new ObjectMapper();
KakaoProfile kakaoProfile = null;
try {
kakaoProfile = objectMapper2.readValue(response2.getBody(), KakaoProfile.class);
} catch (JsonMappingException e) {
e.printStackTrace();
} catch (JsonProcessingException e) {
e.printStackTrace();
}
System.out.println("카카오 id : " + kakaoProfile.getId());
System.out.println("카카오 nickname : " + kakaoProfile.getProperties().getNickname());
return response2.getBody();
}
실행하니 에러가 발생했다

console에 찍힌 에러
com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "connected_at" (class com.project.kakao.KakaoProfile), not marked as ignorable (4 known properties: "kakaoAccount", "id", "connectedAt", "properties"])
at [Source: (String)"{"id":3340965840,"connected_at":"2024-02-20T12:50:34Z","properties":{"nickname":"서린","profile_image":"http://k.kakaocdn.net/dn/BsIPx/btsEA67iCrY/5ATUQ4Xy5zWM59CwQYCw71/img_640x640.jpg","thumbnail_image":"http://k.kakaocdn.net/dn/BsIPx/btsEA67iCrY/5ATUQ4Xy5zWM59CwQYCw71/img_110x110.jpg"},"kakao_account":{"profile_nickname_needs_agreement":false,"profile_image_needs_agreement":false,"profile":{"nickname":"서린","thumbnail_image_url":"http://k.kakaocdn.net/dn/BsIPx/btsEA67iCrY/5ATUQ4Xy5zWM59CwQYCw71/"[truncated 148 chars]; line: 1, column: 34] (through reference chain: com.project.kakao.KakaoProfile["connected_at"])
2024-02-22 13:43:33,396 ERROR [org.apache.catalina.core.ContainerBase.[Tomcat].[localhost].[/].[dispatcherServlet]] Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "com.project.kakao.KakaoProfile.getId()" because "kakaoProfile" is null] with root cause
java.lang.NullPointerException: Cannot invoke "com.project.kakao.KakaoProfile.getId()" because "kakaoProfile" is null
카카오에서는 snake_case형식으로 넘겨주는데
jsonschema2pojo에서 자동으로 CamelCase로 변경해서 생성해줘서 생긴 에러였다
카카오에서 넘겨주는 형식으로 수정해 실행했더니
@Data
@Generated("jsonschema2pojo")
public class KakaoProfile {
public Long id;
public String connected_at;
public Properties properties;
public KakaoAccount kakao_account;
@Data
class Properties {
public String nickname;
public String profile_image;
public String thumbnail_image;
}
@Data
class KakaoAccount {
public Boolean profile_nickname_needs_agreement;
public Boolean profile_image_needs_agreement;
public Profile profile;
@Data
class Profile {
public String nickname;
public String thumbnail_image_url;
public String profile_image_url;
public Boolean is_default_image;
}
}
}

잘 나타나는 것을 볼 수 있었다
console에서도 
잘 찍히는것을 확인했다
그 다음 이제 카카오유저 정보를 MOUNT 서버에서 사용할 정보로 만드는 과정을 진행한다
KakaoController.java
@Controller
@RequestMapping("/kakao")
public class KakaoController {
@ResponseBody
@GetMapping("/callback")
public String kakaoCallback(String code) { //data를 리턴해주는 컨트롤러 함수
//생략---------
//user오브젝트 : username, password,
System.out.println("카카오 id : " + kakaoProfile.getId());
System.out.println("카카오 nickname : " + kakaoProfile.getProperties().getNickname());
System.out.println("mount서버 유저네임 : " + kakaoProfile.getProperties().getNickname() + "_" + kakaoProfile.getId());
UUID tempPassword = UUID.randomUUID(); //임시 패스워드 만들기
System.out.println("mount서버 패스워드 : " + tempPassword);
return response2.getBody();
}

이때 userEntity에 카카오 사용자 정보를 build하기 위해서
코드를 추가해줬다
KakaoController.java
UserEntity userEntity = UserEntity.builder()
.loginId(kakaoProfile.getProperties().getNickname())
.password(tempPassword)
.name(kakaoProfile.getProperties().getNickname() + "_" + kakaoProfile.getId())
.email(email)
.build();
userBO.addKakaoUser(userEntity);
그런데 여기서 보였던 문제점들이 있다
1. password가 인코딩 되지 않아서 인코딩을 해줘야하며
2. 기존 user테이블에서는 email이 not null로 설정되어있는데
카카오에서는 카카오로그인 사용자의 email을 받아올 수 있는 권한이 없어서 카카오로그인 사용자는 email을 null로 받아야 한다는 점이었다
먼저 password를 인코딩 해주기 위해
클래스를 생성했다
PasswordEncoder.java
package com.project.user;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class PasswordEncoder {
/**
* 비밀번호를 MD5 알고리즘으로 해싱
* @param password 해싱할 비밀번호
* @return 해싱된 비밀번호
*/
public static String encode(String password) {
try {
// MD5 해시 함수를 사용하여 비밀번호를 해싱합니다.
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(password.getBytes());
byte[] byteData = md.digest();
// 바이트 데이터를 16진수 문자열로 변환합니다.
StringBuilder sb = new StringBuilder();
for (byte b : byteData) {
sb.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
// 해당 알고리즘이 없는 경우에 대한 예외 처리
e.printStackTrace();
return null;
}
}
}
그다음 기존 일반 로그인 사용자의 회원가입, 로그인 시 패스워드 해싱 코드 수정
//password 해싱 - md5알고리즘
String hashedPassword = PasswordEncoder.encode(password);
// 일반 회원가입
//input:loginId, password, name, email
//output: id(pk)
public Integer addUser(String loginId, String password, String name, String email) {
UserEntity userEntity = userRepository.save(
UserEntity.builder()
.loginId(loginId)
.password(password)
.name(name)
.email(email)
.build());
return userEntity == null ? null : userEntity.getId();
}
/**
* 카카오 회원가입
* @param userEntity 사용자 엔터티
* @return 등록된 사용자의 ID
*/
public Integer addKakaoUser(UserEntity userEntity) {
UserEntity UserEntity = userRepository.save(userEntity);
return userEntity == null ? null : userEntity.getId();
}
create table `user`(
`id` int not null auto_increment primary key,
`loginId` varchar(15) not null,
`password` varchar(256) not null,
`name` varchar(20) not null,
`email` varchar(80) not null,
`createdAt` timestamp default current_timestamp,
`updatedAt` timestamp default current_timestamp
)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE user MODIFY COLUMN email varchar(80) DEFAULT NULL;
인코딩 완료 한 후 user에 사용자 정보 추가 코드!
+
그 아래에 가입자인지 비가입자인지 체크하는 코드 추가
KakaoController.java
@Controller
@RequestMapping("/kakao")
public class KakaoController {
@Autowired
private UserBO userBO;
@Autowired
private PasswordEncoder passwordEncoder;
@ResponseBody
@GetMapping("/callback")
public String kakaoCallback(String code, HttpSession session) { //data를 리턴해주는 컨트롤러 함수
//생략---------------
//user오브젝트 : username, password
System.out.println("카카오 id : " + kakaoProfile.getId());
System.out.println("카카오 nickname : " + kakaoProfile.getProperties().getNickname());
System.out.println("mount서버 유저네임 : " + kakaoProfile.getProperties().getNickname() + "_" + kakaoProfile.getId());
String rawPassword = UUID.randomUUID().toString(); //임시 패스워드 만들기
String encodedPassword = PasswordEncoder.encode(rawPassword);;
System.out.println("mount서버 패스워드 : " + encodedPassword);
UserEntity kakaoUser = UserEntity.builder()
.loginId(kakaoProfile.getProperties().getNickname())
.password(encodedPassword)
.name(kakaoProfile.getProperties().getNickname() + "_" + kakaoProfile.getId())
.build();
//가입자 혹은 비가입자 체크 해서 처리
Optional<UserEntity> originUserEntity = userBO.getUserEntityByName(kakaoUser.getName());
if (originUserEntity.isPresent()) { // 이미 가입된 회원인 경우
// 해당 회원으로 로그인 처리
System.out.println("기존 회원입니다-----------------");
UserEntity loggedInUser = originUserEntity.get();
session.setAttribute("userId", loggedInUser.getId());
session.setAttribute("userName", loggedInUser.getName());
session.setAttribute("userLoginId", loggedInUser.getLoginId());
// 로그인 성공 메시지 반환
return "로그인 성공";
} else { // 비가입자인 경우
// 회원가입 처리
userBO.addKakaoUser(kakaoUser);
// 회원가입 완료 메시지 반환
return "회원가입 완료";
}
}
}
UserBO.java에도 코드 추가
@Service
public class UserBO {
@Autowired
private UserRepository userRepository;
//생략 ----------------------
//카카오 로그인 가입 비가입 체크
@Transactional(readOnly=true)
public Optional<UserEntity> getUserEntityByName(String name) {
Optional<UserEntity> userEntity = userRepository.findByName(name);
return userEntity;
}
}
처음엔 메소드를 getUserEntityByKakaoName()/findByKakaoName()으로 작성했는데,
console에 이런 에러가 찍혔었다
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'commentBO': Unsatisfied dependency expressed through field 'userBO';
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userBO': Unsatisfied dependency expressed through field 'userRepository';
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userRepository' defined in com.project.user.repository.UserRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.
Caused by: org.springframework.data.repository.query.QueryCreationException: Could not create query for public abstract java.util.
Caused by: java.lang.IllegalArgumentException: Failed to create query for method public abstract java.util.Optional com.project.user.repository.UserRepository.findbyKakaoName(java.lang.String)! No property 'findbyKakaoName' found for type 'UserEntity'
Caused by: org.springframework.data.mapping.PropertyReferenceException: No property 'findbyKakaoName' found for type 'UserEntity'
UserRepository 인터페이스에서 findbyKakaoName 메소드를 찾을 수 없고,
userEntity에 findbyKakaoName과 일치하는 속성이 없으므로
이 메소드를 사용자를 찾는 데 사용할 수 없기 때문에 발생한거였다
이 에러를 통해서 ~~By메소드를 생성할 때 domain, entity에 존재하는 속성으로 작성해야된다는 것을 알았다
여태까지는 메소드 이름 짓기가 쉬운? 당연한? 것들만 만들었던지라 이것도 처음 안 내용이다
getUserEntityByName()/findByName()으로 메소드를 수정했더니 에러가 해결되었다
이 에러를 해결하고 나니 바로 다음 에러가 발생했는데,,
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2024-02-22 16:50:18,096 ERROR [org.springframework.boot.diagnostics.LoggingFailureAnalysisReporter]
***************************
APPLICATION FAILED TO START
***************************
Description:
Field passwordEncoder in com.project.kakao.KakaoController required a bean of type 'com.project.user.PasswordEncoder' that could not be found.
The injection point has the following annotations:
- @org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.project.user.PasswordEncoder' in your configuration.
이 에러는 com.project.kakao.KakaoController에서 passwordEncoder 필드를 사용하려고 하는데, 해당 타입의 빈을 찾을 수 없다 라는 의미이다
확인해보니 class가 아니라 interface로 생성해야 하는 거였다
왜 class는 안되고 에러가 발생한건지 궁금해서 찾아봤는데
interface가 코드에서 둘 이상의 위치, 특히 둘 이상의 파일 또는 함수에서 사용될 객체의 속성 및 함수를 생성해야하는 경우에 사용되기 때문이라고 한다
이 에러는 PasswordEncoder클래스를 interface로 수정하니 해결되었다
여기 까지 완료 하고 실행해보니 "회원가입 완료"가 응답왔고

user테이블에도 사용자의 정보가 저장된것을 확인했다

이제 로그인은 완료 되었으니
카카오 로그인이 완료되면 /home/home-list-view 화면으로 이동하도록 구현하고 싶은데
음,, return을 어디로 해야하는지 헷갈린다