Skip to content

Commit a323c61

Browse files
authored
Merge branch 'dev' into feat/#13-2
2 parents 129961b + f285310 commit a323c61

File tree

10 files changed

+163
-11
lines changed

10 files changed

+163
-11
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@ dependencies {
4949
testImplementation 'org.springframework.security:spring-security-test'
5050
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
5151

52+
implementation 'com.google.apis:google-api-services-sheets:v4-rev516-1.23.0'
53+
implementation 'com.google.auth:google-auth-library-oauth2-http:0.20.0'
54+
55+
5256
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
5357
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
5458
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'

src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public enum ErrorCode {
1717
TOKEN_NOT_VALID(400, "C008", "토큰이 올바르지 않습니다."),
1818
INTERNAL_SERVER_ERROR(500, "C009", "서버에 문제가 발생하였습니다."),
1919
NOT_FOUND(404, "C010", "해당 리소스를 찾을 수 없습니다."),
20+
WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."),
2021
EMPTY_RESULT(400, "C012", "조회 결과가 없습니다.")
2122
;
2223

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package dmu.dasom.api.domain.google.controller;
2+
3+
import dmu.dasom.api.domain.google.service.GoogleApiService;
4+
import lombok.RequiredArgsConstructor;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.http.ResponseEntity;
7+
import org.springframework.web.bind.annotation.PostMapping;
8+
import org.springframework.web.bind.annotation.RequestMapping;
9+
import org.springframework.web.bind.annotation.RequestParam;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
import java.util.Collections;
13+
import java.util.List;
14+
15+
@RestController
16+
@RequestMapping("/google")
17+
@RequiredArgsConstructor
18+
public class GoogleController {
19+
20+
private final GoogleApiService googleApiService;
21+
@Value("${google.spreadsheet.id}")
22+
private String spreadsheetId;
23+
24+
private static final String RANGE = "A1";
25+
26+
@PostMapping("/write")
27+
public ResponseEntity<String> writeToSheet(@RequestParam String word){
28+
try{
29+
List<List<Object>> values = List.of(Collections.singletonList(word));
30+
31+
googleApiService.writeToSheet(spreadsheetId, RANGE, values);
32+
return ResponseEntity.ok("Data written successfully to the spreadsheet" + word);
33+
} catch (Exception e){
34+
e.printStackTrace();
35+
return ResponseEntity.internalServerError().body("Failed to write data to the spreadsheet" + e.getMessage());
36+
}
37+
}
38+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package dmu.dasom.api.domain.google.service;
2+
3+
import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;
4+
import com.google.api.client.json.JsonFactory;
5+
import com.google.api.client.json.jackson2.JacksonFactory;
6+
7+
import com.google.api.services.sheets.v4.Sheets;
8+
import com.google.api.services.sheets.v4.model.UpdateValuesResponse;
9+
import com.google.api.services.sheets.v4.model.ValueRange;
10+
import com.google.auth.http.HttpCredentialsAdapter;
11+
import com.google.auth.oauth2.GoogleCredentials;
12+
import dmu.dasom.api.domain.common.exception.CustomException;
13+
import dmu.dasom.api.domain.common.exception.ErrorCode;
14+
import org.slf4j.Logger;
15+
import org.slf4j.LoggerFactory;
16+
import org.springframework.beans.factory.annotation.Value;
17+
import org.springframework.core.io.ClassPathResource;
18+
import org.springframework.stereotype.Service;
19+
20+
import java.io.IOException;
21+
import java.security.GeneralSecurityException;
22+
import java.util.Collections;
23+
import java.util.List;
24+
25+
@Service
26+
public class GoogleApiService {
27+
28+
private static final Logger logger = LoggerFactory.getLogger(GoogleApiService.class);
29+
private static final String APPLICATION_NAME = "Recruit Form";
30+
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
31+
@Value("${google.credentials.file.path}")
32+
private String credentialsFilePath;
33+
private Sheets sheetsService;
34+
35+
// 해당 메소드는 sheets의 인스턴스를 얻는데 사용
36+
private Sheets getSheetsService() throws IOException, GeneralSecurityException{
37+
if(sheetsService == null){
38+
GoogleCredentials credentials = GoogleCredentials
39+
.fromStream(new ClassPathResource(credentialsFilePath).getInputStream())
40+
.createScoped(Collections.singletonList("https://www.googleapis.com/auth/spreadsheets"));
41+
sheetsService = new Sheets.Builder(GoogleNetHttpTransport.newTrustedTransport(), JSON_FACTORY, new HttpCredentialsAdapter(credentials))
42+
.setApplicationName(APPLICATION_NAME)
43+
.build();
44+
}
45+
return sheetsService;
46+
}
47+
48+
public void writeToSheet(String spreadsheetId, String range, List<List<Object>> values) {
49+
try {
50+
Sheets service = getSheetsService();
51+
ValueRange body = new ValueRange().setValues(values);
52+
UpdateValuesResponse result = service.spreadsheets().values()
53+
.update(spreadsheetId, range, body)
54+
.setValueInputOption("USER_ENTERED")
55+
.execute();
56+
logger.info("Updated rows: {}", result.getUpdatedRows());
57+
} catch (IOException e) {
58+
logger.error("Failed to write data to the spreadsheet", e);
59+
throw new CustomException(ErrorCode.WRITE_FAIL);
60+
} catch (GeneralSecurityException e) {
61+
logger.error("Failed to write data to the spreadsheet", e);
62+
throw new CustomException(ErrorCode.INTERNAL_SERVER_ERROR);
63+
}
64+
}
65+
66+
}

src/main/java/dmu/dasom/api/domain/member/controller/MemberController.java

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
import dmu.dasom.api.domain.common.exception.ErrorResponse;
44
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
55
import dmu.dasom.api.domain.member.service.MemberService;
6+
import dmu.dasom.api.global.auth.dto.TokenBox;
7+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
68
import io.swagger.v3.oas.annotations.Operation;
79
import io.swagger.v3.oas.annotations.media.Content;
810
import io.swagger.v3.oas.annotations.media.ExampleObject;
@@ -11,11 +13,10 @@
1113
import io.swagger.v3.oas.annotations.responses.ApiResponses;
1214
import jakarta.validation.Valid;
1315
import lombok.RequiredArgsConstructor;
16+
import org.springframework.http.HttpHeaders;
1417
import org.springframework.http.ResponseEntity;
15-
import org.springframework.web.bind.annotation.PostMapping;
16-
import org.springframework.web.bind.annotation.RequestBody;
17-
import org.springframework.web.bind.annotation.RequestMapping;
18-
import org.springframework.web.bind.annotation.RestController;
18+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
19+
import org.springframework.web.bind.annotation.*;
1920

2021
@RestController
2122
@RequestMapping("/api")
@@ -39,16 +40,32 @@ public class MemberController {
3940
),
4041
@ExampleObject(
4142
name = "이메일 또는 비밀번호 형식 올바르지 않음",
42-
value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }"
43-
)
44-
}
45-
)
46-
)
47-
})
43+
value = "{ \"code\": \"C007\", \"message\": \"요청한 값이 올바르지 않습니다.\" }")}))})
4844
@PostMapping("/auth/signup")
4945
public ResponseEntity<Void> signUp(@Valid @RequestBody final SignupRequestDto request) {
5046
memberService.signUp(request);
5147
return ResponseEntity.ok().build();
5248
}
5349

50+
@Operation(summary = "토큰 갱신")
51+
@ApiResponses(value = {
52+
@ApiResponse(responseCode = "200", description = "토큰 갱신 성공 (Header로 토큰 반환)"),
53+
@ApiResponse(responseCode = "400", description = "실패 케이스",
54+
content = @Content(
55+
mediaType = "application/json",
56+
schema = @Schema(implementation = ErrorResponse.class),
57+
examples = {
58+
@ExampleObject(
59+
name = "RefreshToken 만료",
60+
value = "{ \"code\": \"C004\", \"message\": \"토큰이 만료되었습니다.\" }")}))})
61+
@GetMapping("/auth/rotation")
62+
public ResponseEntity<Void> tokenRotation(@AuthenticationPrincipal final UserDetailsImpl userDetails) {
63+
final TokenBox tokenBox = memberService.tokenRotation(userDetails);
64+
final HttpHeaders headers = new HttpHeaders();
65+
headers.add("Access-Token", tokenBox.getAccessToken());
66+
headers.add("Refresh-Token", tokenBox.getRefreshToken());
67+
68+
return ResponseEntity.ok().headers(headers).build();
69+
}
70+
5471
}

src/main/java/dmu/dasom/api/domain/member/service/MemberService.java

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

33
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
44
import dmu.dasom.api.domain.member.entity.Member;
5+
import dmu.dasom.api.global.auth.dto.TokenBox;
6+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
57

68
public interface MemberService {
79

@@ -11,4 +13,6 @@ public interface MemberService {
1113

1214
void signUp(final SignupRequestDto request);
1315

16+
TokenBox tokenRotation(final UserDetailsImpl userDetails);
17+
1418
}

src/main/java/dmu/dasom/api/domain/member/service/MemberServiceImpl.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
import dmu.dasom.api.domain.member.dto.SignupRequestDto;
66
import dmu.dasom.api.domain.member.entity.Member;
77
import dmu.dasom.api.domain.member.repository.MemberRepository;
8+
import dmu.dasom.api.global.auth.dto.TokenBox;
9+
import dmu.dasom.api.global.auth.jwt.JwtUtil;
10+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
811
import lombok.RequiredArgsConstructor;
912
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
1013
import org.springframework.stereotype.Service;
@@ -17,6 +20,7 @@ public class MemberServiceImpl implements MemberService {
1720

1821
private final BCryptPasswordEncoder encoder;
1922
private final MemberRepository memberRepository;
23+
private final JwtUtil jwtUtil;
2024

2125
// 이메일로 사용자 조회
2226
@Override
@@ -42,4 +46,10 @@ public void signUp(final SignupRequestDto request) {
4246
memberRepository.save(request.toEntity(encoder.encode(request.getPassword())));
4347
}
4448

49+
// 토큰 갱신
50+
@Override
51+
public TokenBox tokenRotation(final UserDetailsImpl userDetails) {
52+
return jwtUtil.tokenRotation(userDetails);
53+
}
54+
4555
}

src/main/java/dmu/dasom/api/global/auth/config/SecurityConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public SecurityFilterChain filterChain(final HttpSecurity http, final Authentica
6868
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
6969
.authorizeHttpRequests(auth -> auth
7070
.requestMatchers("/api/admin/**").hasRole(Role.ROLE_ADMIN.getName())
71-
.requestMatchers("/api/auth/logout").authenticated()
71+
.requestMatchers("/api/auth/logout", "/api/auth/rotation").authenticated()
7272
.anyRequest().permitAll())
7373
.addFilterBefore(jwtFilter, CustomAuthenticationFilter.class)
7474
.addFilterAt(customAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)

src/main/java/dmu/dasom/api/global/auth/jwt/JwtUtil.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import dmu.dasom.api.domain.common.exception.CustomException;
44
import dmu.dasom.api.domain.common.exception.ErrorCode;
55
import dmu.dasom.api.global.auth.dto.TokenBox;
6+
import dmu.dasom.api.global.auth.userdetails.UserDetailsImpl;
67
import io.jsonwebtoken.*;
78
import io.jsonwebtoken.security.SignatureException;
89
import org.springframework.beans.factory.annotation.Value;
@@ -130,4 +131,10 @@ public boolean isExpired(final String token) {
130131
}
131132
}
132133

134+
// Access, Refresh 토큰 갱신
135+
public TokenBox tokenRotation(final UserDetailsImpl userDetails) {
136+
blacklistTokens(userDetails.getUsername());
137+
return generateTokenBox(userDetails.getUsername());
138+
}
139+
133140
}

src/main/resources/application-credentials.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,8 @@ jwt:
1919
secret: ${JWT_SECRET}
2020
access-token-expiration: ${JWT_ACCESS_TOKEN_EXPIRATION}
2121
refresh-token-expiration: ${JWT_REFRESH_TOKEN_EXPIRATION}
22+
google:
23+
credentials:
24+
path: ${GOOGLE_CREDENTIALS_PATH}
25+
spreadsheet:
26+
id: ${GOOGLE_SPREADSHEET_ID}

0 commit comments

Comments
 (0)