Skip to content

Commit

Permalink
Merge pull request #4 from Groom-Team11/feat/#1
Browse files Browse the repository at this point in the history
[Feat] 카카오 로그인 구현
  • Loading branch information
HongSeonAh authored Sep 27, 2024
2 parents a6f0586 + 7960ba9 commit ca77685
Show file tree
Hide file tree
Showing 22 changed files with 728 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,4 @@ out/

### VS Code ###
.vscode/
.env
10 changes: 9 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,19 @@ dependencies {
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

implementation 'org.springframework.boot:spring-boot-starter'
implementation 'io.jsonwebtoken:jjwt:0.9.1'
implementation 'org.springframework.boot:spring-boot-starter-webflux'

implementation 'javax.xml.bind:jaxb-api:2.3.1'
implementation 'org.glassfish.jaxb:jaxb-runtime:2.3.1'

}

tasks.named('test') {
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/bloomgroom/BloomGroomApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
//

@PropertySource(value = { "classpath:database/application-database.yml" }, factory = YamlPropertySourceFactory.class)
//@PropertySource(value = { "classpath:oauth2/application-oauth2.yml" }, factory = YamlPropertySourceFactory.class)
@PropertySource(value = { "classpath:oauth2/application-oauth2.yml" }, factory = YamlPropertySourceFactory.class)
//@PropertySource(value = { "classpath:s3/application-s3.yml" }, factory = YamlPropertySourceFactory.class)
public class BloomGroomApplication {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package com.bloomgroom.domain.user.application;

import com.bloomgroom.domain.user.domain.RefreshToken;
import com.bloomgroom.domain.user.domain.User;
import com.bloomgroom.domain.user.domain.repository.RefreshTokenRepository;
import com.bloomgroom.domain.user.domain.repository.UserRepository;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;

import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Date;

@Service
@RequiredArgsConstructor
public class TokenService {
private final RefreshTokenRepository refreshTokenRepository;
private final UserRepository userRepository;

@Value("${jwt.secret-key}")
private String secretKey;

// AccessToken 생성
public String createAccessToken(User user) { //AccessToken을 JWT형식으로 생성함
return Jwts.builder()
.setSubject(user.getUserId().toString())
.setExpiration(new Date(System.currentTimeMillis() + 3600000)) //1시간
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}

// RefreshToken 생성
public String createRefreshToken(User user) {
return Jwts.builder()
.setSubject(user.getUserId().toString())
.setExpiration(new Date(System.currentTimeMillis() + 7 * 24 * 3600000)) //1주일
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}

// RefreshToken 저장
public void saveRefreshToken(User user, String refreshToken) {

RefreshToken token = new RefreshToken();

token.setRefreshToken(refreshToken);
token.setUser(user);
refreshTokenRepository.save(token);
}

//RefreshToken 이용하여 AccessToken 재발급
public String renewAccessToken(String refreshToken) {

RefreshToken token = refreshTokenRepository.findByRefreshToken(refreshToken).orElse(null);

if (token == null) {
return null;
}
User user = token.getUser();
return createAccessToken(user);
}

// RefreshToken 조회
public String getRefreshToken(User user) {
RefreshToken refreshToken = refreshTokenRepository.findByUser(user);

if (refreshToken != null) {
return refreshToken.getRefreshToken();
}
return null;
}

}
110 changes: 110 additions & 0 deletions src/main/java/com/bloomgroom/domain/user/application/UserService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package com.bloomgroom.domain.user.application;

import com.bloomgroom.domain.user.domain.User;
import com.bloomgroom.domain.user.domain.repository.RefreshTokenRepository;
import com.bloomgroom.domain.user.domain.repository.UserRepository;
import com.bloomgroom.domain.user.dto.response.KakaoTokenRes;
import com.nimbusds.jose.shaded.gson.JsonObject;
import com.nimbusds.jose.shaded.gson.JsonParser;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.*;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

import java.util.Optional;


@Service
@RequiredArgsConstructor
public class UserService {

private final UserRepository userRepository;
private final RefreshTokenRepository refreshTokenRepository;
private final RestTemplate restTemplate = new RestTemplate();



@Value("${spring.security.oauth2.client.registration.kakao.client-id}")
private String kakaoClientId;

@Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
private String redirectUri;

@Value("${spring.security.oauth2.client.provider.kakao.token-uri}")
private String kakaoTokenUrl;

@Value("${spring.security.oauth2.client.provider.kakao.user-info-uri}")
private String kakaoUserInfoUrl;

@Value("${spring.security.oauth2.client.registration.kakao.client-secret}")
private String kakaoClientSecret;

@Value("${kakao.admin-key}")
private String adminKey;

// code로 accessToken 받기
public String getKakaoAccessToken(String code) {

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "authorization_code");
params.add("client_id", kakaoClientId);
params.add("redirect_uri", redirectUri);
params.add("client_secret", kakaoClientSecret);
params.add("code", code);

HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);
ResponseEntity<KakaoTokenRes> response = restTemplate.exchange(
kakaoTokenUrl, HttpMethod.POST, request, KakaoTokenRes.class);

return response.getBody().getAccessToken();
}


// 카카오에서 사용자 정보 가져오기
public User getKakaoUser(String accessToken) {
HttpHeaders headers = new HttpHeaders();
headers.set("Authorization", "Bearer " + accessToken);

HttpEntity<Void> request = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(
kakaoUserInfoUrl, HttpMethod.GET, request, String.class);

// JSON 응답 본문에서 직접 정보 추출
String responseBody = response.getBody();
if (responseBody != null) {

JsonObject jsonObject = JsonParser.parseString(responseBody).getAsJsonObject();
JsonObject kakaoAccount = jsonObject.getAsJsonObject("kakao_account");
JsonObject properties = jsonObject.getAsJsonObject("properties");


String email = kakaoAccount.get("email").getAsString();

User user = new User();
user.setEmail(email);
return user;
}

throw new RuntimeException("유저의 정보가 없습니다.");
}


// 회원가입 (카카오에서 받은 정보를 DB에 저장)
public User signup(User user) {
return userRepository.save(user);
}

public User findByEmail(String email) {
return userRepository.findByEmail(email).orElse(null);
}

public Optional<User> findUserById(Long userId) {
return userRepository.findById(userId);
}
}
30 changes: 30 additions & 0 deletions src/main/java/com/bloomgroom/domain/user/domain/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.bloomgroom.domain.user.domain;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class RefreshToken {

//refreshToken을 가지고 accessToken을 발급해주는 거임

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


@Column(nullable = false, unique = true, length = 255)
private String refreshToken;

@OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
@JoinColumn(name = "user_id")
private User user;

}
22 changes: 22 additions & 0 deletions src/main/java/com/bloomgroom/domain/user/domain/User.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.bloomgroom.domain.user.domain;

import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "user_id")
private Long userId;

@Column(name = "email", nullable = false, unique = true, length = 255)
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.bloomgroom.domain.user.domain.repository;

import com.bloomgroom.domain.user.domain.RefreshToken;
import com.bloomgroom.domain.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;

@Repository
public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByRefreshToken(String refreshToken);
RefreshToken findByUser(User user);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bloomgroom.domain.user.domain.repository;

import com.bloomgroom.domain.user.domain.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByUserId(Long userId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.bloomgroom.domain.user.dto.request;

import lombok.Data;

@Data
public class KakaoTokenReq {
private String refreshToken;
}
12 changes: 12 additions & 0 deletions src/main/java/com/bloomgroom/domain/user/dto/request/TokenReq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.bloomgroom.domain.user.dto.request;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Data
public class TokenReq {

private String accessToken;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.bloomgroom.domain.user.dto.response;

import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.http.HttpStatus;

@Data
@NoArgsConstructor
public class BasicResponse <T>{ //API response 깃에 올라오면 수정봐야함

private String message;
private int statusCode;
private T data;
private HttpStatus status;

// 모든 필드를 포함하는 생성자 추가
public BasicResponse(String message, int statusCode, T data, HttpStatus status) {
this.message = message;
this.statusCode = statusCode;
this.data = data;
this.status = status;
}

public static <T> BasicResponse<T> ofSuccess(T data) {
return new BasicResponse<>("SUCCESS", HttpStatus.OK.value(), data, HttpStatus.OK);
}

public static <T> BasicResponse<T> ofFailure(String message, HttpStatus status) {
return new BasicResponse<>(message, status.value(), null, status);
}

public static <T> BasicResponse<T> ofSuccess(T data, String message) {
return new BasicResponse<>(message, HttpStatus.OK.value(), data, HttpStatus.OK);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.bloomgroom.domain.user.dto.response;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class KakaoLoginRes {

private String accessToken; // JWT 액세스 토큰
private String refreshToken; // 리프레시 토큰
}
Loading

0 comments on commit ca77685

Please sign in to comment.