From fe3f537d0cf51acb15545c69302b217a3ae07f45 Mon Sep 17 00:00:00 2001 From: dainshon <81453127+dainshon@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:25:18 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20CHAT-254-BE-=EC=B9=B4=EC=B9=B4=EC=98=A4?= =?UTF-8?q?-=EB=A1=9C=EA=B7=B8=EC=9D=B8=20(#41)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Feat: CHAT-254-BE-카카오-로그인 * Fix: nickname으로 가입/미가입 판별 - Member에 nickname(varchar) 추가 - nickname으로 가입된 사용자인지 판별후 가입 사용자 -> jwt반환 미가입 사용자 -> 회원가입(db에 저장)후 jwt반환 * Fix: findByNickname() 추가 --- build.gradle | 5 + .../controller/Login/KakaoLogin.java | 18 ++ .../controller/Login/LogInController.java | 62 +++++++ .../com/kuit/chatdiary/domain/Member.java | 8 +- .../dto/login/KakaoLoginResponseDTO.java | 14 ++ .../repository/MemberRepository.java | 1 + .../kuit/chatdiary/service/LogInService.java | 166 ++++++++++++++++++ 7 files changed, 270 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/kuit/chatdiary/controller/Login/KakaoLogin.java create mode 100644 src/main/java/com/kuit/chatdiary/controller/Login/LogInController.java create mode 100644 src/main/java/com/kuit/chatdiary/dto/login/KakaoLoginResponseDTO.java create mode 100644 src/main/java/com/kuit/chatdiary/service/LogInService.java diff --git a/build.gradle b/build.gradle index e8f9e4aa..aa33729a 100644 --- a/build.gradle +++ b/build.gradle @@ -27,11 +27,16 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' implementation 'org.springframework.boot:spring-boot-starter-websocket' + implementation 'com.google.code.gson:gson:2.8.8' compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' runtimeOnly('mysql:mysql-connector-java:8.0.30') runtimeOnly('com.h2database:h2') + + implementation 'io.jsonwebtoken:jjwt-api:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3' } tasks.named('test') { diff --git a/src/main/java/com/kuit/chatdiary/controller/Login/KakaoLogin.java b/src/main/java/com/kuit/chatdiary/controller/Login/KakaoLogin.java new file mode 100644 index 00000000..335ea248 --- /dev/null +++ b/src/main/java/com/kuit/chatdiary/controller/Login/KakaoLogin.java @@ -0,0 +1,18 @@ +package com.kuit.chatdiary.controller.Login; + +import org.springframework.beans.factory.annotation.Value; + +public class KakaoLogin { + + @Value("${KAKAO_API_KEY}") + private String kakaoApiKey; + + @Value("${KAKAO_REDIRECT_URI}") + private String kakaoRedirectUri; + + @Value("${S3_BUCKET}") + private String secretKey; + + + +} diff --git a/src/main/java/com/kuit/chatdiary/controller/Login/LogInController.java b/src/main/java/com/kuit/chatdiary/controller/Login/LogInController.java new file mode 100644 index 00000000..28077b7c --- /dev/null +++ b/src/main/java/com/kuit/chatdiary/controller/Login/LogInController.java @@ -0,0 +1,62 @@ +package com.kuit.chatdiary.controller.Login; + +import com.kuit.chatdiary.dto.login.KakaoLoginResponseDTO; +import com.kuit.chatdiary.service.LogInService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.util.Map; + +@Controller +@RequiredArgsConstructor +@Slf4j +public class LogInController { + + @Autowired + public LogInService logInService; + + + @GetMapping("/kakao/callback") + public KakaoLoginResponseDTO login(@RequestParam("code") String code) throws Exception { + //1. 클라이언트에서 로그인 코드를 보내줌 (서버에서 할일 X) + System.out.println("code: "+code); + + //2. 토큰 받기 + String accessToken = logInService.getAccessToken(code); + System.out.println("accessToken: "+accessToken); + + //3. 사용자 정보 받기 + Map userInfo = logInService.getUserInfo(accessToken); + String nickname = (String)userInfo.get("nickname"); + + System.out.println("nickname = " + nickname); + System.out.println("accessToken = " + accessToken); + + if(nickname == null){ + throw new Exception("인증되지 않은 사용자입니다"); + } + + String jwt = logInService.generateJwt(nickname, 3600000); + System.out.println("jwt: "+jwt); + + Boolean isMember = logInService.isMember(nickname); + + //가입된 사용자 -> 바로 로그인 + if(isMember){ + log.info("가입된 사용자입니다."); + return new KakaoLoginResponseDTO(jwt); + }else{//미가입 사용자 -> 회원가입&로그인 + log.info("미가입 사용자입니다."); + logInService.saveMember(nickname); + + return new KakaoLoginResponseDTO(jwt); + } + + + } + +} diff --git a/src/main/java/com/kuit/chatdiary/domain/Member.java b/src/main/java/com/kuit/chatdiary/domain/Member.java index 5c7e8c27..88810b4b 100644 --- a/src/main/java/com/kuit/chatdiary/domain/Member.java +++ b/src/main/java/com/kuit/chatdiary/domain/Member.java @@ -1,10 +1,7 @@ package com.kuit.chatdiary.domain; import jakarta.persistence.*; -import lombok.Builder; -import lombok.Cleanup; -import lombok.Getter; -import lombok.NoArgsConstructor; +import lombok.*; import org.hibernate.annotations.ColumnDefault; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.LastModifiedDate; @@ -16,6 +13,7 @@ @Entity(name = "member") @Getter +@Setter @NoArgsConstructor @EntityListeners(AuditingEntityListener.class) public class Member { @@ -23,6 +21,8 @@ public class Member { @Column(name = "user_id") private Long userId; + private String nickname; + private String email; private String password; diff --git a/src/main/java/com/kuit/chatdiary/dto/login/KakaoLoginResponseDTO.java b/src/main/java/com/kuit/chatdiary/dto/login/KakaoLoginResponseDTO.java new file mode 100644 index 00000000..e9cb35e1 --- /dev/null +++ b/src/main/java/com/kuit/chatdiary/dto/login/KakaoLoginResponseDTO.java @@ -0,0 +1,14 @@ +package com.kuit.chatdiary.dto.login; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class KakaoLoginResponseDTO { + private String jwt; +} diff --git a/src/main/java/com/kuit/chatdiary/repository/MemberRepository.java b/src/main/java/com/kuit/chatdiary/repository/MemberRepository.java index e67eae5b..0aa2891f 100644 --- a/src/main/java/com/kuit/chatdiary/repository/MemberRepository.java +++ b/src/main/java/com/kuit/chatdiary/repository/MemberRepository.java @@ -4,4 +4,5 @@ import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { + Member findByNickname(String nickname); } diff --git a/src/main/java/com/kuit/chatdiary/service/LogInService.java b/src/main/java/com/kuit/chatdiary/service/LogInService.java new file mode 100644 index 00000000..8e9541ec --- /dev/null +++ b/src/main/java/com/kuit/chatdiary/service/LogInService.java @@ -0,0 +1,166 @@ +package com.kuit.chatdiary.service; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.kuit.chatdiary.domain.Member; +import com.kuit.chatdiary.repository.MemberRepository; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; + +import javax.crypto.SecretKey; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.Date; +import java.util.HashMap; + +@Slf4j +@Service +public class LogInService { + + @Autowired + private final MemberRepository memberRepository; + + @Value("${KAKAO_API_KEY}") + private String kakaoApiKey; + + @Value("${KAKAO_REDIRECT_URI}") + private String kakaoRedirectUri; + + public LogInService(MemberRepository memberRepository) { + this.memberRepository = memberRepository; + } + + // AccessToken 발급하는 메서드 + public String getAccessToken(String code) { + log.info("[KakaoService.getAccessToken]"); + + String accessToken = ""; + String reqUrl = "https://kauth.kakao.com/oauth/token"; + + try{ + URL url = new URL(reqUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + + conn.setRequestMethod("POST"); + conn.setDoOutput(true); + + BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream())); + StringBuilder sb = new StringBuilder(); + + sb.append("grant_type=authorization_code"); + sb.append("&client_id=").append(kakaoApiKey); + sb.append("&redirect_uri=").append(kakaoRedirectUri); + sb.append("&code=").append(code); + + bw.write(sb.toString()); + bw.flush(); + + BufferedReader br; + br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String line = ""; + StringBuilder responseSb = new StringBuilder(); + while((line = br.readLine()) != null){ + responseSb.append(line); + } + String result = responseSb.toString(); + System.out.println("responseBody : "+result); + + JsonParser parser = new JsonParser(); + JsonElement element = parser.parse(result); + accessToken = element.getAsJsonObject().get("access_token").getAsString(); + + br.close(); + bw.close(); + + }catch (Exception e){ + e.printStackTrace(); + } + return accessToken; + } + + // 사용자 정보 가져오는 메서드 + public HashMap getUserInfo(String accessToken) { + HashMap userInfo = new HashMap<>(); + String reqUrl = "https://kapi.kakao.com/v2/user/me"; + try{ + URL url = new URL(reqUrl); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("Authorization", "Bearer " + accessToken); + + BufferedReader br; + br = new BufferedReader(new InputStreamReader(conn.getInputStream())); + + String line = ""; + StringBuilder responseSb = new StringBuilder(); + while((line = br.readLine()) != null){ + responseSb.append(line); + } + String result = responseSb.toString(); + JsonParser parser = new JsonParser(); + JsonElement element = parser.parse(result); + + JsonObject properties = element.getAsJsonObject().get("properties").getAsJsonObject(); + String nickname = properties.getAsJsonObject().get("nickname").getAsString(); + userInfo.put("nickname", nickname); + + br.close(); + + }catch (Exception e){ + e.printStackTrace(); + } + return userInfo; + } + + // jwt 발급하는 메서드 + public String generateJwt(String nickname, long expirationTime){ + + SecretKey secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256); + Date expirationDate = new Date(System.currentTimeMillis() + expirationTime); + + return Jwts.builder() + .setSubject(nickname) + .setExpiration(expirationDate) + .signWith(secretKey, SignatureAlgorithm.HS256) + .compact(); + } + + public Boolean isMember(String nickname) { + Member member = memberRepository.findByNickname(nickname); + if(member==null){ + return false; + } + return true; + } + + public void saveMember(String nickname) { + //이메일, 패스워드는 막넣음 + String defaultEmail = nickname+"@"+nickname; + String defaultPassword = nickname+"123"; + + Member member = new Member(); + member.setNickname(nickname); + member.setEmail(defaultEmail); + member.setPassword(defaultPassword); + + memberRepository.save(member); + + if(member.getUserId() !=null){ + log.info("가입 완료!"); + }else{ + log.info("가입 실패!"); + } + + } +}