03.02 Mini_Project 서버 및 로그인, 회원가입 (추가적으로 스케줄 완료, 취소) 기능 구현

2023. 3. 2. 03:09개발일지

이번에 Mini_Project에서 제가 개발한 부분과 팀원 부재로 추가적으로 개발한 스케줄 완료, 취소 입니다.

Controller
package com.sparta.schedule.controller;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.sparta.schedule.dto.request.UserRequestDto;
import com.sparta.schedule.dto.response.MessageResponseDto;
import com.sparta.schedule.dto.response.UserResponseDto;
import com.sparta.schedule.jwt.JwtUtil;
import com.sparta.schedule.service.KakaoService;
import com.sparta.schedule.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;

@RestController
@RequiredArgsConstructor
@RequestMapping(value = "/user",produces = "application/json")
public class UserController {

    private final UserService userService;
    private final KakaoService kakaoService;



    @PostMapping("/login")
    public ResponseEntity<UserResponseDto> login(@RequestBody UserRequestDto userRequestDto){
        return userService.login(userRequestDto);
    }

    @GetMapping("/idCheck")
    public ResponseEntity<MessageResponseDto> idCheck(@RequestBody UserRequestDto userRequestDto){
        return userService.idCheck(userRequestDto);
    }


    @PostMapping("/signup")
    public ResponseEntity<MessageResponseDto>signup(@Valid @RequestBody UserRequestDto userRequestDto){
        return userService.signup(userRequestDto);
    }


    @PostMapping("/kakao/login")
    public String kakaoLogin(@RequestParam String code, HttpServletResponse response)throws JsonProcessingException {
//        code : 카카오 서버로부터 받은 인가 코드
        String createToken = kakaoService.kakaoLogin(code, response);

//        Cookie 생성 및 직접 브라우저에 Set
        Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, createToken.substring(7));
        cookie.setPath("/");
        response.addCookie(cookie);

        return "redirect:/15.164.158.158:8080/date";
    }


}
patchComplete(Controller)
@PatchMapping("/schedule/complete/{id}")
public ResponseEntity<CompleteResponseDto> patchComplete(@PathVariable Long id,
                                                         @RequestBody CompleteRequestDto completeRequestDto,
                                                         @AuthenticationPrincipal UserDetailsImpl userDetails){
    return scheduleService.patchComplete(id, completeRequestDto, userDetails);
}

@PatchMapping("/schedule/cancel/{id}")
public ResponseEntity<CompleteResponseDto> cancelComplete(@PathVariable Long id,
                                                         @RequestBody CompleteRequestDto completeRequestDto,
                                                         @AuthenticationPrincipal UserDetailsImpl userDetails){
    return scheduleService.cancelComplete(id, completeRequestDto, userDetails);
}
UserRequestDto
package com.sparta.schedule.dto.request;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

import javax.validation.constraints.Pattern;

@Getter
@RequiredArgsConstructor
public class UserRequestDto {


    @Pattern(regexp = "^(?:\\w+\\.?)*\\w+@(?:\\w+\\.)+\\w+$", message = "이메일 형식이 올바르지 않습니다.")
    private String email;

    @Pattern(regexp = "^[a-zA-Z0-9]{8,15}$",message = "비밀번호는 8~15자리 영문 대소문자,숫자를 포함시켜주세요")
    private String password;

    private String username;
}
UserResponseDto
package com.sparta.schedule.dto.response;


import lombok.Builder;
import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public class UserResponseDto {
    private String msg;
    private int code;
    private String username;

    @Builder
    public UserResponseDto(String msg, int code, String username) {
        this.msg = msg;
        this.username = username;
        this.code = code;
    }
    //  HttpStatus 상태 입력으로 Dto 만들기
    public static UserResponseDto User_ServiceCode(HttpStatus status, String msg, String username){
        return UserResponseDto.builder()
                .code(status.value())
                .username(username)
                .msg(msg)
                .build();
    }

//    code입력으로 Dto 만들기
    public static UserResponseDto jwt_filter(int code, String msg, String username){
        return UserResponseDto.builder()
                .code(code)
                .msg(msg)
                .build();
    }



}
KakaoUserInfoDto
package com.sparta.schedule.dto;

import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
public class KakaoUserInfoDto {
    private Long id;
    private String email;
    private String nickname;

    public KakaoUserInfoDto(Long id, String email, String nickname) {
        this.id = id;
        this.email = email;
        this.nickname = nickname;
    }
}
User
package com.sparta.schedule.entity;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Getter
@Entity(name = "users")
@NoArgsConstructor
@AllArgsConstructor
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    @Column(nullable = false)
    private String username;

    private Long kakaoId;

    @Column(nullable = false)
    private String password;


    @Column(nullable = false)
    private String email;


    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;



    @Builder
    public User(String username, String password, String email, Long kakaoId, UserRoleEnum role) {
        this.username = username;
        this.password = password;
        this.email = email;
        this.kakaoId = kakaoId;
        this.role = role;
    }





    public static User user_service(String username, String password, String email, UserRoleEnum role){
        return User.builder()
                .username(username)
                .password(password)
                .role(role)
                .email(email)
                .build();
                }

    public User kakaoIdUpdate(Long kakaoId){
        this.kakaoId = kakaoId;
        return this;
    }

}
UserRoleEnum
package com.sparta.schedule.entity;

public enum UserRoleEnum {
    USER(Authority.USER),   // 사용자 권한
    ADMIN(Authority.ADMIN);   // 관리자 권한

    private final String authority;

    UserRoleEnum(String authority) {
        this.authority = authority;
    }

    public String getAuthority() {
        return this.authority;
    }

    public static class Authority {
        public static final String USER = "ROLE_USER";
        public static final String ADMIN = "ROLE_ADMIN";
    }
}
JwtAuthFilter
package com.sparta.schedule.jwt;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.schedule.dto.response.MessageResponseDto;
import com.sparta.schedule.exception.ErrorCode;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@RequiredArgsConstructor
public class JwtAuthFilter extends OncePerRequestFilter {

    private final JwtUtil jwtUtil;


    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // request 에 담긴 토큰을 가져온다.
        String token = jwtUtil.resolveToken(request);

        // 토큰이 null 이면 다음 필터로 넘어간다.
        if (token == null) {
//            jwtExceptionHandler(response, ErrorType.NOT_VALID_TOKEN);
            filterChain.doFilter(request, response);
            return;
        }

        // 토큰이 유효하지 않으면 예외처리
        if (!jwtUtil.validateToken(token)) {
//            throw new JwtException(ErrorType.NOT_VALID_TOKEN);
            jwtExceptionHandler(response, ErrorCode.NOT_VALID_TOKEN);
            return;
        }

        // 유효한 토큰이라면, 토큰으로부터 사용자 정보를 가져온다.
        Claims info = jwtUtil.getUserInfoFromToken(token);
        setAuthentication(info.getSubject());   // 사용자 정보로 인증 객체 만들기

        // 다음 필터로 넘어간다.
        filterChain.doFilter(request, response);

    }

    private void setAuthentication(String username) {
        SecurityContext context = SecurityContextHolder.createEmptyContext();
        Authentication authentication = jwtUtil.createAuthentication(username); // 인증 객체 만들기
        context.setAuthentication(authentication);

        SecurityContextHolder.setContext(context);
    }

    // 토큰에 대한 오류가 발생했을 때, 커스터마이징해서 Exception 처리 값을 클라이언트에게 알려준다.
    public void jwtExceptionHandler(HttpServletResponse response, ErrorCode error) {
        response.setStatus(error.getCode());
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        try {
            String json = new ObjectMapper().writeValueAsString(MessageResponseDto.jwt_filter(error.getCode(), error.getMessage()));
            response.getWriter().write(json);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

}
JwtUtil
package com.sparta.schedule.jwt;

import com.sparta.schedule.entity.UserRoleEnum;
import com.sparta.schedule.security.UserDetailsServiceImpl;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SecurityException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.security.Key;
import java.util.Base64;
import java.util.Date;

@Slf4j
@Component
@RequiredArgsConstructor
public class JwtUtil {
    private final UserDetailsServiceImpl userDetailsService;
    public static final String AUTHORIZATION_HEADER = "Authorization";
    public static final String AUTHORIZATION_KEY = "auth";
    private static final String BEARER_PREFIX = "Bearer ";
    private static final long TOKEN_TIME = 60 * 60 * 1000L;


    @Value("${jwt.secret.key}")
    private String secretKey;
    private Key key;
    private final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

    @PostConstruct
    public void init() {
        byte[] bytes = Base64.getDecoder().decode(secretKey);
        key = Keys.hmacShaKeyFor(bytes);
    }

    // header 토큰을 가져오기
    public String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
        if (StringUtils.hasText(bearerToken) && bearerToken.startsWith(BEARER_PREFIX)) {
            return bearerToken.substring(7);
        }
        return null;
    }

    // 토큰 생성

    public String createToken(String username, UserRoleEnum role) {

        Date date = new Date();

        return BEARER_PREFIX +
                Jwts.builder()
                        .setSubject(username)
                        .claim(AUTHORIZATION_KEY, role)
                        .setExpiration(new Date(date.getTime() + TOKEN_TIME))
                        .setIssuedAt(date)
                        .signWith(key, signatureAlgorithm)
                        .compact();
    }

    // 토큰 검증
    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (SecurityException | MalformedJwtException e) {
            log.info("Invalid JWT signature, 유효하지 않는 JWT 서명 입니다.");
        } catch (ExpiredJwtException e) {
            log.info("Expired JWT token, 만료된 JWT token 입니다.");
        } catch (UnsupportedJwtException e) {
            log.info("Unsupported JWT token, 지원되지 않는 JWT 토큰 입니다.");
        } catch (IllegalArgumentException e) {
            log.info("JWT claims is empty, 잘못된 JWT 토큰 입니다.");
        }
        return false;
    }

    // 토큰에서 사용자 정보 가져오기
    public Claims getUserInfoFromToken(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token).getBody();
    }

    // 인증 객체 생성
    public Authentication createAuthentication(String username) {
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        return new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    }
}

UserDetailsImpl
package com.sparta.schedule.security;


import com.sparta.schedule.entity.User;
import com.sparta.schedule.entity.UserRoleEnum;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;

public class UserDetailsImpl implements UserDetails {

    private final User user;
    private final String username;

    // 인증이 완료된 사용자 추가하기
    public UserDetailsImpl(User user, String username) {
        this.user = user;
        this.username = username;
    }

    public User getUser() {
        return user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        UserRoleEnum role = user.getRole();
        String authority = role.getAuthority();

        SimpleGrantedAuthority simpleGrantedAuthority = new SimpleGrantedAuthority(authority);
        Collection<GrantedAuthority> authorities = new ArrayList<>();   // 사용자 권한을 GrantedAuthority 로 추상화
        authorities.add(simpleGrantedAuthority);

        return authorities; // GrantedAuthority 로 추상화된 사용자 권한 반환
    }

    @Override
    public String getPassword() {
        return null;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}
UserDetailsServiceImpl
package com.sparta.schedule.security;

import com.sparta.schedule.entity.User;
import com.sparta.schedule.exception.ErrorCode;
import com.sparta.schedule.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service    // custom 하고 Bean 으로 등록 후 사용 가능
@RequiredArgsConstructor
public class UserDetailsServiceImpl implements UserDetailsService {

    private final UserRepository userRepository;

    @Override
    public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
        User user = userRepository.findByEmail(email)
                .orElseThrow(() -> new UsernameNotFoundException(ErrorCode.NOT_FOUND_USER.getMessage()));   // 사용자가 DB 에 없으면 예외처리

        return new UserDetailsImpl(user, user.getEmail());   // 사용자 정보를 UserDetails 로 반환
    }
}
UserRepository
package com.sparta.schedule.repository;

import com.sparta.schedule.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface UserRepository extends JpaRepository<User,Long> {
    Optional<User> findByKakaoId(Long id);

    Optional<User> findByEmail(String email);
}
ScheduleRepository
package com.sparta.schedule.repository;

import com.sparta.schedule.entity.CalendarDate;
import com.sparta.schedule.entity.Schedule;
import com.sparta.schedule.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Optional;

public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
    List<Schedule> findAllByCalendarDate(CalendarDate calendarDate);
//    Optional<Schedule> findByIdAndDate(Long id, String date);
    Optional<Schedule> findByIdAndUser(Long id, User user);

    Optional<Schedule> findByComplete(boolean complete);		//	이 부분입니다.
}
UserService
package com.sparta.schedule.service;

import com.sparta.schedule.dto.response.MessageResponseDto;
import com.sparta.schedule.dto.request.UserRequestDto;
import com.sparta.schedule.dto.response.UserResponseDto;
import com.sparta.schedule.entity.User;
import com.sparta.schedule.entity.UserRoleEnum;
import com.sparta.schedule.exception.ApiException;
import com.sparta.schedule.exception.ErrorCode;
import com.sparta.schedule.jwt.JwtUtil;
import com.sparta.schedule.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Optional;

@Service
@RequiredArgsConstructor
public class UserService {
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;
    private final PasswordEncoder passwordEncoder;

//          회원가입
    @Transactional
    public ResponseEntity<MessageResponseDto> signup(UserRequestDto userRequestDto) {
        String email = userRequestDto.getEmail();
        String password = passwordEncoder.encode(userRequestDto.getPassword());
        String username = userRequestDto.getUsername();

//        회원 중복 확인
        Optional<User> found = userRepository.findByEmail(email);
        if(found.isPresent()){
            throw new ApiException(ErrorCode.DUPLICATED_USERNAME);
        }

        userRepository.save(User.user_service(username,password,email, UserRoleEnum.USER));
        return  ResponseEntity.ok(MessageResponseDto.User_ServiceCode(HttpStatus.OK, "회원가입 성공"));

    }

//          중복 회원 검사 (버튼)
    public ResponseEntity<MessageResponseDto>idCheck(UserRequestDto userRequestDto){
        String email = userRequestDto.getEmail();
        Optional<User> users = userRepository.findByEmail(email);
        
//              중복 회원 일 경우
        if(users.isPresent()){
            throw new IllegalArgumentException("중복 회원입니다.");
        }
//              중복 회원이 아닐 경우 아이디 사용 가능
        return ResponseEntity.ok(MessageResponseDto.User_ServiceCode(HttpStatus.OK,"아이디 사용가능합니다."));
    }



    @Transactional
    public ResponseEntity<UserResponseDto> login(UserRequestDto userRequestDto) {
        String email = userRequestDto.getEmail();
        String password = userRequestDto.getPassword();

//        사용자 확인 및 비밀번호 확인
        Optional<User> user = userRepository.findByEmail(email);
        if(user.isEmpty() || !passwordEncoder.matches(password, user.get().getPassword())){
            throw new ApiException(ErrorCode.NOT_MATCHING_INFO);
    }

        HttpHeaders headers = new HttpHeaders();
        headers.set(JwtUtil.AUTHORIZATION_HEADER, jwtUtil.createToken(user.get().getEmail(), user.get().getRole()));

        return ResponseEntity.ok()
                .headers(headers)
                .body(UserResponseDto.User_ServiceCode(HttpStatus.OK, "로그인 성공", user.get().getUsername()));

//        header에 들어갈 JWT 세팅 body에다가 넣는거
//       String jwtUtil2 = JwtUtil.AUTHORIZATION_HEADER+" "+ jwtUtil.createToken(user.get().getEmail(), user.get().getRole());
//
//        return MessageResponseDto.builder()
//                .code(HttpStatus.OK.value())
//                .msg("성공")
//                .jwtUtil(jwtUtil2)
//                .build();
    }
}
ScheduleService
@Transactional
public ResponseEntity<CompleteResponseDto> patchComplete(Long id,
                                                         CompleteRequestDto completeRequestDto,
                                                         UserDetailsImpl userDetails) {
    Schedule schedule = scheduleRepository.findById(id).orElseThrow(
            () -> new IllegalArgumentException("해당 일정이 없습니다.")
    );

    Schedule check = scheduleRepository.findByComplete(!completeRequestDto.isComplete()).orElseThrow(
            () -> new IllegalArgumentException("완료가 되지 않았습니다.")
    );

    if (check.isComplete()) {
        throw new IllegalStateException("이미 완료된 스케쥴입니다.");
    }

    schedule.updateCompleteStatus(completeRequestDto);
    return ResponseEntity.ok().body(
            CompleteResponseDto.User_ServiceCode(HttpStatus.OK, true, "스케쥴 완료")
    );
}

@Transactional
public ResponseEntity<CompleteResponseDto> cancelComplete(Long id,
                                                         CompleteRequestDto completeRequestDto,
                                                         UserDetailsImpl userDetails) {
    Schedule schedule = scheduleRepository.findById(id).orElseThrow(
            () -> new IllegalArgumentException("해당 일정이 없습니다.")
    );

    Schedule check = scheduleRepository.findByComplete(!completeRequestDto.isComplete()).orElseThrow(
            () -> new IllegalArgumentException("완료가 되지 않았습니다.")
    );

    if (!check.isComplete()) {
        throw new IllegalStateException("완료가 취소된 스케쥴입니다.");
    }

    schedule.updateCompleteStatus(completeRequestDto);
    return ResponseEntity.ok().body(
            CompleteResponseDto.User_ServiceCode(HttpStatus.OK, false, "완료 취소")
    );
}

private void foundUser(Long id, UserDetailsImpl userDetails) {
    Optional<Schedule> found = scheduleRepository.findByIdAndUser(id, userDetails.getUser());
    if (found.isEmpty() && userDetails.getUser().getRole() == UserRoleEnum.USER) {
        throw new IllegalArgumentException("작성자가 일치하지 않습니다.");
    }
}
KakaoService
package com.sparta.schedule.service;


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.schedule.dto.KakaoUserInfoDto;
import com.sparta.schedule.entity.User;
import com.sparta.schedule.entity.UserRoleEnum;
import com.sparta.schedule.jwt.JwtUtil;
import com.sparta.schedule.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import javax.servlet.http.HttpServletResponse;
import java.util.UUID;

@Slf4j
@Service
@RequiredArgsConstructor
public class KakaoService {
    private final PasswordEncoder passwordEncoder;
    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

    public String kakaoLogin(String code, HttpServletResponse response) throws JsonProcessingException {
        // 1. "인가 코드"로 "액세스 토큰" 요청
        String accessToken = getToken(code);

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

        // 3. 필요시에 회원가입
        User kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo);

        // 4. JWT 토큰 반환
        String createToken =  jwtUtil.createToken(kakaoUser.getEmail(), kakaoUser.getRole());
        response.addHeader(JwtUtil.AUTHORIZATION_HEADER, createToken);

        return createToken;
    }


    // 1. "인가 코드"로 "액세스 토큰" 요청
    private String getToken(String code) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP Body 생성
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "authorization_code");
        body.add("client_id", "94c5891ab6cec1f5eddede64f8358dd9");
        body.add("redirect_uri", "http://15.164.158.158:8080/user/kakao/login");
        body.add("code", code);

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
                new HttpEntity<>(body, headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                kakaoTokenRequest,
                String.class
        );

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);
        return jsonNode.get("access_token").asText();
    }

//    ======================================================================================

    // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
    private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoUserInfoRequest = new HttpEntity<>(headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoUserInfoRequest,
                String.class
        );

        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);
        Long id = jsonNode.get("id").asLong();
        String nickname = jsonNode.get("properties")
                .get("nickname").asText();
        String email = jsonNode.get("kakao_account")
                .get("email").asText();

        log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
        return new KakaoUserInfoDto(id, nickname, email);
    }

//  ===============================================================================

    // 3. 필요시에 회원가입
    private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
        // DB 에 중복된 Kakao Id 가 있는지 확인
        Long kakaoId = kakaoUserInfo.getId();
        User kakaoUser = userRepository.findByKakaoId(kakaoId)
                .orElse(null);
        if (kakaoUser == null) {
            // 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
            String kakaoEmail = kakaoUserInfo.getEmail();
            User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
            if (sameEmailUser != null) {
                kakaoUser = sameEmailUser;
                // 기존 회원정보에 카카오 Id 추가
                kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
            } else {
                // 신규 회원가입
                // password: random UUID
                String password = UUID.randomUUID().toString();
                String encodedPassword = passwordEncoder.encode(password);

                // email: kakao email
                String email = kakaoUserInfo.getEmail();

                kakaoUser = new User(kakaoUserInfo.getNickname(),email, encodedPassword, kakaoId, UserRoleEnum.USER);
            }

            userRepository.save(kakaoUser);
        }
        return kakaoUser;
    }


}

 

'개발일지' 카테고리의 다른 글

03.05 TIL (S3 이미지 File 변환 오류)  (0) 2023.03.05
03.05 버킷설정  (0) 2023.03.05
02.28 TIL  (0) 2023.02.28
02.27 Mini_Project 서버 및 로그인, 회원가입 기능 구현(CORS 문제 해결)  (0) 2023.02.27
02.25 CORS  (0) 2023.02.25