Skip to content

Commit 62af4a4

Browse files
authored
Merge pull request #109 from Leets-Official/refactor/#108
refactor: Redis ๋„์ž… ํ›„ Github Access,Refresh ํ† ํฐ ์ €์žฅ ๊ตฌํ˜„
2 parents 75f5217 + 16681ac commit 62af4a4

17 files changed

Lines changed: 301 additions & 37 deletions

File tree

โ€Žbuild.gradleโ€Ž

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ dependencies {
3232
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0'
3333
implementation 'javax.validation:validation-api:2.0.1.Final'
3434
implementation 'org.springframework.boot:spring-boot-starter-data-elasticsearch'
35+
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
3536

3637
compileOnly 'org.projectlombok:lombok'
3738

โ€Žsrc/main/java/com/leets/commitatobe/domain/auth/controller/AuthController.javaโ€Ž

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package com.leets.commitatobe.domain.auth.controller;
22

33
import org.springframework.web.bind.annotation.GetMapping;
4+
import org.springframework.web.bind.annotation.PathVariable;
5+
import org.springframework.web.bind.annotation.PostMapping;
46
import org.springframework.web.bind.annotation.RequestMapping;
57
import org.springframework.web.bind.annotation.RequestParam;
68
import org.springframework.web.bind.annotation.RestController;
79

810
import com.leets.commitatobe.domain.auth.dto.GitHubDto;
11+
import com.leets.commitatobe.domain.auth.dto.GithubToken;
912
import com.leets.commitatobe.domain.auth.dto.LoginResponse;
1013
import com.leets.commitatobe.domain.auth.service.AuthService;
1114
import com.leets.commitatobe.domain.auth.service.AuthQueryService;
15+
import com.leets.commitatobe.domain.auth.service.GithubTokenService;
1216
import com.leets.commitatobe.domain.user.service.UserQueryService;
1317
import com.leets.commitatobe.global.jwt.service.CustomOAuth2UserService;
1418
import com.leets.commitatobe.global.response.ApiResponse;
@@ -30,7 +34,6 @@ public class AuthController {
3034
private final AuthService authService;
3135
private final AuthQueryService authQueryService;
3236
private final UserQueryService userQueryService;
33-
3437
private final CustomOAuth2UserService customOAuth2UserService;
3538

3639
@Operation(
@@ -54,9 +57,9 @@ public void redirectToGitHub(HttpServletResponse response) {
5457
)
5558
@GetMapping("/callback")
5659
public ApiResponse<LoginResponse> githubCallback(@RequestParam("code") String code, HttpServletResponse response) {
57-
String gitHubAccessToken = authService.gitHubLogin(code);
60+
GithubToken token = authService.gitHubLogin(code);
5861

59-
LoginResponse loginResponse = customOAuth2UserService.generateJwt(gitHubAccessToken);
62+
LoginResponse loginResponse = customOAuth2UserService.generateJwt(token);
6063

6164
response.setHeader("Authentication", "Bearer " + loginResponse.jwtResponse().accessToken());
6265

@@ -68,7 +71,17 @@ public ApiResponse<LoginResponse> githubCallback(@RequestParam("code") String co
6871
public ApiResponse<GitHubDto> test() {
6972
GitHubDto user = authQueryService.getGitHubUser();
7073
String gitHubAccessToken = userQueryService.getUserGitHubAccessToken(user.userId());
71-
log.info("๊นƒํ—ˆ๋ธŒ ์—‘์„ธ์Šค ํ† ํฐ: {}", gitHubAccessToken);
74+
log.info("๊นƒํ—ˆ๋ธŒ ์—‘์„ธ์Šค ํ† ํฐ: {}", authService.encrypt(gitHubAccessToken));
7275
return ApiResponse.onSuccess(user);
7376
}
77+
78+
// 1) access๋งŒ ๊นจ๋œจ๋ฆฌ๊ธฐ
79+
private final GithubTokenService githubTokenService;
80+
@PostMapping("/invalidate-access")
81+
public ApiResponse<String> invalidateAccess(String githubId) {
82+
String refresh = githubTokenService.getDecryptedRefreshToken(githubId)
83+
.orElseThrow(() -> new RuntimeException("No refresh token."));
84+
githubTokenService.saveTokens(githubId, new GithubToken("invalid_access_token", refresh));
85+
return ApiResponse.onSuccess("์„ฑ๊ณต");
86+
}
7487
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.leets.commitatobe.domain.auth.dto;
2+
3+
public record GithubToken(
4+
String accessToken,
5+
String refreshToken
6+
) {
7+
}

โ€Žsrc/main/java/com/leets/commitatobe/domain/auth/service/AuthService.javaโ€Ž

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.springframework.web.reactive.function.client.WebClient;
1919

2020
import com.fasterxml.jackson.databind.ObjectMapper;
21+
import com.leets.commitatobe.domain.auth.dto.GithubToken;
2122
import com.leets.commitatobe.global.exception.ApiException;
2223

2324
import jakarta.servlet.http.HttpServletResponse;
@@ -49,7 +50,7 @@ public class AuthService {
4950
@Value("${jwt.iv-secret}")
5051
private String ivSecret;
5152

52-
public String gitHubLogin(String authCode) {
53+
public GithubToken gitHubLogin(String authCode) {
5354
WebClient webClient = WebClient.builder()
5455
.baseUrl("https://github.com")
5556
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@@ -72,11 +73,14 @@ public String gitHubLogin(String authCode) {
7273

7374
// JSON ํ˜•์‹์˜ ์‘๋‹ต ํŒŒ์‹ฑ
7475
String accessToken;
76+
String refreshToken;
7577

7678
try {
7779
ObjectMapper mapper = new ObjectMapper();
7880
Map<String, String> resultMap = mapper.readValue(responseBody, Map.class);
81+
7982
accessToken = resultMap.get("access_token");
83+
refreshToken = resultMap.get("refresh_token");
8084
} catch (Exception e) {
8185
throw new ApiException(_GITHUB_JSON_PARSING_ERROR);
8286
}
@@ -85,7 +89,7 @@ public String gitHubLogin(String authCode) {
8589
throw new ApiException(_GITHUB_TOKEN_GENERATION_ERROR);
8690
}
8791

88-
return accessToken;
92+
return new GithubToken(accessToken, refreshToken);
8993
}
9094

9195
public void redirect(HttpServletResponse response) {
@@ -98,6 +102,39 @@ public void redirect(HttpServletResponse response) {
98102
}
99103
}
100104

105+
public GithubToken refreshAccessToken(String refreshToken) {
106+
WebClient webClient = WebClient.builder()
107+
.baseUrl("https://github.com")
108+
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
109+
.defaultHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
110+
.build();
111+
112+
String responseBody = webClient.post()
113+
.uri("/login/oauth/access_token")
114+
.bodyValue("client_id=" + clientId +
115+
"&client_secret=" + clientSecret +
116+
"&grant_type=refresh_token" +
117+
"&refresh_token=" + refreshToken)
118+
.retrieve()
119+
.bodyToMono(String.class)
120+
.block();
121+
122+
String newAccessToken;
123+
String newRefreshToken;
124+
try {
125+
ObjectMapper mapper = new ObjectMapper();
126+
Map<String, Object> resultMap = mapper.readValue(responseBody, Map.class);
127+
128+
newAccessToken = (String)resultMap.get("access_token");
129+
newRefreshToken = (String)resultMap.get("refresh_token");
130+
131+
} catch (Exception e) {
132+
throw new ApiException(_GITHUB_JSON_PARSING_ERROR);
133+
}
134+
135+
return new GithubToken(newAccessToken, newRefreshToken);
136+
}
137+
101138
public String encrypt(String token) {
102139
byte[] encrypted;
103140
try {
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.leets.commitatobe.domain.auth.service;
2+
3+
import java.util.Optional;
4+
5+
import org.springframework.stereotype.Service;
6+
7+
import com.leets.commitatobe.domain.auth.dto.GithubToken;
8+
import com.leets.commitatobe.domain.token.entity.GithubTokenRedis;
9+
import com.leets.commitatobe.domain.token.repository.GithubTokenRedisRepository;
10+
import com.leets.commitatobe.global.exception.ApiException;
11+
import com.leets.commitatobe.global.response.code.status.ErrorStatus;
12+
13+
import lombok.RequiredArgsConstructor;
14+
15+
@Service
16+
@RequiredArgsConstructor
17+
public class GithubTokenService {
18+
private final GithubTokenRedisRepository githubTokenRedisRepository;
19+
private final AuthService authService;
20+
21+
public void saveTokens(String githubId, GithubToken token) {
22+
GithubTokenRedis tokenRedis = GithubTokenRedis.builder()
23+
.githubId(githubId)
24+
.accessToken(authService.encrypt(token.accessToken()))
25+
.refreshToken(authService.encrypt(token.refreshToken()))
26+
.build();
27+
28+
githubTokenRedisRepository.save(tokenRedis);
29+
}
30+
31+
public GithubToken updateAccessTokenByRefreshToken(String githubId) {
32+
String refreshToken = getDecryptedRefreshToken(githubId)
33+
.orElseThrow(() -> new ApiException(ErrorStatus._REFRESH_TOKEN_EXPIRED));
34+
35+
GithubToken newToken = authService.refreshAccessToken(refreshToken);
36+
37+
saveTokens(githubId, newToken);
38+
39+
return newToken;
40+
}
41+
42+
public Optional<String> getDecryptedAccessToken(String githubId) {
43+
return githubTokenRedisRepository.findById(githubId)
44+
.map(GithubTokenRedis::getAccessToken)
45+
.map(authService::decrypt);
46+
}
47+
48+
public Optional<String> getDecryptedRefreshToken(String githubId) {
49+
return githubTokenRedisRepository.findById(githubId)
50+
.map(GithubTokenRedis::getRefreshToken)
51+
.map(authService::decrypt);
52+
}
53+
}

โ€Žsrc/main/java/com/leets/commitatobe/domain/commit/service/DailyCommitScheduler.javaโ€Ž

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,30 +7,56 @@
77
import org.springframework.stereotype.Component;
88
import org.springframework.transaction.annotation.Transactional;
99

10-
import com.leets.commitatobe.domain.auth.service.AuthService;
10+
import com.leets.commitatobe.domain.auth.dto.GithubToken;
11+
import com.leets.commitatobe.domain.auth.service.GithubTokenService;
1112
import com.leets.commitatobe.domain.commit.domain.Commit;
1213
import com.leets.commitatobe.domain.commit.repository.CommitRepository;
1314
import com.leets.commitatobe.domain.user.domain.User;
1415
import com.leets.commitatobe.domain.user.repository.UserRepository;
16+
import com.leets.commitatobe.global.exception.ApiException;
17+
import com.leets.commitatobe.global.response.code.status.ErrorStatus;
1518

1619
import lombok.RequiredArgsConstructor;
1720

1821
@Component
1922
@RequiredArgsConstructor
2023
public class DailyCommitScheduler {
24+
private static final long SIX_MONTHS = 6L;
25+
2126
private final UserRepository userRepository;
2227
private final GitHubService gitHubService;
2328
private final CommitRepository commitRepository;
2429
private final ExpService expService;
25-
private final AuthService authService;
30+
private final GithubTokenService githubTokenService;
2631

2732
@Scheduled(cron = "0 30 06 * * *")
2833
@Transactional
2934
public void updateAllUsersCommits() {
30-
List<User> users = userRepository.findAll();
35+
List<User> users = userRepository.findAllByIsHumanAccountFalse();
36+
37+
LocalDateTime afterHalfYear = LocalDateTime.now().minusMonths(SIX_MONTHS);
3138

3239
for (User user : users) {
33-
String token = authService.decrypt(user.getGitHubAccessToken());
40+
if (user.getLastLoginAt() != null && !user.getLastLoginAt().isAfter(afterHalfYear)) {
41+
user.changeHumanAccount();
42+
userRepository.save(user);
43+
continue;
44+
}
45+
try {
46+
tryCommitUpdate(user);
47+
} catch (ApiException e) {
48+
GithubToken newToken = githubTokenService.updateAccessTokenByRefreshToken(user.getGithubId());
49+
gitHubService.updateToken(newToken.accessToken());
50+
51+
tryCommitUpdate(user);
52+
}
53+
}
54+
}
55+
56+
private void tryCommitUpdate(User user) {
57+
gitHubService.runWithoutRedirect(() -> {
58+
String token = githubTokenService.getDecryptedAccessToken(user.getGithubId())
59+
.orElseThrow(() -> new ApiException(ErrorStatus._UNAUTHORIZED));
3460
gitHubService.updateToken(token);
3561

3662
LocalDateTime time = user.getLastCommitUpdateTime();
@@ -56,6 +82,6 @@ public void updateAllUsersCommits() {
5682
userRepository.save(user);
5783

5884
expService.calculateAndSaveExp(user.getGithubId());
59-
}
85+
});
6086
}
6187
}

โ€Žsrc/main/java/com/leets/commitatobe/domain/commit/service/GitHubService.javaโ€Ž

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
import com.google.gson.JsonElement;
2626
import com.google.gson.JsonObject;
2727
import com.google.gson.JsonParser;
28+
import com.leets.commitatobe.global.exception.ApiException;
29+
import com.leets.commitatobe.global.response.code.status.ErrorStatus;
2830

2931
import lombok.Getter;
3032
import lombok.RequiredArgsConstructor;
@@ -37,6 +39,7 @@ public class GitHubService {
3739
private final String GITHUB_API_URL = "https://api.github.com";
3840
private String AUTH_TOKEN;
3941
private final Map<LocalDateTime, Integer> commitsByDate = new HashMap<>();
42+
private static final ThreadLocal<Boolean> REDIRECT_ON_401 = ThreadLocal.withInitial(() -> true);
4043
private final WebClient webClient = WebClient.builder()
4144
.baseUrl(GITHUB_API_URL)
4245
.defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@@ -149,17 +152,25 @@ public void countCommits(String fullName, String gitHubUsername, LocalDateTime d
149152

150153
// http ์—ฐ๊ฒฐ
151154
private JsonArray getConnection(String url) {
155+
boolean redirect = REDIRECT_ON_401.get();
156+
152157
Mono<JsonArray> response = webClient.get()
153158
.uri(url)
154159
.header(HttpHeaders.AUTHORIZATION, "Bearer " + AUTH_TOKEN)
155160
.retrieve()
156161
.onStatus(status -> status == HttpStatus.UNAUTHORIZED, clientResponse ->
157-
// AUTH_TOKEN์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
158-
webClient.get()
159-
.uri(SERVER_URI + "/login/github")
160-
.retrieve()
161-
.bodyToMono(Void.class)
162-
.then(Mono.error(new RuntimeException("Unauthorized")))
162+
{
163+
if (redirect) {
164+
// AUTH_TOKEN์ด ์œ ํšจํ•˜์ง€ ์•Š์œผ๋ฉด ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
165+
return webClient.get()
166+
.uri(SERVER_URI + "/login/github")
167+
.retrieve()
168+
.bodyToMono(Void.class)
169+
.then(Mono.error(new ApiException(ErrorStatus._UNAUTHORIZED)));
170+
} else {
171+
return Mono.error(new ApiException(ErrorStatus._UNAUTHORIZED));
172+
}
173+
}
163174
)
164175
.bodyToMono(String.class)
165176
.map(res -> JsonParser.parseString(res).getAsJsonArray());
@@ -202,4 +213,26 @@ private String formatToISO8601(LocalDateTime dateTime) {
202213
public void updateToken(String accessToken) {
203214
this.AUTH_TOKEN = accessToken;
204215
}
216+
217+
public <T> T runWithoutRedirect(java.util.concurrent.Callable<T> work) {
218+
boolean prev = REDIRECT_ON_401.get();
219+
REDIRECT_ON_401.set(false);
220+
try {
221+
return work.call();
222+
} catch (Exception e) {
223+
throw new RuntimeException(e);
224+
} finally {
225+
REDIRECT_ON_401.set(prev);
226+
}
227+
}
228+
229+
public void runWithoutRedirect(Runnable work) {
230+
boolean prev = REDIRECT_ON_401.get();
231+
REDIRECT_ON_401.set(false);
232+
try {
233+
work.run();
234+
} finally {
235+
REDIRECT_ON_401.set(prev);
236+
}
237+
}
205238
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.leets.commitatobe.domain.token.entity;
2+
3+
import org.springframework.data.annotation.Id;
4+
import org.springframework.data.redis.core.RedisHash;
5+
6+
import lombok.AllArgsConstructor;
7+
import lombok.Builder;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
import lombok.Setter;
11+
12+
@Getter
13+
@Setter
14+
@NoArgsConstructor
15+
@AllArgsConstructor
16+
@Builder
17+
@RedisHash(value = "token")
18+
public class GithubTokenRedis {
19+
@Id
20+
private String githubId;
21+
22+
private String accessToken;
23+
24+
private String refreshToken;
25+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.leets.commitatobe.domain.token.repository;
2+
3+
import org.springframework.data.repository.CrudRepository;
4+
5+
import com.leets.commitatobe.domain.token.entity.GithubTokenRedis;
6+
7+
public interface GithubTokenRedisRepository extends CrudRepository<GithubTokenRedis, String> {
8+
}

0 commit comments

Comments
ย (0)