From 7c74b6d4a47620578b88c1fb4c6823b5f466de0f Mon Sep 17 00:00:00 2001 From: nahowo Date: Tue, 19 Nov 2024 16:06:39 +0900 Subject: [PATCH 01/12] =?UTF-8?q?fix:=20user=20update=20=EC=98=A4=EB=A5=98?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/Alchive/backend/service/UserService.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/Alchive/backend/service/UserService.java b/src/main/java/com/Alchive/backend/service/UserService.java index 38a3ff9..bfde415 100644 --- a/src/main/java/com/Alchive/backend/service/UserService.java +++ b/src/main/java/com/Alchive/backend/service/UserService.java @@ -54,7 +54,9 @@ public User getUserDetail(Long userId) { @Transactional public User updateUserDetail(User user, UserUpdateRequest updateRequest) { - return user.update(updateRequest.getDescription(), updateRequest.getAutoSave()); + User updatedUser = userRepository.findById(user.getId()) + .orElseThrow(NoSuchUserIdException::new); + return updatedUser.update(updateRequest.getDescription(), updateRequest.getAutoSave()); } @Transactional From 07c22aee5dbff3161c81a9988800304b2e2ad56e Mon Sep 17 00:00:00 2001 From: nahowo Date: Tue, 19 Nov 2024 21:50:50 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20redis=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 3 +++ docker-compose.yml | 9 ++++++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 0ead835..6128922 100644 --- a/build.gradle +++ b/build.gradle @@ -55,6 +55,9 @@ dependencies { implementation("com.slack.api:bolt:1.18.0") implementation("com.slack.api:bolt-servlet:1.18.0") implementation("com.slack.api:bolt-jetty:1.18.0") + + // Redis + implementation 'org.springframework.boot:spring-boot-starter-data-redis' } tasks.named('test') { diff --git a/docker-compose.yml b/docker-compose.yml index 9963627..a1db57d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -20,7 +20,14 @@ services: networks: - alchive - # springboot: + redis: + image: redis:latest + container_name: redis + ports: + - "6379:6379" + volumes: + - alchive-db:/alchive-db/redis + # container_name: springboot # build: # context: . From d86c2083f8d9bd5b00b3242d39a60b677be8e4fa Mon Sep 17 00:00:00 2001 From: nahowo Date: Tue, 19 Nov 2024 21:54:51 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20refreshToken=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC,=20=EC=84=9C=EB=B9=84=EC=8A=A4,=20=EB=A0=88?= =?UTF-8?q?=ED=8F=AC=EC=A7=80=ED=86=A0=EB=A6=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/CreateRefreshTokenRequest.java | 9 +++++ .../backend/config/redis/RefreshToken.java | 17 ++++++++ .../config/redis/RefreshTokenController.java | 39 +++++++++++++++++++ .../config/redis/RefreshTokenRepository.java | 9 +++++ .../config/redis/RefreshTokenService.java | 27 +++++++++++++ 5 files changed, 101 insertions(+) create mode 100644 src/main/java/com/Alchive/backend/config/redis/CreateRefreshTokenRequest.java create mode 100644 src/main/java/com/Alchive/backend/config/redis/RefreshToken.java create mode 100644 src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java create mode 100644 src/main/java/com/Alchive/backend/config/redis/RefreshTokenRepository.java create mode 100644 src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java diff --git a/src/main/java/com/Alchive/backend/config/redis/CreateRefreshTokenRequest.java b/src/main/java/com/Alchive/backend/config/redis/CreateRefreshTokenRequest.java new file mode 100644 index 0000000..4fbfa0a --- /dev/null +++ b/src/main/java/com/Alchive/backend/config/redis/CreateRefreshTokenRequest.java @@ -0,0 +1,9 @@ +package com.Alchive.backend.config.redis; + +import lombok.Getter; + +@Getter +public class CreateRefreshTokenRequest { + private String email; + private String refreshToken; +} diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java b/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java new file mode 100644 index 0000000..2cc2aaf --- /dev/null +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java @@ -0,0 +1,17 @@ +package com.Alchive.backend.config.redis; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.data.annotation.Id; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +@AllArgsConstructor +@Getter +@RedisHash(value = "jwtToken") +public class RefreshToken { + @Id + @Indexed + private String email; + private String refreshToken; +} diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java new file mode 100644 index 0000000..d0184f5 --- /dev/null +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java @@ -0,0 +1,39 @@ +package com.Alchive.backend.config.redis; + +import com.Alchive.backend.config.result.ResultResponse; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import static com.Alchive.backend.config.result.ResultCode.*; + +@RestController +@Tag(name = "리프레시 토큰", description = "[Test] 리프레시 토큰 관련 api입니다. ") +@RequestMapping("/api/v2/refreshTokens") +@RequiredArgsConstructor +public class RefreshTokenController { + private final RefreshTokenService refreshTokenService; + + @Operation(summary = "리프레시 토큰 저장 메서드", description = "리프레시 토큰을 Redis에 저장하는 메서드입니다. ") + @PostMapping("") + public ResponseEntity saveRefreshToken(@RequestBody CreateRefreshTokenRequest request) { + refreshTokenService.saveRefreshToken(request.getEmail(), request.getRefreshToken()); + return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, request.getRefreshToken())); + } + + @Operation(summary = "리프레시 토큰 검색 메서드", description = "이메일로 리프레시 토큰을 가져오는 메서드입니다. ") + @GetMapping("") + public ResponseEntity getResfreshToken(@RequestParam String email) { + String refreshToken = refreshTokenService.getRefreshToken(email); + return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, refreshToken)); + } + + @Operation(summary = "리프레시 토큰 재발급 메서드", description = "이메일로 리프레시 토큰을 재발급하는 메서드입니다. ") + @GetMapping("/newToken") + public ResponseEntity createRefreshToken(@RequestParam String email) { + String refreshToken = refreshTokenService.createRefreshToken(email); + return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, refreshToken)); + } +} diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenRepository.java b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenRepository.java new file mode 100644 index 0000000..3b7ee23 --- /dev/null +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenRepository.java @@ -0,0 +1,9 @@ +package com.Alchive.backend.config.redis; + +import org.springframework.data.repository.CrudRepository; + +import java.util.Optional; + +public interface RefreshTokenRepository extends CrudRepository { + Optional findByEmail(String email); +} diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java new file mode 100644 index 0000000..ff22da4 --- /dev/null +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java @@ -0,0 +1,27 @@ +package com.Alchive.backend.config.redis; + +import com.Alchive.backend.config.error.exception.user.NoSuchUserIdException; +import com.Alchive.backend.config.jwt.JwtTokenProvider; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class RefreshTokenService { + private final RefreshTokenRepository refreshTokenRepository; + private final JwtTokenProvider jwtTokenProvider; + + public void saveRefreshToken(String email, String refreshToken) { + refreshTokenRepository.save(new RefreshToken(email, refreshToken)); + } + + public String getRefreshToken(String email) { + RefreshToken refreshToken = refreshTokenRepository.findByEmail(email) + .orElseThrow(NoSuchUserIdException::new); + return refreshToken.getRefreshToken(); + } + + public String createRefreshToken(String email) { + return jwtTokenProvider.createRefreshToken(email); + } +} From a212383d16d73aad48b72946db996dc4d903b0c2 Mon Sep 17 00:00:00 2001 From: nahowo Date: Thu, 21 Nov 2024 20:48:00 +0900 Subject: [PATCH 04/12] =?UTF-8?q?refactor:=20=EB=A6=AC=ED=94=84=EB=A0=88?= =?UTF-8?q?=EC=8B=9C=20=ED=86=A0=ED=81=B0=20redis=20=EC=A0=80=EC=9E=A5=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/config/auth/SecurityConfig.java | 4 ++- .../auth/handler/OAuth2SuccessHandler.java | 5 ++- .../config/jwt/JwtAuthenticationFilter.java | 32 ++++++++++++------- .../backend/config/jwt/JwtController.java | 15 ++++++--- .../backend/config/jwt/JwtTokenProvider.java | 31 +++++++++++------- 5 files changed, 57 insertions(+), 30 deletions(-) diff --git a/src/main/java/com/Alchive/backend/config/auth/SecurityConfig.java b/src/main/java/com/Alchive/backend/config/auth/SecurityConfig.java index c1c7223..56c851f 100644 --- a/src/main/java/com/Alchive/backend/config/auth/SecurityConfig.java +++ b/src/main/java/com/Alchive/backend/config/auth/SecurityConfig.java @@ -5,6 +5,7 @@ import com.Alchive.backend.config.auth.service.CustomOAuth2UserService; import com.Alchive.backend.config.jwt.JwtAuthenticationFilter; import com.Alchive.backend.config.jwt.JwtTokenProvider; +import com.Alchive.backend.config.redis.RefreshTokenService; import com.Alchive.backend.service.UserService; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -27,6 +28,7 @@ public class SecurityConfig { private final OAuth2FailureHandler oAuth2FailureHandler; private final JwtTokenProvider jwtTokenProvider; private final UserService userService; + private final RefreshTokenService refreshTokenService; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -39,7 +41,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { requests.anyRequest().permitAll() // 모든 요청을 모든 사용자에게 허용 ) .addFilterBefore( - new JwtAuthenticationFilter(jwtTokenProvider, userService), + new JwtAuthenticationFilter(jwtTokenProvider, userService, refreshTokenService), UsernamePasswordAuthenticationFilter.class ) .sessionManagement(sessionManagement -> diff --git a/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java b/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java index f7d0dcf..fce6bfe 100644 --- a/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java @@ -1,6 +1,7 @@ package com.Alchive.backend.config.auth.handler; import com.Alchive.backend.config.jwt.JwtTokenProvider; +import com.Alchive.backend.config.redis.RefreshTokenService; import com.Alchive.backend.domain.user.User; import com.Alchive.backend.repository.UserRepository; import jakarta.servlet.http.HttpServletRequest; @@ -22,6 +23,7 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final UserRepository userRepository; private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenService refreshTokenService; // 검증 완료된 유저의 정보를 가져와서 토큰 생성, 로그인/회원가입 요청에 맞게 리다이렉트 @Override @@ -40,8 +42,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo String refreshToken = jwtTokenProvider.createRefreshToken(userEmail); targetUrl = UriComponentsBuilder.fromUriString("/") .queryParam("access", accessToken) - .queryParam("refresh", refreshToken) +// .queryParam("refresh", refreshToken) .build().toUriString(); + refreshTokenService.saveRefreshToken(userEmail, refreshToken); } else { // 회원가입인 경우 targetUrl = UriComponentsBuilder.fromUriString("http://localhost:5173/sign") .queryParam("email", email) diff --git a/src/main/java/com/Alchive/backend/config/jwt/JwtAuthenticationFilter.java b/src/main/java/com/Alchive/backend/config/jwt/JwtAuthenticationFilter.java index cfef4b8..0076c42 100644 --- a/src/main/java/com/Alchive/backend/config/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/Alchive/backend/config/jwt/JwtAuthenticationFilter.java @@ -3,7 +3,9 @@ import com.Alchive.backend.config.error.ErrorCode; import com.Alchive.backend.config.error.ErrorResponse; import com.Alchive.backend.config.error.exception.BusinessException; +import com.Alchive.backend.config.error.exception.token.TokenExpiredException; import com.Alchive.backend.config.error.exception.token.TokenNotExistsException; +import com.Alchive.backend.config.redis.RefreshTokenService; import com.Alchive.backend.domain.user.User; import com.Alchive.backend.service.UserService; import com.fasterxml.jackson.databind.ObjectMapper; @@ -12,6 +14,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; @@ -24,9 +27,11 @@ @Component @RequiredArgsConstructor +@Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final UserService userService; + private final RefreshTokenService refreshTokenService; private final AntPathMatcher pathMatcher = new AntPathMatcher(); @@ -42,7 +47,9 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { Map.entry("/api/v2/users/username/{name}", List.of("GET")), Map.entry("/api/v2/sns/{snsId}", List.of("GET")), Map.entry("/api/v2/slack/reminder", List.of("GET")), - Map.entry("/api/v2/slack/added", List.of("GET")) + Map.entry("/api/v2/slack/added", List.of("GET")), + Map.entry("/api/v2/jwt/**", List.of("GET")), + Map.entry("/api/v2/refreshTokens/**", List.of("GET", "POST")) ); // EXCLUDE_URL과 메서드에 일치할 경우 현재 필터를 진행하지 않고 다음 필터 진행 @@ -64,25 +71,26 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse try { // 액세스 토큰 추출 및 검증 String accessToken = jwtTokenProvider.resolveAccessToken(request); - if (accessToken != null && jwtTokenProvider.validateToken(accessToken)) { + String email = jwtTokenProvider.getEmailFromToken(accessToken); + if ( jwtTokenProvider.validateToken(accessToken) ) { authenticateWithToken(accessToken); } - // 액세스 토큰이 없거나 만료된 경우 리프레시 토큰 확인 else { + // 액세스 토큰이 만료된 경우 리프레시 토큰 확인 + log.info("액세스 토큰 만료"); // 리프레시 토큰 추출 및 검증 - String refreshToken = jwtTokenProvider.resolveRefreshToken(request); - if (refreshToken != null && jwtTokenProvider.validateToken(refreshToken)) { - String email = jwtTokenProvider.getEmailFromToken(refreshToken); - // 새로운 액세스, 리프레시 토큰 발급 + String refreshToken = refreshTokenService.getRefreshToken(email); + if ( jwtTokenProvider.validateToken(refreshToken) ) { + // 이메일로 새로운 액세스 토큰 발급 String newAccessToken = jwtTokenProvider.createAccessToken(email); - String newRefreshToken = jwtTokenProvider.createRefreshToken(email); + log.info("새 액세스 토큰: " + newAccessToken); response.setHeader("Authorization", "Bearer " + newAccessToken); - response.setHeader("Refresh-Token", newRefreshToken); // 새로 발급된 액세스 토큰으로 인증 처리 authenticateWithToken(newAccessToken); - } else { - // 토큰이 없는 경우 - throw new TokenNotExistsException(); + } + else { + log.info("리프레시 토큰 만료"); + throw new TokenExpiredException(); } } diff --git a/src/main/java/com/Alchive/backend/config/jwt/JwtController.java b/src/main/java/com/Alchive/backend/config/jwt/JwtController.java index 6aec2d4..955c516 100644 --- a/src/main/java/com/Alchive/backend/config/jwt/JwtController.java +++ b/src/main/java/com/Alchive/backend/config/jwt/JwtController.java @@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import static com.Alchive.backend.config.result.ResultCode.TOKEN_ACCESS_SUCCESS; +import static com.Alchive.backend.config.result.ResultCode.*; @Tag(name = "JWT", description = "[Test] JWT 관련 api입니다.") @RequiredArgsConstructor @@ -18,10 +18,17 @@ public class JwtController { private final JwtTokenProvider jwtTokenProvider; - @Operation(summary = "토큰 재발급 메서드", description = "액세스 토큰을 재발급하는 메서드입니다.") - @GetMapping("") - public ResponseEntity createToken(String email) { + @Operation(summary = "액세스 토큰 재발급 메서드", description = "액세스 토큰을 재발급하는 메서드입니다.") + @GetMapping("/access") + public ResponseEntity createAccessToken(String email) { String accessToken = jwtTokenProvider.createAccessToken(email); return ResponseEntity.ok(ResultResponse.of(TOKEN_ACCESS_SUCCESS, accessToken)); } + + @Operation(summary = "리프레시 토큰 재발급 메서드", description = "리프레시 토큰을 재발급하는 메서드입니다.") + @GetMapping("/refresh") + public ResponseEntity createRefreshToken(String email) { + String refreshToken = jwtTokenProvider.createRefreshToken(email); + return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, refreshToken)); + } } diff --git a/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java b/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java index 6f9b074..0def5d7 100644 --- a/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java +++ b/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java @@ -1,13 +1,14 @@ package com.Alchive.backend.config.jwt; import com.Alchive.backend.config.error.exception.token.TokenExpiredException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import com.Alchive.backend.config.error.exception.token.TokenNotExistsException; +import io.jsonwebtoken.*; import io.jsonwebtoken.io.Decoders; import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; import jakarta.annotation.PostConstruct; import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; @@ -66,7 +67,7 @@ private String resolveToken(HttpServletRequest request, String headerName, Strin String header = request.getHeader(headerName); return prefix.isEmpty() ? header : header.substring(prefix.length()); } catch (NullPointerException | IllegalArgumentException e) { - return null; + throw new TokenNotExistsException(); } } @@ -78,18 +79,24 @@ public boolean validateToken(String token) { .build() .parseClaimsJws(token); return true; - } catch (Exception e) { - throw new TokenExpiredException(); + } catch (ExpiredJwtException e) { + return false; + } catch (JwtException e) { + throw e; } } // 이메일 추출 public String getEmailFromToken(String token) { - Claims claims = Jwts.parserBuilder() - .setSigningKey(secretKey) - .build() - .parseClaimsJws(token) - .getBody(); - return claims.getSubject(); + try { + Claims claims = Jwts.parserBuilder() + .setSigningKey(secretKey) + .build() + .parseClaimsJws(token) + .getBody(); + return claims.getSubject(); + } catch (ExpiredJwtException exception) { + return exception.getClaims().getSubject(); + } } } From dcd75dfc39bde46f598393c36c333d62da0ccfda Mon Sep 17 00:00:00 2001 From: nahowo Date: Thu, 21 Nov 2024 21:24:15 +0900 Subject: [PATCH 05/12] =?UTF-8?q?fix:=20RedisHash=20Value=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Alchive/backend/config/redis/RefreshToken.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java b/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java index 2cc2aaf..d41d44a 100644 --- a/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java @@ -8,7 +8,7 @@ @AllArgsConstructor @Getter -@RedisHash(value = "jwtToken") +@RedisHash(value = "refreshToken") public class RefreshToken { @Id @Indexed From 8cd3c57e6a9db07a4d005c2dcb3544364d81c277 Mon Sep 17 00:00:00 2001 From: nahowo Date: Tue, 3 Dec 2024 19:59:36 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=8B=9C=20=EC=95=A1=EC=84=B8=EC=8A=A4=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EB=A7=8C=20=EB=B0=98=ED=99=98=ED=95=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/Alchive/backend/dto/response/UserResponseDTO.java | 4 +--- src/main/java/com/Alchive/backend/service/UserService.java | 7 +++++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/Alchive/backend/dto/response/UserResponseDTO.java b/src/main/java/com/Alchive/backend/dto/response/UserResponseDTO.java index e366cbc..66c78c1 100644 --- a/src/main/java/com/Alchive/backend/dto/response/UserResponseDTO.java +++ b/src/main/java/com/Alchive/backend/dto/response/UserResponseDTO.java @@ -11,13 +11,11 @@ public class UserResponseDTO { private String userEmail; private String userName; private String accessToken; - private String refreshToken; - public UserResponseDTO(User user, String accessToken, String refreshToken) { + public UserResponseDTO(User user, String accessToken) { this.userId=user.getId(); this.userEmail=user.getEmail(); this.userName=user.getName(); this.accessToken=accessToken; - this.refreshToken=refreshToken; } } diff --git a/src/main/java/com/Alchive/backend/service/UserService.java b/src/main/java/com/Alchive/backend/service/UserService.java index bfde415..b26471a 100644 --- a/src/main/java/com/Alchive/backend/service/UserService.java +++ b/src/main/java/com/Alchive/backend/service/UserService.java @@ -5,6 +5,7 @@ import com.Alchive.backend.config.error.exception.user.UserEmailExistException; import com.Alchive.backend.config.error.exception.user.UserNameExistException; import com.Alchive.backend.config.jwt.JwtTokenProvider; +import com.Alchive.backend.config.redis.RefreshTokenService; import com.Alchive.backend.domain.user.User; import com.Alchive.backend.dto.request.UserCreateRequest; import com.Alchive.backend.dto.request.UserUpdateRequest; @@ -21,6 +22,7 @@ public class UserService { private final UserRepository userRepository; private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenService refreshTokenService; @Transactional public UserResponseDTO createUser(UserCreateRequest request) { @@ -39,8 +41,9 @@ public UserResponseDTO createUser(UserCreateRequest request) { user = userRepository.save(user); // 토큰 생성 후 전달 String accessToken = jwtTokenProvider.createAccessToken(email); - String refreshToken = jwtTokenProvider.createRefreshToken(email); - return new UserResponseDTO(user, accessToken, refreshToken); + String refreshToken = refreshTokenService.createRefreshToken(email); + refreshTokenService.saveRefreshToken(email, refreshToken); + return new UserResponseDTO(user, accessToken); } public boolean isDuplicateUsername(String name) { From edf0975f2be49f3ce3286bc9a990e99e0e595028 Mon Sep 17 00:00:00 2001 From: nahowo Date: Tue, 3 Dec 2024 20:00:54 +0900 Subject: [PATCH 07/12] =?UTF-8?q?refactor:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0?= =?UTF-8?q?=20=EC=84=A4=EC=A0=95=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Alchive/backend/config/SwaggerConfig.java | 7 +------ .../backend/config/auth/handler/OAuth2SuccessHandler.java | 3 +-- .../java/com/Alchive/backend/config/jwt/JwtController.java | 7 ------- 3 files changed, 2 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/Alchive/backend/config/SwaggerConfig.java b/src/main/java/com/Alchive/backend/config/SwaggerConfig.java index 9516445..a40ffdf 100644 --- a/src/main/java/com/Alchive/backend/config/SwaggerConfig.java +++ b/src/main/java/com/Alchive/backend/config/SwaggerConfig.java @@ -26,13 +26,8 @@ public OpenAPI openAPI() { .bearerFormat("JWT") .in(SecurityScheme.In.HEADER) .name(HttpHeaders.AUTHORIZATION); - SecurityScheme refreshTokenSecurityScheme = new SecurityScheme() - .type(SecurityScheme.Type.APIKEY) - .in(SecurityScheme.In.HEADER) - .name("REFRESH-TOKEN"); Components components = new Components() - .addSecuritySchemes(key, accessTokenSecurityScheme) - .addSecuritySchemes(refreshKey, refreshTokenSecurityScheme); + .addSecuritySchemes(key, accessTokenSecurityScheme); return new OpenAPI() .info(apiInfo()) diff --git a/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java b/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java index fce6bfe..21bc04e 100644 --- a/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java +++ b/src/main/java/com/Alchive/backend/config/auth/handler/OAuth2SuccessHandler.java @@ -39,10 +39,9 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo if (user.isPresent()) { // 로그인인 경우 userEmail = user.get().getEmail(); String accessToken = jwtTokenProvider.createAccessToken(userEmail); - String refreshToken = jwtTokenProvider.createRefreshToken(userEmail); + String refreshToken = refreshTokenService.createRefreshToken(userEmail); targetUrl = UriComponentsBuilder.fromUriString("/") .queryParam("access", accessToken) -// .queryParam("refresh", refreshToken) .build().toUriString(); refreshTokenService.saveRefreshToken(userEmail, refreshToken); } else { // 회원가입인 경우 diff --git a/src/main/java/com/Alchive/backend/config/jwt/JwtController.java b/src/main/java/com/Alchive/backend/config/jwt/JwtController.java index 955c516..4781e02 100644 --- a/src/main/java/com/Alchive/backend/config/jwt/JwtController.java +++ b/src/main/java/com/Alchive/backend/config/jwt/JwtController.java @@ -24,11 +24,4 @@ public ResponseEntity createAccessToken(String email) { String accessToken = jwtTokenProvider.createAccessToken(email); return ResponseEntity.ok(ResultResponse.of(TOKEN_ACCESS_SUCCESS, accessToken)); } - - @Operation(summary = "리프레시 토큰 재발급 메서드", description = "리프레시 토큰을 재발급하는 메서드입니다.") - @GetMapping("/refresh") - public ResponseEntity createRefreshToken(String email) { - String refreshToken = jwtTokenProvider.createRefreshToken(email); - return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, refreshToken)); - } } From 8f319828c9aa350091ccc37f44937ed85218d673 Mon Sep 17 00:00:00 2001 From: nahowo Date: Sat, 5 Apr 2025 08:05:23 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat:=20TTL=20=EC=B6=94=EA=B0=80,=20?= =?UTF-8?q?=EC=9E=AC=EB=B0=9C=EA=B8=89=20=EC=8B=9C=20redis=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/Alchive/backend/config/redis/RefreshToken.java | 2 +- .../Alchive/backend/config/redis/RefreshTokenController.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java b/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java index d41d44a..889ac22 100644 --- a/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshToken.java @@ -8,7 +8,7 @@ @AllArgsConstructor @Getter -@RedisHash(value = "refreshToken") +@RedisHash(value = "refreshToken", timeToLive = 6000000) public class RefreshToken { @Id @Indexed diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java index d0184f5..e57cdab 100644 --- a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java @@ -34,6 +34,7 @@ public ResponseEntity getResfreshToken(@RequestParam String emai @GetMapping("/newToken") public ResponseEntity createRefreshToken(@RequestParam String email) { String refreshToken = refreshTokenService.createRefreshToken(email); + refreshTokenService.saveRefreshToken(email, refreshToken); return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, refreshToken)); } } From 6e7bfe32496a2340958ca0ddf84e5e8c84188c74 Mon Sep 17 00:00:00 2001 From: nahowo Date: Sat, 5 Apr 2025 08:16:10 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat:=20redis=20=EB=AA=A9=EC=97=85=20?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/config/DataInitializer.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/Alchive/backend/config/DataInitializer.java b/src/main/java/com/Alchive/backend/config/DataInitializer.java index 7f015bb..cc2a321 100644 --- a/src/main/java/com/Alchive/backend/config/DataInitializer.java +++ b/src/main/java/com/Alchive/backend/config/DataInitializer.java @@ -1,5 +1,7 @@ package com.Alchive.backend.config; +import com.Alchive.backend.config.redis.RefreshTokenRepository; +import com.Alchive.backend.config.redis.RefreshTokenService; import com.Alchive.backend.domain.algorithm.Algorithm; import com.Alchive.backend.domain.algorithmProblem.AlgorithmProblem; import com.Alchive.backend.domain.board.Board; @@ -33,19 +35,22 @@ public class DataInitializer implements CommandLineRunner { private final BoardRepository boardRepository; private final AlgorithmRepository algorithmRepository; private final AlgorithmProblemRepository algorithmProblemRepository; + private final RefreshTokenService refreshTokenService; public DataInitializer(UserRepository userRepository, ProblemRepository problemRepository, SolutionRepository solutionRepository, BoardRepository boardRepository, AlgorithmRepository algorithmRepository, - AlgorithmProblemRepository algorithmProblemRepository) { + AlgorithmProblemRepository algorithmProblemRepository, + RefreshTokenService refreshTokenService) { this.userRepository = userRepository; this.problemRepository = problemRepository; this.solutionRepository = solutionRepository; this.boardRepository = boardRepository; this.algorithmRepository = algorithmRepository; this.algorithmProblemRepository = algorithmProblemRepository; + this.refreshTokenService = refreshTokenService; } @Override @@ -82,6 +87,19 @@ public void run(String... args) throws Exception { .build(); userRepository.save(user4); + // Redis token 생성 + String refreshToken1 = refreshTokenService.createRefreshToken("chohana@alchive.com"); + refreshTokenService.saveRefreshToken("chohana@alchive.com", refreshToken1); + + String refreshToken2 = refreshTokenService.createRefreshToken("parknahyun@alchive.com"); + refreshTokenService.saveRefreshToken("parknahyun@alchive.com", refreshToken2); + + String refreshToken3 = refreshTokenService.createRefreshToken("songyurim@alchive.com"); + refreshTokenService.saveRefreshToken("songyurim@alchive.com", refreshToken3); + + String refreshToken4 = refreshTokenService.createRefreshToken("kimmiyoung@alchive.com"); + refreshTokenService.saveRefreshToken("kimmiyoung@alchive.com", refreshToken4); + // Algorithm 목업 데이터 생성 String[] algorithmNames = {"이분탐색", "DP", "BFS", "DFS", "브루트포스", "그리디", "정렬", "구현", "그래프"}; for (String name : algorithmNames) { From e8028245016de0668053fb1d5fc27eb2572e2ff1 Mon Sep 17 00:00:00 2001 From: nahowo Date: Sat, 5 Apr 2025 08:19:43 +0900 Subject: [PATCH 10/12] =?UTF-8?q?fix:=20User=20=ED=83=88=ED=87=B4=20?= =?UTF-8?q?=EC=8B=9C=20redis=20=EC=BA=90=EC=8B=9C=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Alchive/backend/config/redis/RefreshTokenService.java | 8 ++++++++ .../java/com/Alchive/backend/service/UserService.java | 1 + 2 files changed, 9 insertions(+) diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java index ff22da4..b1a67af 100644 --- a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenService.java @@ -4,6 +4,7 @@ import com.Alchive.backend.config.jwt.JwtTokenProvider; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -11,6 +12,7 @@ public class RefreshTokenService { private final RefreshTokenRepository refreshTokenRepository; private final JwtTokenProvider jwtTokenProvider; + @Transactional public void saveRefreshToken(String email, String refreshToken) { refreshTokenRepository.save(new RefreshToken(email, refreshToken)); } @@ -21,7 +23,13 @@ public String getRefreshToken(String email) { return refreshToken.getRefreshToken(); } + @Transactional public String createRefreshToken(String email) { return jwtTokenProvider.createRefreshToken(email); } + + @Transactional + public void deleteRefreshToken(String email) { + refreshTokenRepository.deleteById(email); + } } diff --git a/src/main/java/com/Alchive/backend/service/UserService.java b/src/main/java/com/Alchive/backend/service/UserService.java index b26471a..e52a92c 100644 --- a/src/main/java/com/Alchive/backend/service/UserService.java +++ b/src/main/java/com/Alchive/backend/service/UserService.java @@ -65,6 +65,7 @@ public User updateUserDetail(User user, UserUpdateRequest updateRequest) { @Transactional public void deleteUserDetail(User user) { userRepository.delete(user); + refreshTokenService.deleteRefreshToken(user.getEmail()); } public void validateUser(Long userId, Long requestedId) { From aba14dbcaeed1e9e6dddaec60649179142f4d798 Mon Sep 17 00:00:00 2001 From: nahowo Date: Sat, 5 Apr 2025 08:52:18 +0900 Subject: [PATCH 11/12] =?UTF-8?q?fix:=20=ED=86=A0=ED=81=B0=EC=97=90=20type?= =?UTF-8?q?=20=ED=95=84=EB=93=9C=20=EC=B6=94=EA=B0=80,=20access-refresh=20?= =?UTF-8?q?=EC=9A=94=EC=B2=AD=20=EC=8B=9C=20=EA=B5=AC=EB=B6=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/config/jwt/JwtController.java | 2 +- .../backend/config/jwt/JwtTokenProvider.java | 16 +++++++++++----- .../config/redis/RefreshTokenController.java | 6 +++--- .../backend/config/result/ResultCode.java | 6 ++++-- 4 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/Alchive/backend/config/jwt/JwtController.java b/src/main/java/com/Alchive/backend/config/jwt/JwtController.java index 4781e02..0489ee4 100644 --- a/src/main/java/com/Alchive/backend/config/jwt/JwtController.java +++ b/src/main/java/com/Alchive/backend/config/jwt/JwtController.java @@ -22,6 +22,6 @@ public class JwtController { @GetMapping("/access") public ResponseEntity createAccessToken(String email) { String accessToken = jwtTokenProvider.createAccessToken(email); - return ResponseEntity.ok(ResultResponse.of(TOKEN_ACCESS_SUCCESS, accessToken)); + return ResponseEntity.ok(ResultResponse.of(TOKEN_ACCESS_CREATE_SUCCESS, accessToken)); } } diff --git a/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java b/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java index 0def5d7..a527244 100644 --- a/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java +++ b/src/main/java/com/Alchive/backend/config/jwt/JwtTokenProvider.java @@ -35,15 +35,16 @@ protected void init() { // 액세스 및 리프레시 토큰 생성 public String createAccessToken(String email) { - return createToken(email, ACCESS_EXPIRE_LENGTH); + return createToken(email, ACCESS_EXPIRE_LENGTH, "ACCESS"); } public String createRefreshToken(String email) { - return createToken(email, REFRESH_EXPIRE_LENGTH); + return createToken(email, REFRESH_EXPIRE_LENGTH, "REFRESH"); } - private String createToken(String email, Long expireLength) { + private String createToken(String email, Long expireLength, String type) { Claims claims = Jwts.claims().setSubject(email); + claims.put("type", type); return Jwts.builder().setClaims(claims) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + expireLength)) @@ -74,10 +75,15 @@ private String resolveToken(HttpServletRequest request, String headerName, Strin // 토큰 검증 public boolean validateToken(String token) { try { - Jwts.parserBuilder() + Claims claims = Jwts.parserBuilder() .setSigningKey(secretKey) .build() - .parseClaimsJws(token); + .parseClaimsJws(token) + .getBody(); + String type = (String) claims.get("type"); + if (!type.equals("ACCESS")) { + throw new TokenNotExistsException(); + } return true; } catch (ExpiredJwtException e) { return false; diff --git a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java index e57cdab..cd1d920 100644 --- a/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java +++ b/src/main/java/com/Alchive/backend/config/redis/RefreshTokenController.java @@ -20,14 +20,14 @@ public class RefreshTokenController { @PostMapping("") public ResponseEntity saveRefreshToken(@RequestBody CreateRefreshTokenRequest request) { refreshTokenService.saveRefreshToken(request.getEmail(), request.getRefreshToken()); - return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, request.getRefreshToken())); + return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SAVE_SUCCESS, request.getRefreshToken())); } @Operation(summary = "리프레시 토큰 검색 메서드", description = "이메일로 리프레시 토큰을 가져오는 메서드입니다. ") @GetMapping("") public ResponseEntity getResfreshToken(@RequestParam String email) { String refreshToken = refreshTokenService.getRefreshToken(email); - return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, refreshToken)); + return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_GET_SUCCESS, refreshToken)); } @Operation(summary = "리프레시 토큰 재발급 메서드", description = "이메일로 리프레시 토큰을 재발급하는 메서드입니다. ") @@ -35,6 +35,6 @@ public ResponseEntity getResfreshToken(@RequestParam String emai public ResponseEntity createRefreshToken(@RequestParam String email) { String refreshToken = refreshTokenService.createRefreshToken(email); refreshTokenService.saveRefreshToken(email, refreshToken); - return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_SUCCESS, refreshToken)); + return ResponseEntity.ok(ResultResponse.of(TOKEN_REFRESH_CREATE_SUCCESS, refreshToken)); } } diff --git a/src/main/java/com/Alchive/backend/config/result/ResultCode.java b/src/main/java/com/Alchive/backend/config/result/ResultCode.java index f83581b..9a407a0 100644 --- a/src/main/java/com/Alchive/backend/config/result/ResultCode.java +++ b/src/main/java/com/Alchive/backend/config/result/ResultCode.java @@ -15,8 +15,10 @@ public enum ResultCode { USER_UPDATE_SUCCESS("U008", "회원 상세정보 수정 성공"), // AUTH - TOKEN_ACCESS_SUCCESS("A001", "액세스 토큰 생성 성공"), - TOKEN_REFRESH_SUCCESS("A002", "리프레쉬 토큰 생성 성공"), + TOKEN_ACCESS_CREATE_SUCCESS("A001", "액세스 토큰 생성 성공"), + TOKEN_REFRESH_CREATE_SUCCESS("A002", "리프레쉬 토큰 생성 성공"), + TOKEN_REFRESH_SAVE_SUCCESS("A003", "리프레쉬 토큰 저장 성공"), + TOKEN_REFRESH_GET_SUCCESS("A003", "리프레쉬 토큰 조회 성공"), // SOLUTION SOLUTION_CREATE_SUCCESS("S001", "풀이 생성 성공"), From 8d90b31aeff611427668a5628e1231331a6e64a4 Mon Sep 17 00:00:00 2001 From: nahowo Date: Mon, 14 Apr 2025 12:05:06 +0900 Subject: [PATCH 12/12] =?UTF-8?q?CD=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/cicd.yml | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index c915cdd..7f95a0c 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -51,16 +51,16 @@ jobs: docker tag ${{ secrets.DOCKERHUB_REPOSITORY }} ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${GITHUB_SHA::7} docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${GITHUB_SHA::7} - # Deploy - - name: Deploy - uses: appleboy/ssh-action@master - with: - host: 35.216.106.69 - username: ${{ secrets.SSH_USERNAME }} - key: ${{ secrets.SSH_PRIVATE_KEY }} - envs: GITHUB_SHA - script: | - sudo echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin - sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${GITHUB_SHA::7} - sudo docker tag ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${GITHUB_SHA::7} alchive_spring - sudo docker-compose up -d +# # Deploy +# - name: Deploy +# uses: appleboy/ssh-action@master +# with: +# host: 35.216.106.69 +# username: ${{ secrets.SSH_USERNAME }} +# key: ${{ secrets.SSH_PRIVATE_KEY }} +# envs: GITHUB_SHA +# script: | +# sudo echo ${{ secrets.DOCKERHUB_PASSWORD }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin +# sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${GITHUB_SHA::7} +# sudo docker tag ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPOSITORY }}:${GITHUB_SHA::7} alchive_spring +# sudo docker-compose up -d