Skip to content

Commit 1e94d0c

Browse files
authored
Merge pull request #14 from 2026-capston-design/dev
release
2 parents dc776fd + 90931b7 commit 1e94d0c

File tree

19 files changed

+385
-221
lines changed

19 files changed

+385
-221
lines changed

.github/workflows/deploy.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@ jobs:
6060
KAKAO_CLIENT_ID=${{ secrets.KAKAO_CLIENT_ID }}
6161
KAKAO_REDIRECT_URI=${{secrets.KAKAO_REDIRECT_URI }}
6262
JWT_SECRET=${{ secrets.JWT_SECRET }}
63+
REDIS_HOST=${{ secrets.REDIS_HOST }}
64+
REDIS_PORT=${{ secrets.REDIS_PORT }}
6365
EOF
6466
6567
# EC2로 .env 업로드

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
3232
implementation 'org.springframework.boot:spring-boot-starter-security'
3333
implementation 'org.springframework.boot:spring-boot-starter-webflux'
34+
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
3435
testImplementation 'org.springframework.security:spring-security-test'
3536
compileOnly 'org.projectlombok:lombok'
3637
runtimeOnly 'com.mysql:mysql-connector-j'
Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
package backend.capstone;
22

3+
import io.swagger.v3.oas.annotations.Operation;
34
import org.springframework.web.bind.annotation.GetMapping;
45
import org.springframework.web.bind.annotation.RestController;
56

67
@RestController
78
public class TestController {
89

9-
@GetMapping("/test")
10-
public String test() {
11-
return "test";
12-
}
10+
@Operation(summary = "테스트용 엔드포인트",
11+
description = """
12+
서버가 정상적으로 작동하는지 확인하는 테스트용 엔드포인트입니다.
13+
헤더에 엑세스 토큰을 넣어서 요청했을 때 200 상태코드와 함께 "test"문자열이 정상적으로 반환되는지 확인해보세요.
14+
엑세스 토큰 만료 메시지를 받으면 재발급 API를 호출해주세요.
15+
""")
16+
@GetMapping("/test")
17+
public String test() {
18+
return "test";
19+
}
1320

1421
}

src/main/java/backend/capstone/auth/controller/AuthController.java

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,43 @@
22

33
import backend.capstone.auth.dto.KakaoLoginRequest;
44
import backend.capstone.auth.dto.LoginResponse;
5-
import backend.capstone.auth.jwt.service.JwtTokenProvider;
5+
import backend.capstone.auth.dto.TokenPair;
66
import backend.capstone.auth.service.AuthService;
77
import lombok.RequiredArgsConstructor;
88
import org.springframework.http.HttpStatus;
99
import org.springframework.web.bind.annotation.GetMapping;
1010
import org.springframework.web.bind.annotation.PostMapping;
1111
import org.springframework.web.bind.annotation.RequestBody;
12+
import org.springframework.web.bind.annotation.RequestHeader;
1213
import org.springframework.web.bind.annotation.RequestMapping;
1314
import org.springframework.web.bind.annotation.ResponseStatus;
1415
import org.springframework.web.bind.annotation.RestController;
1516

1617
@RestController
1718
@RequestMapping("/api/auth")
1819
@RequiredArgsConstructor
19-
public class AuthController {
20+
public class AuthController implements AuthControllerSpec {
2021

21-
private final AuthService authService;
22-
private final JwtTokenProvider jwtTokenProvider;
22+
private final AuthService authService;
2323

24-
@PostMapping("/login/kakao")
25-
@ResponseStatus(HttpStatus.CREATED)
26-
public LoginResponse kakaoLogin(@RequestBody KakaoLoginRequest request) {
27-
return authService.kakaoLogin(request.kakaoAccessToken());
28-
}
24+
@Override
25+
@PostMapping("/login/kakao")
26+
@ResponseStatus(HttpStatus.CREATED)
27+
public LoginResponse kakaoLogin(@RequestBody KakaoLoginRequest request) {
28+
return authService.kakaoLogin(request.kakaoAccessToken());
29+
}
2930

30-
@GetMapping("/test")
31-
public String issueTestJwt() {
32-
return jwtTokenProvider.createAccessToken(1L);
33-
}
31+
@Override
32+
@PostMapping("/refresh")
33+
@ResponseStatus(HttpStatus.CREATED)
34+
public TokenPair refresh(@RequestHeader(value = "X-Refresh-Token") String refreshToken) {
35+
return authService.refreshAccessToken(refreshToken);
36+
}
37+
38+
@Override
39+
@GetMapping("/test-issue")
40+
public TokenPair issueTestJwt() {
41+
return authService.testIssue();
42+
}
3443

3544
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package backend.capstone.auth.controller;
2+
3+
import backend.capstone.auth.dto.KakaoLoginRequest;
4+
import backend.capstone.auth.dto.LoginResponse;
5+
import backend.capstone.auth.dto.TokenPair;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.tags.Tag;
8+
9+
@Tag(name = "인증관련 API")
10+
public interface AuthControllerSpec {
11+
12+
@Operation(
13+
summary = "카카오 로그인",
14+
description = """
15+
카카오 엑세스 토큰을 받아서 카카오 유저를 조회하고 db에 존재하는 유저면 로그인, 존재하지 않는 유저면 회원가입 후 로그인합니다.
16+
응답받은 엑세스 토큰과 리프레시 토큰을 사용해주세요.
17+
"""
18+
)
19+
LoginResponse kakaoLogin(KakaoLoginRequest request);
20+
21+
@Operation(
22+
summary = "토큰 재발급",
23+
description = """
24+
API 응답으로 401 상태코드와 함께 다음과 같은 엑세스 토큰 만료 메시지를 받을 때 이 API를 호출합니다.
25+
```json
26+
{
27+
"code": "ACCESS_TOKEN_EXPIRED",
28+
"message": "만료된 액세스 토큰입니다."
29+
}
30+
```
31+
X-Refresh-Token 헤더에 리프레시 토큰을 넣어주세요. 엑세스토큰은 넣지 않아도 됩니다.
32+
이 API의 응답으로 받은 엑세스 토큰과 리프레시 토큰을 사용해주세요. (그전에 저장한 엑세스 토큰과 리프레시 토큰은 폐지)
33+
"""
34+
)
35+
TokenPair refresh(String refreshToken);
36+
37+
@Operation(
38+
summary = "테스트용 토큰 발급",
39+
description = """
40+
테스트 유저에 대한 엑세스 토큰과 리프레시 토큰을 발급합니다.
41+
리프레시 토큰을 활용한 토큰 재발급 또한 가능합니다.
42+
테스트용으로만 사용해주세요. (나중에 이 API는 지울 예정)
43+
"""
44+
)
45+
TokenPair issueTestJwt();
46+
}
Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
package backend.capstone.auth.dto;
22

33
public record LoginResponse(
4-
Long userId,
5-
String nickname,
6-
String profileImageUrl,
7-
String accessToken
4+
Long userId,
5+
String nickname,
6+
String profileImageUrl,
7+
String accessToken,
8+
String refreshToken
89
) {
910

1011
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package backend.capstone.auth.dto;
2+
3+
public record TokenPair(
4+
String accessToken,
5+
String refreshToken) {
6+
7+
}
Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package backend.capstone.auth.entrypoint;
22

3+
import backend.capstone.auth.exception.AuthErrorCode;
34
import com.fasterxml.jackson.databind.ObjectMapper;
4-
import jakarta.servlet.ServletException;
55
import jakarta.servlet.http.HttpServletRequest;
66
import jakarta.servlet.http.HttpServletResponse;
77
import java.io.IOException;
@@ -16,19 +16,24 @@
1616
@RequiredArgsConstructor
1717
public class ExAuthenticationEntryPoint implements AuthenticationEntryPoint {
1818

19-
private final ObjectMapper objectMapper;
19+
private final ObjectMapper objectMapper;
2020

21-
@Override
22-
public void commence(HttpServletRequest request, HttpServletResponse response,
23-
AuthenticationException authException) throws IOException, ServletException {
24-
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
25-
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
26-
response.setCharacterEncoding("UTF-8");
21+
@Override
22+
public void commence(HttpServletRequest request, HttpServletResponse response,
23+
AuthenticationException authException) throws IOException {
24+
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
25+
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
26+
response.setCharacterEncoding("UTF-8");
2727

28-
objectMapper.writeValue(response.getWriter(), Map.of(
29-
"error", "UNAUTHORIZED",
30-
"message", "로그인이 필요한 서비스입니다"
31-
));
28+
AuthErrorCode errorCode = (AuthErrorCode) request.getAttribute("AUTH_ERROR_CODE");
29+
if (errorCode == null) {
30+
errorCode = AuthErrorCode.UNKNOWN_ERROR;
31+
}
3232

33-
}
33+
objectMapper.writeValue(response.getWriter(), Map.of(
34+
"code", errorCode.name(),
35+
"message", errorCode.getMessage()
36+
));
37+
38+
}
3439
}

src/main/java/backend/capstone/auth/exception/AuthErrorCode.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,15 @@
99
@AllArgsConstructor
1010
public enum AuthErrorCode implements ErrorCode {
1111

12-
INVALID_KAKAO_ACCESS_TOKEN("유효하지 않은 카카오 엑세스 토큰입니다.", HttpStatus.UNAUTHORIZED),
13-
KAKAO_SERVER_ERROR("카카오 서버 에러가 발생하였습니다.", HttpStatus.INTERNAL_SERVER_ERROR);
12+
INVALID_KAKAO_ACCESS_TOKEN("유효하지 않은 카카오 엑세스 토큰입니다.", HttpStatus.UNAUTHORIZED),
13+
KAKAO_SERVER_ERROR("카카오 서버 에러가 발생하였습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
14+
HASH_NOT_SUPPORT("해시 알고리즘을 지원하지 않습니다.", HttpStatus.INTERNAL_SERVER_ERROR),
15+
ACCESS_TOKEN_EXPIRED("만료된 액세스 토큰입니다.", HttpStatus.UNAUTHORIZED),
16+
INVALID_ACCESS_TOKEN("유효하지 않은 액세스 토큰입니다.", HttpStatus.UNAUTHORIZED),
17+
MISSING_ACCESS_TOKEN("액세스 토큰이 존재하지 않습니다.", HttpStatus.UNAUTHORIZED),
18+
INVALID_REFRESH_TOKEN("유효하지 않은 리프레시 토큰입니다. 다시 로그인해주세요", HttpStatus.UNAUTHORIZED),
19+
UNKNOWN_ERROR("알 수 없는 오류가 발생했습니다.", HttpStatus.UNAUTHORIZED);
1420

15-
private final String message;
16-
private final HttpStatus status;
21+
private final String message;
22+
private final HttpStatus status;
1723
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package backend.capstone.auth.jwt;
2+
3+
public enum TokenStatus {
4+
VALID,
5+
EXPIRED,
6+
INVALID_SIGNATURE,
7+
UNSUPPORTED,
8+
MALFORMED,
9+
INVALID_TOKEN,
10+
MISSING_TOKEN
11+
}

0 commit comments

Comments
 (0)