Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 컴파일
- java변수
- 연관관계
- multi-threading
- 상수
- 인텔리제이 한글 깨짐 해결법
- Java데이터 타입
- OPP개념
- continue문
- Java
- 접근제어지시자
- function
- 집합관계
- 포함관계
- 메서드
- this예약어
- 인텔리제이 기초 설정
- JAVA객체지향
- IntelliJ IDEA
- 자바 멀티스레딩
- 시스템 환경 변수 편집
- While
- for문
- break문
- Thread
- 메서드 오버로딩
- 반복문
- JAVA기초
- 생성자
- 형 변환
Archives
- Today
- Total
최원종의 개발 블로그
V14 소셜로그인(카카오) 리팩토링 본문

카카오 소셜 로그인 리팩토링
- Controller → Service 로 책임 이동 (카카오소셜로그인 한 곳에서 전체 흐름 관리)
- 토큰 발급, 프로필 조회, 사용자 생성/조회 로직을 메서드로 분리해 가독성과 재사용성 향상.
UserController 코드 수정
// 1. 동의 항목 승인 이후 카카오 인가 서버에서 인가 코드가 리다이렉트 됨.
@GetMapping("/kakao-redirect")
public String kakaoCallback(@RequestParam(name = "code") String code, HttpSession session) {
// 외부 통신을 할 때 예외처리 권장 사항
try {
User sessionUser = userService.카카오소셜로그인(code);
// 우리 서버 세션에 회원 정보 저장해야 로그인 처리 됨.
session.setAttribute(Define.SESSION_USER, sessionUser);
} catch (Exception e) {
log.error("카카오 로그인 실패 " + e.getMessage());
throw new Exception401("소셜 로그인 실패");
}
return "redirect:/";
}
UserService 코드(초기 파라미터 선언)
@Slf4j
@Service // IoC
@RequiredArgsConstructor // DI
@Transactional(readOnly = true) // 기본적인 읽기 전용 트랜잭션 처리 , 조회시 더티 체킹 안 일어남
public class UserService {
private final UserRepository userRepository;
// DI
private final PasswordEncoder passwordEncoder;
// 초기 파라미터 값을 가져 오는 방법
@Value("${oauth.kakao.client-id}")
private String kakaoClientId;
@Value("${oauth.kakao.client-secret}")
private String kakaoClientSecret;
@Value("${tenco.key}")
private String tencoKey;
//... 생략
1. 카카오소셜로그인 틀 작업
// 0
@Transactional
public User 카카오소셜로그인(String code) {
// 1. 발급 받은 인가 코드로 액세스 토큰 발급 요청
UserResponse.OAuthToken oAuthToken = 카카오액세스토큰발급(code);
// 2. 발급 받은 액세스 토큰으로 사용자 카카오 프로필 조회
UserResponse.KakaoProfile kakaoProfile = 카카오프로필조회(oAuthToken.getAccessToken());
// 3. 응답 받은 결과로 우리 서버에 가입 여부 조회 및 자동 회원 가입 처리
User userEntity = 카카오조회및자동회원가입처리(kakaoProfile);
// 4. 컨트롤러로 User 반환
return userEntity;
}
2. 카카오액세스토큰발급
// 1
private UserResponse.OAuthToken 카카오액세스토큰발급(String code) {
RestTemplate restTemplate1 = new RestTemplate();
HttpHeaders headers1 = new HttpHeaders();
headers1.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
LinkedMultiValueMap<String, String> multiValueMap = new LinkedMultiValueMap();
multiValueMap.add("grant_type", "authorization_code");
multiValueMap.add("client_id", kakaoClientId);
multiValueMap.add("redirect_uri", "http://localhost:8080/kakao-redirect");
multiValueMap.add("code", code);
multiValueMap.add("client_secret", kakaoClientSecret);
// 순서 중요 : 바디 + 헤더 결합 ( HTTP 요청 메세지 구축)
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity(multiValueMap, headers1);
// HTTP 요청 후 응답
ResponseEntity<UserResponse.OAuthToken> response1 = restTemplate1.exchange(
"https://kauth.kakao.com/oauth/token",
HttpMethod.POST,
request,
UserResponse.OAuthToken.class
);
UserResponse.OAuthToken oAuthToken = response1.getBody();
return oAuthToken;
}
3. 카카오 프필 조회 코드
// 2
private UserResponse.KakaoProfile 카카오프로필조회(String token) {
String accessToken = token;
RestTemplate restTemplate2 = new RestTemplate();
HttpHeaders headers2 = new HttpHeaders();
// 주의! 반드시 Bearer + "공백한칸" + 토큰
headers2.add("Authorization", "Bearer " + accessToken);
headers2.add("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
HttpEntity request2 = new HttpEntity(headers2);
// HTTP 요청 2
ResponseEntity<UserResponse.KakaoProfile> response2 = restTemplate2.exchange(
"https://kapi.kakao.com/v2/user/me",
HttpMethod.POST,
request2,
UserResponse.KakaoProfile.class
);
UserResponse.KakaoProfile kakaoProfile = response2.getBody();
return kakaoProfile;
}
4. 카카오조회 및 자동 회원가입 처리
// 3
private User 카카오조회및자동회원가입처리(UserResponse.KakaoProfile kakaoProfile) {
// 고유한 username 생성(중복 방지 용)
String username = kakaoProfile.getKakaoAccount().getProfile().getNickname() + "_" + kakaoProfile.getId();
// 회원 가입 여부 확인
User user = 사용자이름조회(username);
if(user == null) {
log.info("기본 회원이 아님 자동 회원 가입 진행");
User newUser = User.builder()
.username(username)
.password(passwordEncoder.encode(tencoKey)) // 임시 비밀번호 (노출 절대 금지)
.email(username + "@kakao.com") // 임의 이메일 설정 (추후 DB 제약 방지)
.oAuthProvider(OAuthProvider.KAKAO) // 로그인 경로 설정
.build();
String profileImage = kakaoProfile.getKakaoAccount().getProfile().getProfileImageUrl();
if(profileImage != null && !profileImage.isEmpty()) {
newUser.setProfileImage(profileImage); // 카아오 서버에서 받은 이미지 설정
}
// 회원 가입 후 상태 저장
user = userRepository.save(newUser);
} else {
System.out.println("이미 가입된 사용자라 바로 로그인 처리");
}
return user;
}
User 에 빌더 생성자 수정 코드
@Builder
public User(Integer id, String username, String password,
String email, Timestamp createdAt,
String profileImage, OAuthProvider oAuthProvider, List<UserRole> roles) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.createdAt = createdAt;
this.profileImage = profileImage;
this.oAuthProvider = (oAuthProvider != null) ? oAuthProvider : OAuthProvider.LOCAL;
// 1. roles(ArrayList 타입 임) 가 null 빈 리스트로 초기회 (NPE 방지 처리)
this.roles = (roles != null) ? roles : new ArrayList<>();
// 2. roles 가 비어있으면 - USER 기본 권한으로 자동 설정
if(this.roles.isEmpty()) {
this.roles.add(UserRole.builder().role(Role.USER).build());
}
}
UserService 에서 회원가입 부분 수정 (중복 선언 제거)
/**
* 회원 가입 처리
* @param joinDTO (사용자 회원가입 요청 정보)
* @return User (저장된 사용자 정보)
*/
@Transactional
public User 회원가입(UserRequest.JoinDTO joinDTO) {
log.info("회원가입 서비스 시작");
// 회원가입시 사용자 이름 중복 체크
userRepository.findByUsername(joinDTO.getUsername()).ifPresent(user -> {
log.warn("회원가입 실패 - 중복된 사용자명 : {}", user.getUsername());
throw new Exception400("이미 존재하는 사용자 이름입니다");
});
// 프로필 이미지 저장 기능 구현 (선택 사항 임)
String profileImageFilename = null;
if(joinDTO.getProfileImage() != null && joinDTO.getProfileImage().isEmpty() == false) {
try {
// 이미지 파일이 맞는지 검증
if(FileUtil.isImageFile(joinDTO.getProfileImage()) == false) {
throw new Exception400("이미지 파일만 업로드 가능합니다");
}
profileImageFilename = FileUtil.saveFile(joinDTO.getProfileImage(), FileUtil.IMAGES_DIR);
} catch (Exception e) {
// 디스크 공간 없거나, 권한 없음
throw new Exception500("프로필 이미지 저장 실패");
}
}
// 코드 수정
User user = joinDTO.toEntity(profileImageFilename);
String hashPwd = passwordEncoder.encode(joinDTO.getPassword());
user.setPassword(hashPwd);
return userRepository.save(user);
}
application-dev.yml 파일 추가 코드
servlet:
# URL 에 jsessionid 가 노출 되는 것 방지
session:
tracking-modes: cookie
application-dev.yml 전체 코드⬇️
더보기
server:
servlet:
# USL 에 jsessionid 가 노출 되는것 방지
session:
tracking-modes: cookie
encoding:
charset: utf-8
force: true
#파일 업로드 설정
multipart:
enabled: true #파일 업로드 활성화
max-file-size: 50MB #단일 파일 최대 크기 설정 (1MB)
max-request-size: 100MB # 전체 요청(Request) 최대 크기 설정
port: 8080
logging:
level:
root: INFO #모든 라이브러리는 INFO 이상만 출력
com.tenco: DEBUG # 내 프로젝트는 DEBUG 이상 모두 출력
spring:
mustache:
servlet:
# 머스태치 템플릿 엔진에서 request 객체와 세션 객체에 접근할 수 있도록 허용하는 설정 추가
expose-session-attributes: false
expose-request-attributes: true
#데이터베이스 연결 설정 (MySQL)
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/myblog?serverTimezone=Asia/Seoul
username: root
password: root
# driver-class-name: org.h2.Driver
# url: jdbc:h2:mem:test
# username: sa
# password:
h2:
console:
enabled: true
# 초기 데이터 설정
# SQL 초기화 설정
sql:
init:
mode: always # 외부 DB(MySQL)에서도 실행되도록 설정
#mode: never # 절대 실행 안됨
data-locations:
- classpath:db/data.sql
jpa:
# Open Session In View -> false 설정
# true (기본 값) -> 뷰 렌더링까지 DB 세션 유지 됨(LAZY 로딩 가능)
# false : 트랜잭션 종료시 DB 세션 종료 ( Controller, View LAZY 로딩 불가)
# false 사용시 Service 필요한 데이터를 모두 조회하고 응답시 DTO 설계 방식으로 처리 한다.
open-in-view: false
hibernate:
# create 애플리케이션 시작시 테이블 새로 생성
# 기존 데이터는 모드 삭제됨 (개발용)
ddl-auto: create
#SQL 쿼리를 콘솔에 출력 (개발용)
show-sql: true
properties:
hibernate:
# SQL 쿼리를 보기 좋게 포맷팅
format_sql: true
# N + 1 문제를 완화 하기위한 완충 장치 설정
# 기본 WHERE 절에서 in 절(in 쿼리로 변경 해준다)
default_batch_fetch_size: 100
# data.sql 파일을 하이버네티트 초기화 이후에 실행
# data.sql 파일을 Hibernate 초기화 이후에 실행
defer-datasource-initialization: true
# 이메일 발송 설정
mail:
# SMTP 서버 호스트 주소 (Google의 경우 smtp.gmail.com 고정)
host: smtp.gmail.com
# SMTP 서버 포트 번호 (587: TLS(Transport Layer Security) 암호화 통신 전용 포트)
# 인터넷으로 정보를 주고받을 때 정보를 암호화해서 아무도 훔쳐보지 못하게 하는 보안 기술
port: 587
# 발신자 계정 (본인의 전체 Gmail 주소)
username: chl66953@gmail.com
# 발신자 인증 키 (Google 보안 설정에서 생성한 16자리 앱 비밀번호)
# 주의: 일반 구글 로그인 비밀번호가 아님
password: buxdtqfsjsxlxydf
# JavaMail 추가 속성 설정
properties:
mail:
smtp:
# SMTP 서버 인증 활성화 (ID/PW 검사 여부)
auth: true
# STARTTLS 설정 (보안 연결)
starttls:
# 비암호화 연결을 암호화 연결(TLS)로 승격 활성화
enable: true
# TLS 연결을 강제할지 여부 (보안상 true 권장)
required: true
# [타임아웃 설정 - 서버 간 통신 기준]
# 소켓 연결 제한 시간 (단위: 밀리초)
# 서버에 접속을 시도할 때 5초(5000ms) 동안 응답이 없으면 연결 실패 처리
connectiontimeout: 5000
# 소켓 읽기 제한 시간 (단위: 밀리초)
# 메일 전송 요청 후 서버로부터 응답(Response)을 기다리는 최대 시간
timeout: 5000
# 소켓 쓰기 제한 시간 (단위: 밀리초)
# 데이터를 서버로 전송할 때 걸리는 최대 허용 시간
writetimeout: 5000
'Spring boot 입문 > Spring boot 입문 -1' 카테고리의 다른 글
| V15-2 이메일 인증 기능 추가 사전준비 및 프로그램 흐름 (0) | 2026.06.18 |
|---|---|
| V15-1 회원가입시 이메일 인증 처리 (0) | 2026.06.09 |
| V13-6 회원 정보 수정 기능 수정 (0) | 2026.06.09 |
| V13-5 env 파일에 카카오 회원가입 비번 설정 (0) | 2026.06.08 |
| V13-4 마이페이지 소셜 이미지 출력하기 위한 코드 수정 (0) | 2026.06.02 |
