Skip to content

Commit

Permalink
Merge pull request #43 from Orange-Co/feature/auth
Browse files Browse the repository at this point in the history
Feat: implement KAKAO social login
  • Loading branch information
Kang1221 authored Aug 10, 2024
2 parents 986e8cc + be97e8e commit 50b412b
Show file tree
Hide file tree
Showing 16 changed files with 432 additions and 45 deletions.
15 changes: 12 additions & 3 deletions src/main/java/co/orange/ddanzi/controller/AuthController.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
package co.orange.ddanzi.controller;

import co.orange.ddanzi.dto.auth.LoginDto;
import co.orange.ddanzi.domain.user.enums.LoginType;
import co.orange.ddanzi.dto.auth.SigninRequestDto;
import co.orange.ddanzi.global.common.response.ApiResponse;
import co.orange.ddanzi.service.AuthService;
import com.fasterxml.jackson.core.JsonProcessingException;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand All @@ -16,7 +18,14 @@ public class AuthController {
private final AuthService authService;

@PostMapping("/signin/test")
ApiResponse<?> signin(@RequestBody LoginDto requestDto){
return authService.testSignin(requestDto.getIdToken());
ApiResponse<?> test(@RequestBody SigninRequestDto requestDto){
return authService.testSignin(requestDto.getToken());
}

@PostMapping("/signin")
ApiResponse<?> signin(@RequestBody SigninRequestDto requestDto) throws JsonProcessingException {
if(requestDto.getType().equals(LoginType.KAKAO))
return authService.kakaoSignIn(requestDto.getToken());
return authService.kakaoSignIn(requestDto.getToken());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,6 @@ public class Authentication extends BaseTimeEntity {
@Column(name = "name", nullable = false, length = 255)
private String name; //이름

@Column(name = "email", nullable = false, length = 320)
private String email; //이메일

@Column(name = "phone", nullable = false, length = 15)
private String phone; //전화번호

Expand All @@ -48,10 +45,9 @@ public class Authentication extends BaseTimeEntity {


@Builder
public Authentication(User user, String name, String email, String phone, LocalDate birth, Sex sex, Nation nation) {
public Authentication(User user, String name, String phone, LocalDate birth, Sex sex, Nation nation) {
this.user = user;
this.name = name;
this.email = email;
this.phone = phone;
this.birth = birth;
this.sex = sex;
Expand Down
14 changes: 7 additions & 7 deletions src/main/java/co/orange/ddanzi/domain/user/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import co.orange.ddanzi.global.common.domain.BaseTimeEntity;
import co.orange.ddanzi.domain.user.enums.LoginType;
import co.orange.ddanzi.domain.user.enums.MemberStatus;
import co.orange.ddanzi.domain.user.enums.UserStatus;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
Expand All @@ -18,27 +18,27 @@ public class User extends BaseTimeEntity {
@Column(name = "user_id")
private Long id; //유저 고유 ID

@Column(name = "login_id")
private String loginId; //로그인 ID
@Column(name = "email", nullable = false, length = 320)
private String email; //가입 이메일

@Enumerated(EnumType.STRING)
@Column(name = "type")
private LoginType type; //로그인 타입(KAKAO/APPLE)

@Column(name = "nickname", nullable = false, length = 10)
@Column(name = "nickname", nullable = false, unique = true, length = 20)
private String nickname; // 닉네임 -> 자동생성

@Enumerated(EnumType.STRING)
@Column(name = "status")
private MemberStatus status; //상태(ACTIVATE/SLEEP/DELETE)
private UserStatus status; //상태(ACTIVATE/SLEEP/DELETE)

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "authentication_id")
private Authentication authentication;

@Builder
public User(String loginId, LoginType type, String nickname, MemberStatus status) {
this.loginId = loginId;
public User(String email, LoginType type, String nickname, UserStatus status) {
this.email = email;
this.nickname = nickname;
this.type = type;
this.status = status;
Expand Down
17 changes: 0 additions & 17 deletions src/main/java/co/orange/ddanzi/domain/user/enums/MemberStatus.java

This file was deleted.

4 changes: 2 additions & 2 deletions src/main/java/co/orange/ddanzi/domain/user/enums/Sex.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

@Getter
public enum Sex {
MAN("남"),
WOMAN("여")
MALE("MALE"),
FEMALE("FEMALE")
;
private final String sex;
Sex(String sex) {
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/co/orange/ddanzi/domain/user/enums/UserStatus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package co.orange.ddanzi.domain.user.enums;

import lombok.Getter;

@Getter
public enum UserStatus {
UNAUTHENTICATED("인증안됨"),
ACTIVATE("활성"),
SLEEP("휴면"),
DELETE("삭제")
;

private final String userStatus;
UserStatus(String userStatus) {
this.userStatus = userStatus;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
import lombok.Getter;

@Getter
public class LoginDto {
private String idToken;
public class SigninRequestDto {
private String token;
private LoginType type;

}
12 changes: 12 additions & 0 deletions src/main/java/co/orange/ddanzi/dto/auth/SigninResponseDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package co.orange.ddanzi.dto.auth;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class SigninResponseDto {
private String accesstoken;
private String refreshtoken;
private String nickname;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public enum Success {
SUCCESS(HttpStatus.OK, "Request successfully processed."),

// 200 OK SUCCESS
SIGNIN_KAKAO_SUCCESS(HttpStatus.CREATED, "Successfully sign in using Kakao"),
SIGNIN_APPLE_SUCCESS(HttpStatus.CREATED, "Successfully sign in using Kakao"),

GET_REDIS_KEY_SUCCESS(HttpStatus.CREATED, "Successfully retrieved the redis key"),

GET_HOME_INFO_SUCCESS(HttpStatus.OK, "Successfully retrieved home information."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public User getUser() {
if (currentUserNickname == null) {
return null;
}
return userRepository.findByLoginId(currentUserNickname)
return userRepository.findByEmail(currentUserNickname)
.orElseThrow(() -> new UserNotFoundException());

}
Expand All @@ -44,7 +44,7 @@ public String getCurrentUserNickname() {

if (principalObject instanceof UserDetails) {
UserDetails userDetails = (UserDetails) principalObject;
log.info("id token -> {}", userDetails.getUsername());
log.info("email -> {}", userDetails.getUsername());
return userDetails.getUsername();
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
String path = request.getRequestURI();
return path.startsWith("/api/v1/auth")
|| path.equals("/api/v1/search")
;
}
}
24 changes: 22 additions & 2 deletions src/main/java/co/orange/ddanzi/global/config/jwt/JwtUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import java.util.Collections;
import java.util.Date;
import java.util.concurrent.TimeUnit;


@Slf4j
Expand All @@ -36,9 +37,9 @@ public class JwtUtils {

private final StringRedisTemplate stringRedisTemplate;

public String createAccessToken(String idToken) {
public String createAccessToken(String email) {
Claims claims = Jwts.claims();
claims.put("idToken", idToken);
claims.put("email", email);

long validTime = accessTokenTime;
Date now = new Date();
Expand All @@ -51,7 +52,26 @@ public String createAccessToken(String idToken) {
.compact();
}

public String createRefreshToken(String email) {
Claims claims = Jwts.claims();
claims.put("email", email);
long validTime = refreshTokenTime;
Date now = new Date();
String refreshToken = Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + validTime))
.signWith(SignatureAlgorithm.HS256, jwtSecretKey)
.compact();

updateUserRefreshToken(email, refreshToken);

return refreshToken;
}

public void updateUserRefreshToken(String email, String refreshToken) {
stringRedisTemplate.opsForValue().set(email, refreshToken, refreshTokenTime, TimeUnit.MILLISECONDS);
}

public String resolveJWT(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,7 @@
import java.util.Optional;

public interface UserRepository extends CrudRepository<User, Long> {
Optional<User> findByLoginId(String loginId);
Optional<User> findByEmail(String email);

Optional<User> findByNickname(String nickname);
}
102 changes: 99 additions & 3 deletions src/main/java/co/orange/ddanzi/service/AuthService.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,35 @@
package co.orange.ddanzi.service;

import co.orange.ddanzi.domain.user.User;
import co.orange.ddanzi.domain.user.enums.LoginType;
import co.orange.ddanzi.domain.user.enums.UserStatus;
import co.orange.ddanzi.dto.auth.AuthResponseDto;
import co.orange.ddanzi.dto.auth.SigninResponseDto;
import co.orange.ddanzi.global.common.error.Error;
import co.orange.ddanzi.global.common.response.ApiResponse;
import co.orange.ddanzi.global.common.response.Success;
import co.orange.ddanzi.global.config.jwt.JwtUtils;
import co.orange.ddanzi.repository.UserRepository;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.Optional;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;


@Slf4j
Expand All @@ -25,18 +42,97 @@ public class AuthService {
@Transactional
public ApiResponse<?> testSignin(String idToken){

Optional<User> optionalUser = userRepository.findByLoginId(idToken);
Optional<User> optionalUser = userRepository.findByEmail(idToken);
if(optionalUser.isEmpty()){
return ApiResponse.onFailure(Error.ERROR, null);
}
User user = optionalUser.get();

AuthResponseDto responseDto = AuthResponseDto.builder()
.accesstoken(jwtUtils.createAccessToken(user.getLoginId()))
.accesstoken(jwtUtils.createAccessToken(user.getEmail()))
.nickname(user.getNickname())
.build();
return ApiResponse.onSuccess(Success.SUCCESS, responseDto);
}

@Transactional
public ApiResponse<?> kakaoSignIn(String token) throws JsonProcessingException {
log.info("카카오 로그인 진입");
String email = getKakaoEmail(token);
log.info("카카오 이메일 조회 성공 email: {}", email);
Optional<User> user = userRepository.findByEmail(email);

if (user.isEmpty()){
log.info("카카오 회원 가입 시작");
kakaoSignUp(email);
user = userRepository.findByEmail(email);
}

SigninResponseDto responseDto = SigninResponseDto.builder()
.accesstoken(jwtUtils.createAccessToken(email))
.refreshtoken(jwtUtils.createRefreshToken(email))
.nickname(user.get().getNickname())
.build();
return ApiResponse.onSuccess(Success.SIGNIN_KAKAO_SUCCESS, responseDto);
}


public void kakaoSignUp(String email) {
User user = User.builder()
.email(email)
.type(LoginType.KAKAO)
.status(UserStatus.UNAUTHENTICATED)
.nickname(generateNickname())
.build();
userRepository.save(user);
}

public String getKakaoEmail(String accessToken) throws JsonProcessingException {
log.info("HTTP 해더 생성");
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

log.info("HTTP 요청 보내기");
HttpEntity<String> kakaoUserInfoRequest = new HttpEntity<>(headers);
RestTemplate rt = new RestTemplate();

ResponseEntity<String> response = rt.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.GET,
kakaoUserInfoRequest,
String.class
);

log.info("응답 수신 성공");
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);

String email = jsonNode.get("kakao_account").get("email").asText();

return email;
}

private List<String> loadWordsFromFile(String classpath) throws IOException {
ClassPathResource resource = new ClassPathResource(classpath);
try (BufferedReader reader = new BufferedReader(new InputStreamReader(resource.getInputStream()))) {
return reader.lines().collect(Collectors.toList());
}
}
private String generateNickname() {
try {
List<String> adjectives = loadWordsFromFile("nickname/adjectives.txt");
List<String> animals = loadWordsFromFile("nickname/animals.txt");
String selectedAdjectives = adjectives.get(new Random().nextInt(adjectives.size()));
String selectedAnimals = animals.get(new Random().nextInt(animals.size()));
int randomSuffix = (int) (Math.random() * 100);
return selectedAdjectives + selectedAnimals + randomSuffix;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}


Loading

0 comments on commit 50b412b

Please sign in to comment.