Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 카카오 로그인 초기 세팅 #6

Merged
merged 13 commits into from
Jan 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'


implementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-ui', version: '2.3.0'
testImplementation group: 'org.springdoc', name: 'springdoc-openapi-starter-webmvc-api', version: '2.3.0'
Expand All @@ -31,6 +33,7 @@ dependencies {
annotationProcessor "org.springframework.boot:spring-boot-configuration-processor"

runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.mariadb.jdbc:mariadb-java-client:2.6.2'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand Down
Original file line number Diff line number Diff line change
@@ -1,74 +1,74 @@
package com.ttubeog.domain.auth.application;

import com.ttubeog.domain.member.domain.Provider;
import com.ttubeog.domain.member.domain.Role;
import com.ttubeog.domain.member.domain.Member;
import com.ttubeog.domain.member.domain.repository.MemberRepository;
import com.ttubeog.global.DefaultAssert;
import com.ttubeog.global.config.security.auth.OAuth2UserInfo;
import com.ttubeog.global.config.security.auth.OAuth2UserInfoFactory;
import com.ttubeog.global.config.security.token.UserPrincipal;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import java.util.Optional;

@RequiredArgsConstructor
@Service
public class CustomDefaultOAuth2UserService extends DefaultOAuth2UserService{

private final MemberRepository memberRepository;

@Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest);
try {
return processOAuth2User(oAuth2UserRequest, oAuth2User);
} catch (Exception e) {
DefaultAssert.isAuthentication(e.getMessage());
}
return null;
}

private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(oAuth2UserRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes());
DefaultAssert.isAuthentication(!oAuth2UserInfo.getEmail().isEmpty());

Optional<Member> userOptional = memberRepository.findByEmail(oAuth2UserInfo.getEmail());
Member member;
if(userOptional.isPresent()) {
member = userOptional.get();
DefaultAssert.isAuthentication(member.getProvider().equals(Provider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId())));
member = updateExistingUser(member, oAuth2UserInfo);
} else {
member = registerNewUser(oAuth2UserRequest, oAuth2UserInfo);
}

return UserPrincipal.create(member, oAuth2User.getAttributes());
}

private Member registerNewUser(OAuth2UserRequest oAuth2UserRequest, OAuth2UserInfo oAuth2UserInfo) {
Member member = Member.builder()
.provider(Provider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId()))
.providerId(oAuth2UserInfo.getId())
.name(oAuth2UserInfo.getName())
.email(oAuth2UserInfo.getEmail())
.imageUrl(oAuth2UserInfo.getImageUrl())
.role(Role.USER)
.build();
return memberRepository.save(member);
}

private Member updateExistingUser(Member member, OAuth2UserInfo oAuth2UserInfo) {

member.updateName(oAuth2UserInfo.getName());
member.updateImageUrl(oAuth2UserInfo.getImageUrl());

return memberRepository.save(member);
}
}
//package com.ttubeog.domain.auth.application;
//
//import com.ttubeog.domain.member.domain.Provider;
//import com.ttubeog.domain.member.domain.Role;
//import com.ttubeog.domain.member.domain.Member;
//import com.ttubeog.domain.member.domain.repository.MemberRepository;
//import com.ttubeog.global.DefaultAssert;
//import com.ttubeog.global.config.security.auth.OAuth2UserInfo;
//import com.ttubeog.global.config.security.token.UserPrincipal;
//import lombok.RequiredArgsConstructor;
//import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
//import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
//import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
//import org.springframework.security.oauth2.core.user.OAuth2User;
//import org.springframework.stereotype.Service;
//
//import java.util.Optional;
//
//@RequiredArgsConstructor
//@Service
//public class CustomDefaultOAuth2UserService extends DefaultOAuth2UserService {
//
// private final MemberRepository memberRepository;
//
// @Override
// public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
// OAuth2User oAuth2User = super.loadUser(oAuth2UserRequest);
// try {
// return processOAuth2User(oAuth2UserRequest, oAuth2User);
// } catch (Exception e) {
// DefaultAssert.isAuthentication(e.getMessage());
// }
// return null;
// }
//
// private OAuth2User processOAuth2User(OAuth2UserRequest oAuth2UserRequest, OAuth2User oAuth2User) {
// OAuth2UserInfo oAuth2UserInfo = OAuth2UserInfoFactory.getOAuth2UserInfo(oAuth2UserRequest.getClientRegistration().getRegistrationId(), oAuth2User.getAttributes());
// DefaultAssert.isAuthentication(!oAuth2UserInfo.getEmail().isEmpty());
//
// Optional<Member> userOptional = memberRepository.findByEmail(oAuth2UserInfo.getEmail());
// Member member;
// if (userOptional.isPresent()) {
// member = userOptional.get();
// DefaultAssert.isAuthentication(member.getProvider().equals(Provider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId())));
// member = updateExistingMember(member, oAuth2UserInfo);
// } else {
// member = registerNewMember(oAuth2UserRequest, oAuth2UserInfo);
// }
//
// return UserPrincipal.create(member, oAuth2User.getAttributes());
// }
//
// private Member registerNewMember(OAuth2UserRequest oAuth2UserRequest, OAuth2UserInfo oAuth2UserInfo) {
// Member member = Member.builder()
// .provider(Provider.valueOf(oAuth2UserRequest.getClientRegistration().getRegistrationId()))
// .providerId(oAuth2UserInfo.getId())
// .name(oAuth2UserInfo.getName())
// .email(oAuth2UserInfo.getEmail())
// .imageUrl(oAuth2UserInfo.getImageUrl())
// .refreshToken(oAuth2UserInfo.getRefreshToken())
// .role(Role.USER)
// .build();
//
// return memberRepository.save(member);
// }
//
// private Member updateExistingMember(Member member, OAuth2UserInfo oAuth2UserInfo) {
//
// member.updateName(oAuth2UserInfo.getName());
// member.updateImageUrl(oAuth2UserInfo.getImageUrl());
//
// return memberRepository.save(member);
// }
//}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

@RequiredArgsConstructor
@Service
public class CustomUserDetailsService implements UserDetailsService{
public class CustomMemberDetailsService implements UserDetailsService{

private final MemberRepository memberRepository;

Expand All @@ -24,7 +24,7 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep

Member member = memberRepository.findByEmail(email)
.orElseThrow(() ->
new UsernameNotFoundException("유저 정보를 찾을 수 없습니다.")
new UsernameNotFoundException("멤버 정보를 찾을 수 없습니다.")
);

return UserPrincipal.create(member);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class CustomTokenProviderService {
private OAuth2Config oAuth2Config;

@Autowired
private CustomUserDetailsService customUserDetailsService;
private CustomMemberDetailsService customMemberDetailsService;

public TokenMapping refreshToken(Authentication authentication, String refreshToken) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Expand Down Expand Up @@ -94,13 +94,13 @@ public Long getUserIdFromToken(String token) {

public UsernamePasswordAuthenticationToken getAuthenticationById(String token){
Long userId = getUserIdFromToken(token);
UserDetails userDetails = customUserDetailsService.loadUserById(userId);
UserDetails userDetails = customMemberDetailsService.loadUserById(userId);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
return authentication;
}

public UsernamePasswordAuthenticationToken getAuthenticationByEmail(String email){
UserDetails userDetails = customUserDetailsService.loadUserByUsername(email);
UserDetails userDetails = customMemberDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
return authentication;
}
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/com/ttubeog/domain/auth/application/JwtTokenService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.ttubeog.domain.auth.application;

import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.io.Encoders;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.util.Date;
import java.util.Random;

@Service
public class JwtTokenService implements InitializingBean {

private final String secretKey;
private final long accessTokenExpirationInSeconds;
private final long refreshTokenExpirationInSeconds;
private static Key key;


public JwtTokenService(
@Value("${jwt.secret-key}") String secretKey,
@Value("${jwt.access-expired-time}") long accessTokenExpirationInSeconds,
@Value("${jwt.refresh-expired-time}") long refreshTokenExpirationInSeconds
) {
this.secretKey = secretKey;
this.accessTokenExpirationInSeconds = accessTokenExpirationInSeconds;
this.refreshTokenExpirationInSeconds = refreshTokenExpirationInSeconds;
}

@Override
public void afterPropertiesSet() throws Exception {
key = getKeyFromBase64EncodedKey(encodeBase64SecretKey(secretKey));
}

// JWT 토큰 생성
public String createToken(String payload, long expireLength) {
// 토큰에 포함될 정보
Claims claims = Jwts.claims().setSubject(payload);
// 현재 날짜와 시간
Date now = new Date();
// 유효기간
Date validity = new Date(now.getTime() + expireLength);

return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(validity)
.signWith(key, SignatureAlgorithm.HS256) // 토큰 서명
.compact();
}

// 액세스 토큰 생성
public String createAccessToken(String payload) {
return createToken(payload, accessTokenExpirationInSeconds);
}

// 리프레시 토큰 생성
public String createRefreshToken() {
byte[] array = new byte[7];
new Random().nextBytes(array);
String newPayload = new String(array, StandardCharsets.UTF_8);

return createToken(newPayload, refreshTokenExpirationInSeconds);
}

public String getPayload(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
} catch (ExpiredJwtException e) {
return e.getClaims().getSubject();
}
}

public boolean validateToken(String token) {
try {
Jws<Claims> claimsJws = Jwts.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token);
return !claimsJws.getBody().getExpiration().before(new Date());
} catch (JwtException | IllegalArgumentException exception) {
return false;
}
}


private String encodeBase64SecretKey(String secretKey) {
return Encoders.BASE64.encode(secretKey.getBytes(StandardCharsets.UTF_8));
}

private Key getKeyFromBase64EncodedKey(String encodedSecretKey) {
byte[] keyBytes = Decoders.BASE64.decode(encodedSecretKey);

Key key = Keys.hmacShaKeyFor(keyBytes);

return key;
}

// 클라이언트 쿠키에 리프레시 토큰을 저장
public void addRefreshTokenToCookie(String refreshToken, HttpServletResponse response) {
Long age = refreshTokenExpirationInSeconds;
Cookie cookie = new Cookie("refresh_token", refreshToken);
cookie.setPath("/");
cookie.setMaxAge(age.intValue());
cookie.setHttpOnly(true);
response.addCookie(cookie);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.ttubeog.domain.auth.application;


import com.ttubeog.domain.auth.dto.KakaoInfoDto;
import com.ttubeog.domain.member.application.MemberService;
import com.ttubeog.domain.member.dto.MemberDto;
import lombok.RequiredArgsConstructor;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;

import java.util.Map;

@RequiredArgsConstructor
@Service
public class KakaoOauthService {
private final MemberService memberService;

// 카카오 API를 호출해서 AccessToken으로 멤버 정보를 가져오는 로직
public Map<String, Object> getMemberInfoByToken(String accessToken) {
return WebClient.create()
.get()
.uri("https://kapi.kakao.com/v2/user/me")
.headers(httpHeaders -> httpHeaders.setBasicAuth(accessToken))
.retrieve()
.bodyToMono(new ParameterizedTypeReference<Map<String, Object>>() {
})
.block();
}


// 카카오 API에서 가져온 멤버 정보를 DB 저장, 업데이트
public MemberDto getMemberProfileByToken(String accessToken) {
Map<String, Object> memberInfoByToken = getMemberInfoByToken(accessToken);
KakaoInfoDto kakaoInfoDto = new KakaoInfoDto(memberInfoByToken);
MemberDto memberDto = MemberDto.builder()
.id(kakaoInfoDto.getId())
.email(kakaoInfoDto.getEmail())
.platform("kakao")
.build();

if(memberService.findById(memberDto.getId()) != null) {
memberService.update(memberDto);
} else {
memberService.save(memberDto);
}
return memberDto;
}
}
Loading
Loading