Skip to content

Commit

Permalink
Feat : 네이버 소셜 로그인 로직 추가 및 관련 변경 사항 반영
Browse files Browse the repository at this point in the history
- 네이버 소셜 로그인 로직 구현
- CI_MVP.yml에 네이터 소셜 로그인 키 추가
- 멤버 엔티티에 소셜로그인 타입 (SocialType) 추가
- 멤버 엔티티에 소셜아이디 타입을 Long 에서 String 으로 변경
- 소셜아이디 타입 변경에 따른 JWT 로직 일부 수정
  • Loading branch information
hyeonda02 committed Dec 20, 2024
1 parent 2b2c8fa commit f4ad899
Show file tree
Hide file tree
Showing 18 changed files with 337 additions and 46 deletions.
14 changes: 14 additions & 0 deletions .github/workflows/CI_MVP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ jobs:
echo "spring.security.oauth2.client.provider.kakao.token-uri=${{ secrets.KAKAO_TOKEN_URI }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.provider.kakao.user-info-uri=${{ secrets.KAKAO_USER_INFO_URI }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.provider.kakao.user-name-attribute=${{ secrets.KAKAO_USER_NAME_ATTRIBUTE }}" >> ./src/main/resources/application.properties
# Naver OAuth2 Configuration
echo "spring.security.oauth2.client.registration.naver.client-name=${{ secrets.NAVER_CLIENT_NAME }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.registration.naver.client-id=${{ secrets.NAVER_CLIENT_ID }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.registration.naver.client-secret=${{ secrets.NAVER_CLIENT_SECRET }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.registration.naver.redirect-uri=${{ secrets.NAVER_REDIRECT_URI }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.registration.naver.authorization-grant-type=${{ secrets.NAVER_AUTH_GRANT_TYPE }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.registration.naver.scope=${{ secrets.NAVER_SCOPE }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.provider.naver.authorization-uri=${{ secrets.NAVER_AUTHORIZATION_URI }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.provider.naver.token-uri=${{ secrets.NAVER_TOKEN_URI }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.provider.naver.user-info-uri=${{ secrets.NAVER_USER_INFO_URI }}" >> ./src/main/resources/application.properties
echo "spring.security.oauth2.client.provider.naver.user-name-attribute=${{ secrets.NAVER_USER_NAME_ATTRIBUTE }}" >> ./src/main/resources/application.properties
- name: Build with Gradle Wrapper
run: ./gradlew build

Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package umc.kkijuk.server.auth.controller;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import umc.kkijuk.server.auth.service.NaverAuthService;
import umc.kkijuk.server.member.domain.Member;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequiredArgsConstructor
public class NaverAuthController {
private final NaverAuthService naverAuthService;

@GetMapping("/auth/naver/login")
public ResponseEntity<Object> naverCallback(@RequestParam("code") String code,
@RequestParam("state") String state){
try{
String naverAccessToken = naverAuthService.getNaverAccessToken(code, state);
Member member = naverAuthService.processNaverUser(naverAccessToken);
Map<String, Object> tokens = new HashMap<>();
tokens.put("Token", naverAuthService.generateTokens(member));
log.info("네이버 로그인 성공: 사용자 이름={}, 네이버 ID={}", member.getName(), member.getSocialId());
return ResponseEntity.ok(tokens);

}catch (Exception e){
log.error("네이버 인증 처리 중 오류 발생 : {}",e.getMessage(),e);
return ResponseEntity.internalServerError().body(Map.of("error", "네이버 인증 처리 실패"));
}
}
}
24 changes: 24 additions & 0 deletions src/main/java/umc/kkijuk/server/auth/dto/NaverTokenResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package umc.kkijuk.server.auth.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class NaverTokenResponse {
@JsonProperty("access_token")
private String accessToken;
@JsonProperty("refresh_token")
private String refreshToken;
@JsonProperty("token_type")
private String tokenType;
@JsonProperty("expires_in")
private String expiresIn;
@JsonProperty("error")
private String error;
@JsonProperty("error_description")
private String errorDescription;
}
31 changes: 31 additions & 0 deletions src/main/java/umc/kkijuk/server/auth/dto/NaverUserResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package umc.kkijuk.server.auth.dto;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class NaverUserResponse {
@JsonProperty("resultcode")
private String resultCode;
@JsonProperty("message")
private String message;
@JsonProperty("response")
private NaverUserDetail naverUserDetail;
@Getter
@NoArgsConstructor
@AllArgsConstructor
public static class NaverUserDetail {
private String id;
private String name;
private String email;
private String birthday;
private String birthyear;
private String mobile;

}

}
14 changes: 7 additions & 7 deletions src/main/java/umc/kkijuk/server/auth/jwt/JwtFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,24 @@ protected void doFilterInternal(

String requestUri = request.getRequestURI();

// 카카오 로그인 경로 제외
if (requestUri.startsWith("/auth/kakao/login")) {
// 카카오 로그인 경로 제외, 네이버 로그인 경로 제외
if (requestUri.startsWith("/auth/kakao/login")||requestUri.startsWith("/auth/naver/login")) {
chain.doFilter(request, response);
return;
}

try {
final String authorizationHeader = request.getHeader("Authorization");
Long kakaoId = null;
String socialId = null;
String jwt = null;

if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
kakaoId = jwtUtil.extractSocialId(jwt);
socialId = jwtUtil.extractSocialId(jwt);
}

if (kakaoId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
Member member = memberRepository.findBySocialId(kakaoId).orElse(null);
if (socialId != null && SecurityContextHolder.getContext().getAuthentication() == null) {
Member member = memberRepository.findBySocialId(socialId).orElse(null);

if (member != null && jwtUtil.validateToken(jwt, String.valueOf(member.getSocialId()))) {
UserDetails userDetails =
Expand All @@ -69,7 +69,7 @@ protected void doFilterInternal(

SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
log.warn("Invalid JWT Token for member: {}", kakaoId);
log.warn("Invalid JWT Token for member: {}", socialId);
}
}
} catch (Exception e) {
Expand Down
7 changes: 3 additions & 4 deletions src/main/java/umc/kkijuk/server/auth/jwt/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -72,14 +72,13 @@ public boolean validateToken(String token, String socialId) {
}
}

public Long extractSocialId(String token) {
return Long.valueOf(
Jwts.parserBuilder()
public String extractSocialId(String token) {
return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody()
.getId());
.getId();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ public Map<String, Object> getKakaoUserInfo(String accessToken) {

public Member processKakaoUser(String accessToken) {
Map<String, Object> kakaoUserInfo = getKakaoUserInfo(accessToken);
Long kakaoId = Long.valueOf(kakaoUserInfo.get("id").toString());
String kakaoId = kakaoUserInfo.get("id").toString();
String email = extractEmail(kakaoUserInfo);
String name = extractName(kakaoUserInfo);
String phoneNumber = extractPhoneNumber(kakaoUserInfo);
LocalDate birthDate = extractBirthDate(kakaoUserInfo);

log.info("카카오 사용자 정보 추출 - 이메일: {}, 이름: {}, 카카오 ID: {}, 전화번호: {}, 생년월일: {}", email, name, kakaoId, phoneNumber, birthDate);

return memberRepository.findBySocialId(kakaoId)
return memberRepository.findBySocialId(String.valueOf(kakaoId))
.orElseGet(() -> {
log.info("신규 사용자 생성 - 카카오 ID: {}", kakaoId);
return memberService.createUserWithKakaoId(kakaoId, kakaoUserInfo);
Expand Down
126 changes: 126 additions & 0 deletions src/main/java/umc/kkijuk/server/auth/service/NaverAuthService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package umc.kkijuk.server.auth.service;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import umc.kkijuk.server.auth.dto.NaverTokenResponse;
import umc.kkijuk.server.auth.dto.NaverUserResponse;
import umc.kkijuk.server.auth.jwt.JwtUtil;
import umc.kkijuk.server.member.domain.Member;
import umc.kkijuk.server.member.repository.MemberRepository;
import umc.kkijuk.server.member.service.MemberService;

import java.time.LocalDate;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@Service
@RequiredArgsConstructor
public class NaverAuthService {
private final MemberRepository memberRepository;
private final JwtUtil jwtUtil;
private final MemberService memberService;
private final RestTemplate restTemplate;

@Value("${spring.security.oauth2.client.registration.naver.client-id}")
private String clientId;
@Value("${spring.security.oauth2.client.registration.naver.client-secret}")
private String clientSecret;
@Value("${spring.security.oauth2.client.provider.naver.token-uri}")
private String tokenUri;
@Value("${spring.security.oauth2.client.provider.naver.user-info-uri}")
private String userInfoUri;
@Value("${spring.security.oauth2.client.registration.naver.authorization-grant-type}")
private String grantType;

public String getNaverAccessToken(String code, String state){
String url = UriComponentsBuilder.fromHttpUrl(tokenUri)
.queryParam("grant_type",grantType)
.queryParam("client_id", clientId)
.queryParam("client_secret", clientSecret)
.queryParam("code", code)
.queryParam("state", state)
.build().toUriString();


ResponseEntity<NaverTokenResponse> response =
restTemplate.exchange(url, HttpMethod.GET, null, NaverTokenResponse.class);

return response.getBody().getAccessToken();

}

public NaverUserResponse.NaverUserDetail getNaverUserInfo(String naverAccessToken) {
HttpHeaders headers = new HttpHeaders();
headers.setBearerAuth(naverAccessToken);

HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(headers);

ResponseEntity<NaverUserResponse> response =
restTemplate.exchange(userInfoUri, HttpMethod.GET, request, NaverUserResponse.class);

return response.getBody().getNaverUserDetail();

}
public Member processNaverUser(String naverAccessToken) {
NaverUserResponse.NaverUserDetail naverUserInfo = getNaverUserInfo(naverAccessToken);
String naverId = naverUserInfo.getId ();
String email = naverUserInfo.getEmail();
String name = naverUserInfo.getName();
String phoneNumber = naverUserInfo.getMobile();
LocalDate birthDate = extractBirthDate(naverUserInfo);

log.info("네이버 사용자 정보 추출 - 이메일: {}, 이름: {}, 카카오 ID: {}, 전화번호: {}, 생년월일: {}", email, name, naverId, phoneNumber, birthDate);

return memberRepository.findBySocialId(naverId)
.orElseGet(() -> {
log.info("신규 사용자 생성 - 네이버 ID: {}", naverId);
return memberService.createUserWithNaverId(naverId,naverUserInfo);
});
}

public Object generateTokens(Member member) {
String naverId = String.valueOf(member.getSocialId());
String accessToken = jwtUtil.createAccessToken(naverId);
String refreshToken = jwtUtil.createRefreshToken(naverId);

log.info("JWT Token 생성 완료 - 네이버 ID : {}, accessToken : {}, refreshToken : {}", naverId, accessToken, refreshToken);

Map<String, String> tokens = new HashMap<>();
tokens.put("accessToken", accessToken);
tokens.put("refreshToken", refreshToken);

return tokens;
}

public LocalDate extractBirthDate(NaverUserResponse.NaverUserDetail naverUserInfo) {
String birthday = (String) naverUserInfo.getBirthday();
String birthyear = (String) naverUserInfo.getBirthyear();
log.info("Naver 생년월일 정보: {}, {}", birthday,birthyear );


if (birthday == null || birthday.isEmpty()) {
return null;
}

int year = (birthyear != null && !birthyear.isEmpty())
? Integer.parseInt(birthyear)
: LocalDate.now().getYear();

String[] dateParts = birthday.split("-");
int month = Integer.parseInt(dateParts[0]);
int day = Integer.parseInt(dateParts[1]);


return LocalDate.of(year, month, day);
}
}
2 changes: 1 addition & 1 deletion src/main/java/umc/kkijuk/server/common/LoginUser.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public Long extractMemberId(String bearerToken) {
}

String token = bearerToken.substring(7);
Long socialId = jwtUtil.extractSocialId(token);
String socialId = jwtUtil.extractSocialId(token);

if (socialId == null) {
throw new IllegalArgumentException("유효하지 않은 토큰입니다.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public ResponseEntity<MemberEmailResponse> getEmail() {
public ResponseEntity<AuthResponse> refreshToken(@RequestBody RefreshTokenRequest request) {

String refreshToken = request.getRefreshToken();
Long kakaoId = jwtUtil.extractSocialId(refreshToken);
String kakaoId = jwtUtil.extractSocialId(refreshToken);

AuthResponse response = memberService.refreshAuthToken(refreshToken, kakaoId);

Expand All @@ -157,7 +157,7 @@ public ResponseEntity<AuthResponse> refreshToken(@RequestBody RefreshTokenReques
@GetMapping("/myPage/info")
public ResponseEntity<MemberInfoResponse> getInfo(@RequestHeader("Authorization") String bearerToken) {
Long loginUser = LoginUser.get().extractMemberId(bearerToken);
MemberInfoResponse memberInfoResponse = memberService.getMemberInfo(loginUser);
MemberInfoResponse memberInfoResponse = memberService.getMemberInfo(String.valueOf(loginUser));
return ResponseEntity
.status(HttpStatus.OK)
.body(memberInfoResponse);
Expand Down Expand Up @@ -196,7 +196,7 @@ public ResponseEntity<Boolean> postField(@RequestBody MemberFieldDto memberField
@Operation(summary = "로그아웃", description = "사용자 로그아웃")
@PostMapping("/logout")
public ResponseEntity<String> logout(@RequestHeader("Authorization") String token) {
Long kakaoId = jwtUtil.extractSocialId(token.substring(7));
String kakaoId = jwtUtil.extractSocialId(token.substring(7));
memberService.invalidateRefreshToken(kakaoId);
return ResponseEntity.ok("로그아웃 완료");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
@Data
@Builder
public class MemberInfoResponse {
private Long kakaoId;
private String socialId;
private String email;
private String name;
private String phoneNumber;
Expand Down
8 changes: 6 additions & 2 deletions src/main/java/umc/kkijuk/server/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ public class Member extends BaseEntity {
@Column(name = "member_id")
private Long id;

private Long socialId;
private String socialId;

@Enumerated(EnumType.STRING)
private SocialType socialType;

@NotNull
private String email;
Expand Down Expand Up @@ -98,7 +101,7 @@ public void deleteRecruitTag(String tag) {
this.recruitTags.remove(tag);
}

public void setSocialId(Long kakaoId) {
public void setSocialId(String kakaoId) {
this.socialId = kakaoId;
}

Expand All @@ -115,5 +118,6 @@ public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public void setRefreshToken(String refreshToken){ this.refreshToken = refreshToken;}
public void setSocialType(SocialType type){this.socialType = type;}

}
Loading

0 comments on commit f4ad899

Please sign in to comment.