Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 13 additions & 13 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,16 @@ dependencies {
implementation 'io.micrometer:micrometer-registry-prometheus'

// Slack API
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'
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")
implementation 'com.slack.api:slack-api-client:1.44.1'

// Discord API
implementation 'net.dv8tion:JDA:5.0.0-beta.5'

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

tasks.named('test') {
Expand Down
36 changes: 23 additions & 13 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,30 @@ services:
networks:
- alchive

springboot:
container_name: springboot
restart: always
depends_on:
- mysql
redis:
image: redis:latest
container_name: redis
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
networks:
- alchive
- "6379:6379"
volumes:
- alchive-db:/alchive-db/redis

container_name: springboot
build:
context: .
dockerfile: Dockerfile
restart: always
depends_on:
- mysql
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: ${SPRING_DATASOURCE_URL}
SPRING_DATASOURCE_USERNAME: ${SPRING_DATASOURCE_USERNAME}
SPRING_DATASOURCE_PASSWORD: ${SPRING_DATASOURCE_PASSWORD}
SPRING_PROFILES_ACTIVE: ${SPRING_PROFILES_ACTIVE}
networks:
- alchive

# # Monitoring
# prometheus:
Expand Down
20 changes: 19 additions & 1 deletion src/main/java/com/Alchive/backend/config/DataInitializer.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -82,6 +87,19 @@ public void run(String... args) throws Exception {
.build();
userRepository.save(user4);

// Redis token 생성
String refreshToken1 = refreshTokenService.createRefreshToken("[email protected]");
refreshTokenService.saveRefreshToken("[email protected]", refreshToken1);

String refreshToken2 = refreshTokenService.createRefreshToken("[email protected]");
refreshTokenService.saveRefreshToken("[email protected]", refreshToken2);

String refreshToken3 = refreshTokenService.createRefreshToken("[email protected]");
refreshTokenService.saveRefreshToken("[email protected]", refreshToken3);

String refreshToken4 = refreshTokenService.createRefreshToken("[email protected]");
refreshTokenService.saveRefreshToken("[email protected]", refreshToken4);

// Algorithm 목업 데이터 생성
String[] algorithmNames = {"이분탐색", "DP", "BFS", "DFS", "브루트포스", "그리디", "정렬", "구현", "그래프"};
for (String name : algorithmNames) {
Expand Down
7 changes: 1 addition & 6 deletions src/main/java/com/Alchive/backend/config/SwaggerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand All @@ -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 ->
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -22,6 +23,7 @@
public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final UserRepository userRepository;
private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenService refreshTokenService;

// 검증 완료된 유저의 정보를 가져와서 토큰 생성, 로그인/회원가입 요청에 맞게 리다이렉트
@Override
Expand All @@ -37,11 +39,11 @@ 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 { // 회원가입인 경우
targetUrl = UriComponentsBuilder.fromUriString("http://localhost:5173/sign")
.queryParam("email", email)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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();


Expand All @@ -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과 메서드에 일치할 경우 현재 필터를 진행하지 않고 다음 필터 진행
Expand All @@ -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();
}
}

Expand Down
10 changes: 5 additions & 5 deletions src/main/java/com/Alchive/backend/config/jwt/JwtController.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,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
Expand All @@ -19,10 +19,10 @@
public class JwtController {
private final JwtTokenProvider jwtTokenProvider;

@Operation(summary = "토큰 재발급 메서드", description = "액세스 토큰을 재발급하는 메서드입니다.")
@GetMapping("")
public ResponseEntity<ResultResponse> createToken(String email) {
@Operation(summary = "액세스 토큰 재발급 메서드", description = "액세스 토큰을 재발급하는 메서드입니다.")
@GetMapping("/access")
public ResponseEntity<ResultResponse> 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));
}
}
Loading