Skip to content

Commit 09e175e

Browse files
chanrhanchanrhan
andauthored
feat: 카카오톡 로그인 구현 (#56)
* build: spring-security, jwt, webclient 의존성 추가 * feat: 카카오 로그인 API 및 비즈니스 로직 추가 * fix(KakaoAuthClient): 토큰 API 요청 시 중복 URL path 제거 * refactor(KakaoApiUrlConstant): 카카오 API Url 상수 클래스 추가 * fix: yml에서 key값 받아올 수 있도록 변경 * feat: 회원가입 로직 추가 * style: 메서드명 수정: getAccessToekn -> oAuthLogin * style: createAuthorizationRedirectUri 매개변수명 수정 * build: weblcient 의존성 추가 * fix: 카카오 로그인 오류 수정 * feat: JWT토큰 필터 추가 * feat: ArgumentResolver 추가 및 resovler 내에 사용자 인가 로직 추가 * feat: @auth 어노테이션으로 사용자 정보 불러오도록 수정 * feat: 사용자 정보 조회 API 추가 * refactor: 불필요한 코드 제거 * chore: yml 파일 수정 --------- Co-authored-by: chanrhan <km1104rs@naver.com>
1 parent bde9ef4 commit 09e175e

29 files changed

+550
-38
lines changed

build.gradle

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,21 @@ dependencies {
6262
implementation 'net.coobird:thumbnailator:0.4.20'
6363

6464
implementation 'software.amazon.awssdk:s3'
65+
66+
// WebClient
67+
implementation 'org.springframework.boot:spring-boot-starter-webflux'
68+
69+
// JWT
70+
implementation 'io.jsonwebtoken:jjwt-api:0.12.3'
71+
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.3'
72+
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.3'
73+
74+
// Netty MacOS
75+
implementation('io.netty:netty-resolver-dns-native-macos') {
76+
artifact {
77+
classifier = "osx-aarch_64" // Apple Silicon
78+
}
79+
}
6580
}
6681

6782
tasks.named('test') {
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package kr.kro.photoliner.common.dto.response;
2+
3+
public record JwtResponse(
4+
String accessToken
5+
) {
6+
7+
}

src/main/java/kr/kro/photoliner/domain/album/controller/AlbumController.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import kr.kro.photoliner.domain.album.dto.response.AlbumPhotoMarkersResponse;
1313
import kr.kro.photoliner.domain.album.dto.response.AlbumsResponse;
1414
import kr.kro.photoliner.domain.album.service.AlbumService;
15+
import kr.kro.photoliner.global.auth.Auth;
1516
import lombok.RequiredArgsConstructor;
1617
import org.springframework.data.domain.Pageable;
1718
import org.springframework.data.domain.Sort;
@@ -25,7 +26,6 @@
2526
import org.springframework.web.bind.annotation.PostMapping;
2627
import org.springframework.web.bind.annotation.RequestBody;
2728
import org.springframework.web.bind.annotation.RequestMapping;
28-
import org.springframework.web.bind.annotation.RequestParam;
2929
import org.springframework.web.bind.annotation.RestController;
3030

3131
@RestController
@@ -37,48 +37,53 @@ public class AlbumController {
3737

3838
@PostMapping
3939
public ResponseEntity<AlbumCreateResponse> createAlbum(
40-
@Valid @RequestBody AlbumCreateRequest request
40+
@Valid @RequestBody AlbumCreateRequest request,
41+
@Auth Long userId
4142
) {
42-
AlbumCreateResponse response = albumService.createAlbum(request);
43+
AlbumCreateResponse response = albumService.createAlbum(userId, request);
4344
return ResponseEntity.status(HttpStatus.CREATED).body(response);
4445
}
4546

4647
@GetMapping
4748
public ResponseEntity<AlbumsResponse> getAlbums(
48-
@RequestParam Long userId,
49-
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable
49+
@PageableDefault(sort = "createdAt", direction = Sort.Direction.DESC) Pageable pageable,
50+
@Auth Long userId
5051
) {
5152
return ResponseEntity.ok(albumService.getAlbums(userId, pageable));
5253
}
5354

5455
@PatchMapping("/{albumId}/title")
5556
public ResponseEntity<Void> updateAlbumTitle(
5657
@PathVariable Long albumId,
57-
@RequestBody @Valid AlbumTitleUpdateRequest request
58+
@RequestBody @Valid AlbumTitleUpdateRequest request,
59+
@Auth Long userId
5860
) {
5961
albumService.updateAlbumTitle(albumId, request);
6062
return ResponseEntity.noContent().build();
6163
}
6264

6365
@DeleteMapping
6466
public ResponseEntity<Void> deletePhoto(
65-
@Valid @RequestBody AlbumDeleteRequest request
67+
@Valid @RequestBody AlbumDeleteRequest request,
68+
@Auth Long userId
6669
) {
6770
albumService.deleteAlbums(request);
6871
return ResponseEntity.noContent().build();
6972
}
7073

7174
@GetMapping("/{albumId}/photos")
7275
public ResponseEntity<AlbumPhotoItemsResponse> getAlbumItems(
73-
@PathVariable Long albumId
76+
@PathVariable Long albumId,
77+
@Auth Long userId
7478
) {
7579
return ResponseEntity.ok(albumService.getAlbumPhotoItems(albumId));
7680
}
7781

7882
@PostMapping("/{albumId}/photos")
7983
public ResponseEntity<Void> createAlbumItems(
8084
@PathVariable Long albumId,
81-
@RequestBody @Valid AlbumItemCreateRequest request
85+
@RequestBody @Valid AlbumItemCreateRequest request,
86+
@Auth Long userId
8287
) {
8388
albumService.createAlbumItems(albumId, request);
8489
return ResponseEntity.noContent().build();
@@ -87,7 +92,8 @@ public ResponseEntity<Void> createAlbumItems(
8792
@DeleteMapping("/{albumId}/photos")
8893
public ResponseEntity<Void> deleteAlbumItems(
8994
@PathVariable Long albumId,
90-
@RequestBody @Valid AlbumItemDeleteRequest request
95+
@RequestBody @Valid AlbumItemDeleteRequest request,
96+
@Auth Long userId
9197
) {
9298
albumService.deleteAlbumItems(albumId, request);
9399
return ResponseEntity.noContent().build();
@@ -96,7 +102,8 @@ public ResponseEntity<Void> deleteAlbumItems(
96102
@GetMapping("/{albumId}/markers")
97103
public ResponseEntity<AlbumPhotoMarkersResponse> getAlbumPhotoMarkers(
98104
@PathVariable Long albumId,
99-
@Valid AlbumPhotoMarkersRequest request
105+
@Valid AlbumPhotoMarkersRequest request,
106+
@Auth Long userId
100107
) {
101108
return ResponseEntity.ok(albumService.getAlbumPhotoMarkers(albumId, request));
102109
}

src/main/java/kr/kro/photoliner/domain/album/dto/request/AlbumCreateRequest.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package kr.kro.photoliner.domain.album.dto.request;
22

33
import jakarta.validation.constraints.NotEmpty;
4-
import jakarta.validation.constraints.NotNull;
54

65
public record AlbumCreateRequest(
7-
@NotNull
8-
Long userId,
96
@NotEmpty
107
String title
118
) {

src/main/java/kr/kro/photoliner/domain/album/service/AlbumService.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ public class AlbumService {
3838
private final GeometryFactory geometryFactory;
3939

4040
@Transactional
41-
public AlbumCreateResponse createAlbum(AlbumCreateRequest request) {
42-
User user = userRepository.findUserById(request.userId())
43-
.orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_USER, "user id: " + request.userId()));
41+
public AlbumCreateResponse createAlbum(Long userId, AlbumCreateRequest request) {
42+
User user = userRepository.findUserById(userId)
43+
.orElseThrow(() -> CustomException.of(ApiResponseCode.NOT_FOUND_USER, "user id: " + userId));
4444
Album album = Album.builder()
4545
.title(request.title())
4646
.user(user)

src/main/java/kr/kro/photoliner/domain/photo/controller/PhotoController.java

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import kr.kro.photoliner.domain.photo.dto.response.PresignedUrlResponse;
1414
import kr.kro.photoliner.domain.photo.infra.S3CustomClient;
1515
import kr.kro.photoliner.domain.photo.service.PhotoService;
16+
import kr.kro.photoliner.global.auth.Auth;
1617
import lombok.RequiredArgsConstructor;
1718
import org.springframework.data.domain.Pageable;
1819
import org.springframework.data.domain.Sort;
@@ -39,7 +40,7 @@ public class PhotoController {
3940

4041
@GetMapping
4142
public ResponseEntity<PhotosResponse> getPhotos(
42-
@RequestParam Long userId,
43+
@Auth Long userId,
4344
@RequestParam(required = false) Boolean hasLocation,
4445
@RequestParam(required = false) Boolean hasCapturedDate,
4546
@PageableDefault(sort = "capturedDt", direction = Sort.Direction.DESC) Pageable pageable
@@ -48,8 +49,11 @@ public ResponseEntity<PhotosResponse> getPhotos(
4849
}
4950

5051
@GetMapping("/markers")
51-
public ResponseEntity<PhotoMarkersResponse> getPhotoMarkers(@Valid PhotoMarkersRequest request) {
52-
return ResponseEntity.ok(photoService.getPhotoMarkers(request));
52+
public ResponseEntity<PhotoMarkersResponse> getPhotoMarkers(
53+
@Valid PhotoMarkersRequest request,
54+
@Auth Long userId
55+
) {
56+
return ResponseEntity.ok(photoService.getPhotoMarkers(userId, request));
5357
}
5458

5559
@PostMapping("/presigned-urls")
@@ -62,14 +66,16 @@ public ResponseEntity<List<PresignedUrlResponse>> getPresignedUrls(
6266

6367
@PostMapping
6468
public ResponseEntity<Void> createPhotos(
65-
@Valid @RequestBody CreatePhotosRequest request
69+
@Valid @RequestBody CreatePhotosRequest request,
70+
@Auth Long userId
6671
) {
67-
photoService.createPhotos(request);
72+
photoService.createPhotos(userId, request);
6873
return ResponseEntity.status(HttpStatus.CREATED).build();
6974
}
7075

7176
@PatchMapping("/{photoId}/captured-date")
7277
public ResponseEntity<Void> updatePhotoCapturedDate(
78+
@Auth Long userId,
7379
@PathVariable Long photoId,
7480
@Valid @RequestBody PhotoCapturedDateUpdateRequest request
7581
) {
@@ -80,15 +86,17 @@ public ResponseEntity<Void> updatePhotoCapturedDate(
8086
@PatchMapping("/{photoId}/location")
8187
public ResponseEntity<Void> updatePhotoLocation(
8288
@PathVariable Long photoId,
83-
@Valid @RequestBody PhotoLocationUpdateRequest request
89+
@Valid @RequestBody PhotoLocationUpdateRequest request,
90+
@Auth Long userId
8491
) {
8592
photoService.updatePhotoLocation(photoId, request);
8693
return ResponseEntity.noContent().build();
8794
}
8895

8996
@DeleteMapping
9097
public ResponseEntity<Void> deletePhoto(
91-
@Valid @RequestBody DeletePhotosRequest request
98+
@Valid @RequestBody DeletePhotosRequest request,
99+
@Auth Long userId
92100
) {
93101
photoService.deletePhotos(request);
94102
return ResponseEntity.noContent().build();

src/main/java/kr/kro/photoliner/domain/photo/dto/request/CreatePhotosRequest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,6 @@
1111
import org.locationtech.jts.geom.Coordinate;
1212

1313
public record CreatePhotosRequest(
14-
@NotNull
15-
Long userId,
16-
17-
@NotNull
1814
@NotEmpty
1915
List<InnerPhoto> photos
2016
) {

src/main/java/kr/kro/photoliner/domain/photo/dto/request/PhotoMarkersRequest.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@
22

33
import jakarta.validation.constraints.Max;
44
import jakarta.validation.constraints.Min;
5-
import jakarta.validation.constraints.NotNull;
65
import org.locationtech.jts.geom.Coordinate;
76

87
public record PhotoMarkersRequest(
9-
@NotNull @Min(0)
10-
Long userId,
11-
128
@Min(0) @Max(90)
139
double swLat,
1410

src/main/java/kr/kro/photoliner/domain/photo/service/PhotoService.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,20 +44,20 @@ public PhotosResponse getPhotosByIds(Long userId, Boolean hasLocation, Boolean h
4444
}
4545

4646
@Transactional(readOnly = true)
47-
public PhotoMarkersResponse getPhotoMarkers(PhotoMarkersRequest request) {
47+
public PhotoMarkersResponse getPhotoMarkers(Long userId, PhotoMarkersRequest request) {
4848
Point sw = geometryFactory.createPoint(request.getSouthWestCoordinate());
4949
Point ne = geometryFactory.createPoint(request.getNorthEastCoordinate());
5050

51-
Photos photos = photoRepository.getByUserIdInBox(request.userId(), sw, ne);
51+
Photos photos = photoRepository.getByUserIdInBox(userId, sw, ne);
5252

5353
return PhotoMarkersResponse.from(photos);
5454
}
5555

5656
@Transactional
57-
public void createPhotos(CreatePhotosRequest request) {
57+
public void createPhotos(Long userId, CreatePhotosRequest request) {
5858
List<Photo> photos = request.photos().stream()
5959
.map(photo -> Photo.builder()
60-
.userId(request.userId())
60+
.userId(userId)
6161
.fileName(photo.fileName())
6262
.filePath(cdnURL + ORIGINAL_BASE_PATH + photo.uploadFileName())
6363
.thumbnailPath(cdnURL + THUMBNAIL_BASE_PATH + photo.uploadFileName())
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,52 @@
11
package kr.kro.photoliner.domain.user.controller;
22

3+
import java.net.URI;
4+
import kr.kro.photoliner.common.dto.response.JwtResponse;
5+
import kr.kro.photoliner.domain.user.dto.response.UserInfoResponse;
36
import kr.kro.photoliner.domain.user.service.UserService;
7+
import kr.kro.photoliner.global.auth.Auth;
8+
import kr.kro.photoliner.global.kakao.login.service.KakaoAuthService;
49
import lombok.RequiredArgsConstructor;
10+
import org.springframework.http.HttpStatus;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RequestParam;
515
import org.springframework.web.bind.annotation.RestController;
616

717
@RestController
818
@RequiredArgsConstructor
19+
@RequestMapping("/api/v1/users")
920
public class UserController {
1021

1122
private final UserService userService;
23+
private final KakaoAuthService kakaoAuthService;
24+
private static final String LOGIN_REDIRECT_URL = "http://localhost:5173/login/kakao";
25+
26+
@GetMapping("/login/kakao")
27+
public ResponseEntity<JwtResponse> login(@RequestParam(value = "code") String authorizationCode) {
28+
JwtResponse jwtResponse = userService.oAuthLogin(authorizationCode);
29+
30+
String redirectUrl = LOGIN_REDIRECT_URL + "#accessToken=" + jwtResponse.accessToken();
31+
32+
return ResponseEntity
33+
.status(HttpStatus.FOUND)
34+
.location(URI.create(redirectUrl))
35+
.build();
36+
}
37+
38+
@GetMapping("/login/kakao/authorization")
39+
public ResponseEntity<Void> authorize() {
40+
return ResponseEntity
41+
.status(HttpStatus.FOUND)
42+
.location(URI.create(kakaoAuthService.getAuthorizationRedirectUrl()))
43+
.build();
44+
}
45+
46+
@GetMapping("/info")
47+
public ResponseEntity<UserInfoResponse> getUserInfo(
48+
@Auth Long userId
49+
) {
50+
return ResponseEntity.ok(userService.getUserInfo(userId));
51+
}
1252
}

0 commit comments

Comments
 (0)