-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/#11 #12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Feat/#11 #12
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,63 @@ | ||
| name: Deploy Auth to ECS | ||
|
|
||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| workflow_dispatch: | ||
|
|
||
| env: | ||
| AWS_REGION: ap-northeast-2 | ||
| AWS_ACCOUNT_ID: 727452759104 | ||
| ECR_REPOSITORY: momentlit/auth | ||
| ECS_CLUSTER: default | ||
| ECS_SERVICE: momentlit-auth-service | ||
| IMAGE_TAG: latest | ||
|
|
||
| jobs: | ||
| deploy: | ||
| name: Build and Deploy Auth | ||
| runs-on: ubuntu-latest | ||
|
Comment on lines
+17
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win GitHub 토큰 권한을 읽기 전용으로 제한하세요.
수정 예시 on:
push:
branches:
- main
workflow_dispatch:
+permissions:
+ contents: read
+
env:🤖 Prompt for AI AgentsSource: Linters/SAST tools |
||
|
|
||
| steps: | ||
| - name: Checkout source code | ||
| uses: actions/checkout@v4 | ||
|
|
||
| - name: Configure AWS credentials | ||
| uses: aws-actions/configure-aws-credentials@v5 | ||
| with: | ||
| aws-region: ${{ env.AWS_REGION }} | ||
| aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} | ||
| aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} | ||
|
|
||
| - name: Login to Amazon ECR | ||
| uses: aws-actions/amazon-ecr-login@v2 | ||
|
|
||
| - name: Set up Docker Buildx | ||
| uses: docker/setup-buildx-action@v3 | ||
|
|
||
| - name: Build and push Docker image | ||
| run: | | ||
| IMAGE_URI=${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/${ECR_REPOSITORY}:${IMAGE_TAG} | ||
|
|
||
| docker buildx build \ | ||
| --platform linux/amd64 \ | ||
| --provenance=false \ | ||
| -t $IMAGE_URI \ | ||
| . \ | ||
| --push | ||
|
|
||
| - name: Force new ECS deployment | ||
| run: | | ||
| aws ecs update-service \ | ||
| --cluster $ECS_CLUSTER \ | ||
| --service $ECS_SERVICE \ | ||
| --force-new-deployment \ | ||
| --region $AWS_REGION | ||
|
|
||
| - name: Wait for ECS service stable | ||
| run: | | ||
| aws ecs wait services-stable \ | ||
| --cluster $ECS_CLUSTER \ | ||
| --services $ECS_SERVICE \ | ||
| --region $AWS_REGION | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,8 +7,140 @@ servers: | |
| - url: "Needs confirmation" | ||
| description: "Needs confirmation" | ||
| tags: [] | ||
| paths: {} | ||
| paths: | ||
| /auth/oauth/naver: | ||
| get: | ||
| summary: "Redirect to Naver OAuth" | ||
| parameters: | ||
| - name: state | ||
| in: query | ||
| required: false | ||
| schema: | ||
| type: string | ||
| responses: | ||
| "302": | ||
| description: "Redirects to Naver authorization endpoint." | ||
| /auth/oauth/naver/callback: | ||
| get: | ||
| summary: "Handle Naver OAuth callback" | ||
| parameters: | ||
| - name: code | ||
| in: query | ||
| required: true | ||
| schema: | ||
| type: string | ||
| - name: state | ||
| in: query | ||
| required: false | ||
| schema: | ||
| type: string | ||
| responses: | ||
| "200": | ||
| description: "JWT tokens returned after Naver login." | ||
| "400": | ||
| $ref: "#/components/responses/BadRequestError" | ||
| "502": | ||
| $ref: "#/components/responses/NaverOauthError" | ||
| /auth/oauth/kakao: | ||
| get: | ||
| summary: "Redirect to Kakao OAuth" | ||
| parameters: | ||
| - name: state | ||
| in: query | ||
| required: false | ||
| schema: | ||
| type: string | ||
| responses: | ||
| "302": | ||
| description: "Redirects to Kakao authorization endpoint." | ||
| /auth/oauth/kakao/callback: | ||
| get: | ||
| summary: "Handle Kakao OAuth callback" | ||
| parameters: | ||
| - name: code | ||
| in: query | ||
| required: true | ||
| schema: | ||
| type: string | ||
| - name: state | ||
| in: query | ||
| required: false | ||
| schema: | ||
| type: string | ||
| responses: | ||
| "200": | ||
| description: "JWT tokens returned after Kakao login." | ||
| "400": | ||
| $ref: "#/components/responses/BadRequestError" | ||
| "502": | ||
| $ref: "#/components/responses/KakaoOauthError" | ||
|
Comment on lines
+20
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win OAuth 성공·리다이렉트 응답 계약을 명시하세요. 구현은 시작 엔드포인트에서 As per coding guidelines, 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| components: | ||
| schemas: {} | ||
| responses: {} | ||
| schemas: | ||
| ErrorResponse: | ||
| type: object | ||
| properties: | ||
| message: | ||
| type: string | ||
| example: "[ERROR: Request/BadRequest] Refresh Token을 입력해주세요." | ||
| data: | ||
| nullable: true | ||
| example: null | ||
| required: | ||
| - message | ||
| - data | ||
|
Comment on lines
+79
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 오류 응답에
As per coding guidelines, API error responses should have a consistent format with 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| responses: | ||
| BadRequestError: | ||
| description: "Invalid request input." | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/ErrorResponse" | ||
| example: | ||
| message: "[ERROR: Request/BadRequest] Refresh Token을 입력해주세요." | ||
| data: null | ||
| UnauthorizedError: | ||
| description: "Authentication failed or token is invalid." | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/ErrorResponse" | ||
| example: | ||
| message: "[ERROR: Auth/Unauthorized] 유효하지 않은 Refresh Token입니다." | ||
| data: null | ||
| GoogleOauthError: | ||
| description: "Google OAuth request failed." | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/ErrorResponse" | ||
| example: | ||
| message: "[ERROR: Auth/Oauth/Google] Google Access Token을 발급받을 수 없습니다." | ||
| data: null | ||
| NaverOauthError: | ||
| description: "Naver OAuth request failed." | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/ErrorResponse" | ||
| example: | ||
| message: "[ERROR: Auth/Oauth/Naver] Naver Access Token을 발급받을 수 없습니다." | ||
| data: null | ||
| KakaoOauthError: | ||
| description: "Kakao OAuth request failed." | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/ErrorResponse" | ||
| example: | ||
| message: "[ERROR: Auth/Oauth/Kakao] Kakao Access Token을 발급받을 수 없습니다." | ||
| data: null | ||
| InternalServerError: | ||
| description: "Unexpected server error." | ||
| content: | ||
| application/json: | ||
| schema: | ||
| $ref: "#/components/schemas/ErrorResponse" | ||
| example: | ||
| message: "[ERROR: ?/?] 서버 내부 오류가 발생했습니다." | ||
| data: null | ||
| securitySchemes: {} | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -43,7 +43,8 @@ No repository or persistence component is visible. Build dependencies include Sp | |
|
|
||
| ## Exception Handling | ||
|
|
||
| No project-specific exception handling is visible. | ||
| Project-specific exception handling is implemented under `com.example.auth.global.exception`. | ||
| Auth-specific exceptions extend `AuthException` and are handled by `GlobalExceptionHandler`. | ||
|
Comment on lines
+46
to
+47
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win 서비스 개요의 나머지 오래된 설명도 함께 갱신하세요. 같은 문서가 controller, service, DTO, HTTP API, 인증 동작이 없다고 설명해 현재 OAuth 구현과 모순됩니다. 패키지 구조·주요 API·OAuth 인증·테스트 현황 섹션도 현재 상태에 맞추세요. 🤖 Prompt for AI Agents |
||
|
|
||
| ## Test Structure | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -31,7 +31,15 @@ State transitions are documented only where visible in entity or service methods | |
|
|
||
| ## Exception Cases | ||
|
|
||
| No project-specific exception handling is visible. HTTP status mapping for these exceptions is Needs confirmation unless explicitly handled in code. | ||
| Project-specific exception handling is visible under `com.example.auth.global.exception`. | ||
| Visible mappings: | ||
|
|
||
| - `BadRequestException`: `400 Bad Request` | ||
| - `UnauthorizedException`: `401 Unauthorized` | ||
| - `TokenNotFoundException`: `401 Unauthorized` | ||
| - `GoogleOauthException`: `502 Bad Gateway` | ||
| - Other `AuthException`: `500 Internal Server Error` | ||
|
Comment on lines
+40
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win Naver·Kakao OAuth 오류 상태 코드를 명시적으로 정렬하세요.
As per coding guidelines, 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| - Other `Exception`: `500 Internal Server Error` | ||
|
|
||
| ## API Behavior Policy | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -57,6 +57,42 @@ public ResponseEntity<ApiResponse<OauthGoogleCallbackResponse>> googleOauthCallb | |
| return ResponseEntity.ok(ResponseUtil.success("Google 로그인에 성공했습니다.", response)); | ||
| } | ||
|
|
||
| @GetMapping("/oauth/naver") | ||
| public ResponseEntity<Void> naverOauth(@RequestParam(required = false) String state) { | ||
| URI redirectUri = authService.createNaverAuthorizationUri(state); | ||
| return ResponseEntity.status(HttpStatus.FOUND) | ||
| .location(redirectUri) | ||
| .build(); | ||
| } | ||
|
|
||
| @GetMapping("/oauth/naver/callback") | ||
| public ResponseEntity<ApiResponse<OauthGoogleCallbackResponse>> naverOauthCallback( | ||
| @RequestParam String code, | ||
| @RequestParam(required = false) String state | ||
| ) { | ||
| OauthGoogleCallbackResponse response = | ||
| authService.loginWithNaver(code, state); | ||
| return ResponseEntity.ok(ResponseUtil.success("Naver 로그인에 성공했습니다.", response)); | ||
| } | ||
|
Comment on lines
+60
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift OAuth 현재 Also applies to: 78-94 🤖 Prompt for AI Agents |
||
|
|
||
| @GetMapping("/oauth/kakao") | ||
| public ResponseEntity<Void> kakaoOauth(@RequestParam(required = false) String state) { | ||
| URI redirectUri = authService.createKakaoAuthorizationUri(state); | ||
| return ResponseEntity.status(HttpStatus.FOUND) | ||
| .location(redirectUri) | ||
| .build(); | ||
| } | ||
|
|
||
| @GetMapping("/oauth/kakao/callback") | ||
| public ResponseEntity<ApiResponse<OauthGoogleCallbackResponse>> kakaoOauthCallback( | ||
| @RequestParam String code, | ||
| @RequestParam(required = false) String state | ||
| ) { | ||
| OauthGoogleCallbackResponse response = | ||
| authService.loginWithKakao(code, state); | ||
| return ResponseEntity.ok(ResponseUtil.success("Kakao 로그인에 성공했습니다.", response)); | ||
| } | ||
|
|
||
| @PostMapping("/refresh") | ||
| public ResponseEntity<ApiResponse<RefreshResponse>> refresh(@RequestBody RefreshRequest request) { | ||
| // Refresh Token이 유효하면 새로운 토큰 묶음을 발급합니다. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| package com.example.auth.global.client; | ||
|
|
||
| import com.example.auth.global.client.dto.response.KakaoTokenResponse; | ||
| import com.example.auth.global.client.dto.response.KakaoUserInfoResponse; | ||
| import java.net.URI; | ||
| import org.springframework.beans.factory.annotation.Value; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.stereotype.Component; | ||
| import org.springframework.util.LinkedMultiValueMap; | ||
| import org.springframework.util.MultiValueMap; | ||
| import org.springframework.util.StringUtils; | ||
| import org.springframework.web.client.RestClient; | ||
| import org.springframework.web.util.UriComponentsBuilder; | ||
|
|
||
| @Component | ||
| public class KakaoOauthClient { | ||
|
|
||
| private final RestClient restClient; | ||
| private final String clientId; | ||
| private final String clientSecret; | ||
| private final String redirectUri; | ||
| private final String authorizationUri; | ||
| private final String tokenUri; | ||
| private final String userInfoUri; | ||
| private final String scope; | ||
|
|
||
| public KakaoOauthClient( | ||
| @Value("${oauth.kakao.client-id}") String clientId, | ||
| @Value("${oauth.kakao.client-secret}") String clientSecret, | ||
| @Value("${oauth.kakao.redirect-uri}") String redirectUri, | ||
| @Value("${oauth.kakao.authorization-uri}") String authorizationUri, | ||
| @Value("${oauth.kakao.token-uri}") String tokenUri, | ||
| @Value("${oauth.kakao.user-info-uri}") String userInfoUri, | ||
| @Value("${oauth.kakao.scope}") String scope | ||
| ) { | ||
| this.restClient = RestClient.create(); | ||
| this.clientId = clientId; | ||
| this.clientSecret = clientSecret; | ||
| this.redirectUri = redirectUri; | ||
| this.authorizationUri = authorizationUri; | ||
| this.tokenUri = tokenUri; | ||
| this.userInfoUri = userInfoUri; | ||
| this.scope = scope; | ||
| } | ||
|
|
||
| public URI createAuthorizationUri(String state) { | ||
| UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(authorizationUri) | ||
| .queryParam("response_type", "code") | ||
| .queryParam("client_id", clientId) | ||
| .queryParam("redirect_uri", redirectUri); | ||
|
|
||
| if (StringUtils.hasText(scope)) { | ||
| builder.queryParam("scope", scope); | ||
| } | ||
|
|
||
| if (StringUtils.hasText(state)) { | ||
| builder.queryParam("state", state); | ||
| } | ||
|
|
||
| return builder.build() | ||
| .encode() | ||
| .toUri(); | ||
| } | ||
|
|
||
| public KakaoTokenResponse requestToken(String code) { | ||
| MultiValueMap<String, String> body = new LinkedMultiValueMap<>(); | ||
| body.add("grant_type", "authorization_code"); | ||
| body.add("client_id", clientId); | ||
| body.add("redirect_uri", redirectUri); | ||
| body.add("code", code); | ||
|
|
||
| if (StringUtils.hasText(clientSecret)) { | ||
| body.add("client_secret", clientSecret); | ||
| } | ||
|
|
||
| return restClient.post() | ||
| .uri(tokenUri) | ||
| .contentType(MediaType.APPLICATION_FORM_URLENCODED) | ||
| .body(body) | ||
| .retrieve() | ||
| .body(KakaoTokenResponse.class); | ||
| } | ||
|
|
||
| public KakaoUserInfoResponse requestUserInfo(String accessToken) { | ||
| return restClient.get() | ||
| .uri(userInfoUri) | ||
| .headers(headers -> headers.setBearerAuth(accessToken)) | ||
| .retrieve() | ||
| .body(KakaoUserInfoResponse.class); | ||
|
Comment on lines
+76
to
+89
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== locate target file =="
fd -a 'KakaoOauthClient.java|GlobalExceptionHandler.java|application.*\.(yml|yaml|properties)' . || true
echo "== target file outline =="
ast-grep outline src/main/java/com/example/auth/global/client/KakaoOauthClient.java --view expanded || true
echo "== target file relevant lines =="
cat -n src/main/java/com/example/auth/global/client/KakaoOauthClient.java | sed -n '1,140p'
echo "== search exception classes and global handler =="
rg -n "class KakaoOauthException|KakaoOauthException|RestClientException|GlobalExceptionHandler|ResponseEntity|\n.*Exception" src/main/java -S || true
echo "== search RestClient setup =="
rg -n "RequestHeaderExchangeFilterFunction|DefaultClientRequestAuthenticator|KakaoOauthClient|restClient|`@Component`|`@Service`" src/main/java/com/example -S || trueRepository: MomentLit/Auth Length of output: 8229 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== global exception handler =="
cat -n src/main/java/com/example/auth/global/exception/GlobalExceptionHandler.java | sed -n '1,180p'
echo "== exception-related files =="
fd -a '.*(Exception|Error).*\.java$' src/main/java || true
for f in $(fd '.*(Exception|Error).*\.java$' src/main/java || true); do
echo "---- $f ----"
cat -n "$f" | sed -n '1,140p'
done
echo "== auth service relevant lines =="
cat -n src/main/java/com/example/auth/service/AuthService.java | sed -n '1,180p'
echo "== build files / dependencies =="
fd 'build.gradle|settings.gradle|pom.xml' . -x sh -c 'echo "---- $1 ----"; sed -n "1,220p" "$1"' sh {}Repository: MomentLit/Auth Length of output: 25098 🌐 Web query:
💡 Result: In the Spring Framework, the RestClient class handles HTTP error responses (status codes 4xx and 5xx) by throwing a RestClientException or one of its subclasses by default [1][2][3]. Specifically, if no custom status handler is defined, the client throws a RestClientResponseException when receiving a response with a status code of 400 or higher [4][5]. To customize this behavior, you can use the onStatus method on the ResponseSpec interface to define specific handlers for different HTTP status codes [1][6]. Key points for handling status errors: Default Behavior: By default, RestClient raises a RestClientException (specifically RestClientResponseException) for 4xx and 5xx status codes [2][5][7]. Customizing Handlers: You can override the default behavior by registering a status handler using the.onStatus(Predicate, ErrorHandler) method [4][8]. Predicate and Handler: The onStatus method accepts a predicate to match the status code and a handler function that defines how to process the request and response, often by throwing a custom exception [1][6]. Example of custom error handling: restClient.get.uri("https://example.com/api/resource").retrieve.onStatus(HttpStatusCode::is4xxClientError, (request, response) -> { throw new MyCustomException("Client error occurred: " + response.getStatusCode); }).body(String.class); Global Configuration: If you want to apply custom error handling to all requests, you can configure it at the builder level using.defaultStatusHandler(HttpStatusCode::isError, (request, response) ->...) when creating the RestClient instance [1][2]. RestClientResponseException provides access to the raw status code, headers, and the response body, allowing you to extract detailed information if you choose to catch the default exception rather than defining a custom handler [7]. Citations:
Kakao OAuth 콜백 시 Kakao API 실패를 502로 반환하도록 예외를 변환하세요.
🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
불변 이미지로 배포하고 새 task definition revision을 지정하세요.
latest를 덮어쓴 뒤 같은 task definition을 강제 재배포하면, 동시에 실행된 배포에서 다른 커밋의 이미지를 가져오거나 롤백 대상이 사라질 수 있습니다. 커밋 SHA 또는 ECR digest로 태그하고, 해당 URI를 반영한 task definition revision을 등록한 뒤 서비스에 지정하세요.Also applies to: 39-56
🤖 Prompt for AI Agents