Skip to content
This repository has been archived by the owner on Oct 20, 2024. It is now read-only.

Commit

Permalink
Merge pull request #8 from FinFellows/feat/#1_kakaoLogin
Browse files Browse the repository at this point in the history
Feat/#1 kakao login
  • Loading branch information
LEEJaeHyeok97 authored Dec 30, 2023
2 parents 04706e5 + ab67616 commit e13e4d8
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 34 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ repositories {
}

dependencies {
// implementation 'net.bytebuddy:byte-buddy:1.11.22'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
implementation 'org.springframework.boot:spring-boot-starter-security'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,53 @@

@Slf4j
@Service
public class CustomTokenProvierService {
public class CustomTokenProviderService {

@Autowired
private OAuth2Config oAuth2Config;

@Autowired
private final CustomUserDetailsService customUserDetailsService;

public CustomTokenProvierService(CustomUserDetailsService customUserDetailsService) {
public CustomTokenProviderService(CustomUserDetailsService customUserDetailsService) {
this.customUserDetailsService = customUserDetailsService;
}


public TokenMapping refreshToken(Authentication authentication, String refreshToken) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Date now = new Date();

Date accessTokenExpiresIn = new Date(now.getTime() + oAuth2Config.getAuth().getAccessTokenExpirationMsec());

String secretKey = oAuth2Config.getAuth().getTokenSecret();
byte[] keyBytes = Decoders.BASE64.decode(secretKey);
Key key = Keys.hmacShaKeyFor(keyBytes);

String accessToken = Jwts.builder()
.setSubject(Long.toString(userPrincipal.getId()))
.setIssuedAt(new Date())
.setExpiration(accessTokenExpiresIn)
.signWith(key, SignatureAlgorithm.HS512)
.compact();

return TokenMapping.builder()
.email(userPrincipal.getEmail())
.accessToken(accessToken)
.refreshToken(refreshToken)
.build();
}

public Long getExpiration(String token) {
// accessToken 남은 유효시간
Date expiration = Jwts.parserBuilder().setSigningKey(oAuth2Config.getAuth().getTokenSecret()).build().parseClaimsJws(token).getBody().getExpiration();
// 현재 시간
Long now = new Date().getTime();
//시간 계산
return (expiration.getTime() - now);
}


//토큰 만들기
public TokenMapping createToken(Authentication authentication) {
UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
Expand Down Expand Up @@ -96,6 +130,12 @@ public UsernamePasswordAuthenticationToken getAuthenticationById(String token) {
return authentication;
}

public UsernamePasswordAuthenticationToken getAuthenticationByEmail(String email) {
UserDetails userDetails = customUserDetailsService.loadUserByUsername(email);
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
return authentication;
}

public Long getUserIdFromToken(String token) {
Claims claims = Jwts.parserBuilder()
.setSigningKey(oAuth2Config.getAuth().getTokenSecret())
Expand All @@ -105,4 +145,6 @@ public Long getUserIdFromToken(String token) {

return Long.parseLong(claims.getSubject());
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.finfellows.domain.user.domain.repository.UserRepository;
import com.finfellows.global.config.security.token.UserPrincipal;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
Expand All @@ -16,6 +17,7 @@
@RequiredArgsConstructor
@Service
@Transactional(readOnly = true)
@Slf4j
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;

Expand All @@ -30,13 +32,13 @@ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundExcep
}

public UserDetails loadUserById(Long id) {
// Optional<User> user = userRepository.findById(id);
Optional<User> user = userRepository.findByProviderId(Long.toString(id));
if (user.isPresent()) {
return UserPrincipal.createUser(user.get());
}

throw new UsernameNotFoundException("유효하지 않는 유저입니다.");
log.debug("Attempting to load user by ID: {}", id);
return userRepository.findById(id)
.map(UserPrincipal::createUser)
.orElseThrow(() -> {
log.error("User with ID: {} could not be found.", id);
return new UsernameNotFoundException("유효하지 않는 유저입니다.");
});
}


Expand Down
119 changes: 115 additions & 4 deletions src/main/java/com/finfellows/domain/auth/application/KakaoService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
import com.finfellows.domain.auth.domain.Token;
import com.finfellows.domain.auth.domain.repository.TokenRepository;
import com.finfellows.domain.auth.dto.*;
import com.finfellows.domain.user.domain.Role;
import com.finfellows.domain.user.domain.User;
import com.finfellows.domain.user.domain.repository.UserRepository;
import com.finfellows.global.DefaultAssert;
import com.finfellows.global.config.security.OAuth2Config;
import com.finfellows.global.config.security.token.UserPrincipal;
import com.finfellows.global.error.DefaultAuthenticationException;
import com.finfellows.global.payload.ErrorCode;
import com.finfellows.global.payload.Message;
Expand All @@ -24,7 +27,6 @@
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
Expand Down Expand Up @@ -53,7 +55,7 @@ public class KakaoService {

private final UserRepository userRepository;
private final TokenRepository tokenRepository;
private final CustomTokenProvierService customTokenProvierService;
private final CustomTokenProviderService customTokenProviderService;


@Value("${spring.security.oauth2.client.provider.kakao.authorization-uri}")
Expand Down Expand Up @@ -164,16 +166,18 @@ public KakaoProfile getKakaoProfile(String accessToken) {
}


@Transactional
public AuthRes kakaoLogin(KakaoProfile kakaoProfile) {

// 이미 DB에 회원 정보가 저장되어 있으면 로그인 시키고, 없다면 DB에 등록 후 로그인.

Optional<User> byEmail = userRepository.findByEmail(kakaoProfile.getKakaoAccount().getEmail());
if (!byEmail.isPresent()) {
User user = User.builder()
.providerId(Long.toString(kakaoProfile.getId()))
.providerId(kakaoProfile.getId())
.email(kakaoProfile.getKakaoAccount().getEmail())
.name(kakaoProfile.getKakaoAccount().getProfile().getNickname())
.role(Role.USER)
.build();

User saveUser = userRepository.save(user);
Expand All @@ -191,7 +195,7 @@ public AuthRes kakaoLogin(KakaoProfile kakaoProfile) {



TokenMapping tokenMapping = customTokenProvierService.createToken(authentication);
TokenMapping tokenMapping = customTokenProviderService.createToken(authentication);


Token token = Token.builder()
Expand Down Expand Up @@ -220,4 +224,111 @@ public Message signOut(final RefreshTokenReq tokenRefreshRequest) {
.message("로그아웃 하였습니다.")
.build();
}


@Transactional
public Message deleteAccount(UserPrincipal userPrincipal) {
Optional<User> user = userRepository.findById(userPrincipal.getId());
DefaultAssert.isTrue(user.isPresent(), "유저가 올바르지 않습니다.");

Optional<Token> token = tokenRepository.findByEmail(userPrincipal.getEmail());
DefaultAssert.isTrue(token.isPresent(), "토큰이 유효하지 않습니다.");

userRepository.delete(user.get());
tokenRepository.delete(token.get());


return Message.builder()
.message("회원 탈퇴 하였습니다.")
.build();
}

@Transactional
public AuthRes adminSignIn(KakaoProfile kakaoProfile) {
Optional<User> byEmail = userRepository.findByEmail(kakaoProfile.getKakaoAccount().getEmail());
if (!byEmail.isPresent()) {
User user = User.builder()
.providerId(kakaoProfile.getId())
.email(kakaoProfile.getKakaoAccount().getEmail())
.name(kakaoProfile.getKakaoAccount().getProfile().getNickname())
.role(Role.ADMIN)
.build();

User saveUser = userRepository.save(user);


}

Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
kakaoProfile.getKakaoAccount().getEmail(),
kakaoProfile.getId() //providerId랑 같다.
)
);



TokenMapping tokenMapping = customTokenProviderService.createToken(authentication);


Token token = Token.builder()
.refreshToken(tokenMapping.getRefreshToken())
.email(tokenMapping.getEmail())
.build();

tokenRepository.save(token);

Token savedToken = tokenRepository.save(token);


return AuthRes.builder()
.accessToken(tokenMapping.getAccessToken())
.refreshToken(token.getRefreshToken())
.build();

}

public ResponseEntity<?> refresh(RefreshTokenReq refreshTokenReq) {
//1차 검증
boolean checkValid = valid(refreshTokenReq.getRefreshToken());
DefaultAssert.isAuthentication(checkValid);

Optional<Token> token = tokenRepository.findByRefreshToken(refreshTokenReq.getRefreshToken());
Authentication authentication = customTokenProviderService.getAuthenticationByEmail(token.get().getEmail());

//4. refresh token 정보 값을 업데이트 한다.
//시간 유효성 확인
TokenMapping tokenMapping;

Long expirationTime = customTokenProviderService.getExpiration(refreshTokenReq.getRefreshToken());
if(expirationTime > 0){
tokenMapping = customTokenProviderService.refreshToken(authentication, token.get().getRefreshToken());
}else{
tokenMapping = customTokenProviderService.createToken(authentication);
}

Token updateToken = token.get().updateRefreshToken(tokenMapping.getRefreshToken());
tokenRepository.save(updateToken);

AuthRes authResponse = AuthRes.builder().accessToken(tokenMapping.getAccessToken()).refreshToken(updateToken.getRefreshToken()).build();

return ResponseEntity.ok(authResponse);
}

private boolean valid(String refreshToken) {

// 1. 토큰 형식 물리적 검증
boolean validateCheck = customTokenProviderService.validateToken(refreshToken);
DefaultAssert.isTrue(validateCheck, "Token 검증에 실패하였습니다.");

// 2. refresh token 값을 불러온다.
Optional<Token> token = tokenRepository.findByRefreshToken(refreshToken);
DefaultAssert.isTrue(token.isPresent(), "탈퇴 처리된 회원입니다.");

// 3. email 값을 통해 인증값을 불러온다.
Authentication authentication = customTokenProviderService.getAuthenticationByEmail(token.get().getEmail());
DefaultAssert.isTrue(token.get().getEmail().equals(authentication.getName()), "사용자 인증에 실패했습니다.");

return true;
}
}
9 changes: 7 additions & 2 deletions src/main/java/com/finfellows/domain/auth/domain/Token.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@
@Getter
@Table(name = "token")
@NoArgsConstructor // 생성자 직접 생성 대신 써줬는데 문제가 된다면 삭제하고 token 생성자 직접 만들어 수정해야할듯
@AllArgsConstructor
@Builder
@Entity
public class Token extends BaseEntity {

Expand All @@ -25,9 +23,16 @@ public class Token extends BaseEntity {
@Column(name = "refresh_token", nullable = false)
private String refreshToken;


public Token updateRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
return this;
}


@Builder
public Token(String email, String refreshToken) {
this.email = email;
this.refreshToken = refreshToken;
}
}
Loading

0 comments on commit e13e4d8

Please sign in to comment.