-
Notifications
You must be signed in to change notification settings - Fork 0
Feat/jwt 필터 추가 #2
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
The head ref may contain hidden characters: "feat/jwt-\uD544\uD130-\uCD94\uAC00"
Conversation
- cors 관리를 스프링 시큐리티에서 수행하도록 변경
…Member-Id 헤더로 변환 기능 구현
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.
Pull request overview
This PR introduces JWT authentication to the gateway service, transforming it into a centralized authentication point for the microservices architecture. The implementation adds security filters, CORS configuration, and deployment manifests for Kubernetes (k3s) environments.
Key Changes:
- JWT authentication filter with RSA public key verification
- CORS configuration for cross-origin requests
- Gateway filter to extract member ID from JWT and forward as custom header
- Kubernetes deployment, service, and ingress configurations
- CI/CD pipelines for automated testing and deployment
Reviewed changes
Copilot reviewed 17 out of 18 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
src/main/java/me/pinitgateway/security/SecurityConfig.java |
Defines security filter chains with JWT authentication and public endpoint exceptions |
src/main/java/me/pinitgateway/security/JwtAuthenticationProvider.java |
Implements reactive authentication manager for JWT validation |
src/main/java/me/pinitgateway/security/CorsProperties.java |
Configuration properties class for CORS settings |
src/main/java/me/pinitgateway/jwt/JwtTokenProvider.java |
Handles JWT parsing, validation, and claims extraction using RSA public key |
src/main/java/me/pinitgateway/jwt/JwtAuthenticationToken.java |
Custom authentication token for JWT-based authentication |
src/main/java/me/pinitgateway/jwt/JwtAuthenticationFilter.java |
WebFlux filter to extract and authenticate JWT from Authorization header |
src/main/java/me/pinitgateway/jwt/RsaKeyProvider.java |
Utility to load RSA public key from PEM file |
src/main/java/me/pinitgateway/filter/JwtSubToMemberIdHeaderGatewayFilterFactory.java |
Gateway filter to forward member ID from JWT as X-Member-Id header to downstream services |
src/main/resources/application-prod.yml |
Production configuration with gateway routes and JWT filter registration |
src/main/resources/application-dev.yml |
Development configuration with local service routing |
src/test/resources/application.yml |
Test configuration with CORS and JWT public key path |
src/test/resources/jwt-public-key.pem |
RSA public key for test environment |
k8s/deployment.yaml |
Kubernetes deployment manifest with rolling update strategy and health probes |
k8s/service.yaml |
Kubernetes service definition for gateway |
k8s/ingress.yaml |
Updates ingress to route all traffic through gateway service |
.github/workflows/ci.yaml |
CI workflow for build and test on pull requests |
.github/workflows/cd.yml |
CD workflow for build, push, and deploy to k3s cluster |
build.gradle |
Adds JJWT dependencies for JWT handling |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| * 현재 필터 규칙 | ||
| * - /actuator/health/liveness : 인증 안함 | ||
| * - /actuator/health/readiness : 인증 안함 | ||
| * - /v3/** : 인증 안함 | ||
| * - /swagger-ui/** : 인증 안함 | ||
| * - /async-api/** : 인증 안함 | ||
| * - auth host + /login : 인증 안함 | ||
| * - auth host + /signup : 인증 안함 | ||
| * - auth host + /refresh : 인증 안함 | ||
| * - auth host + /login/** : 인증 안함 | ||
| * - 그 외 : 인증 필요 |
Copilot
AI
Dec 30, 2025
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.
The comment is in Korean while the code uses English. For consistency and international collaboration, code comments should be in English.
| * 현재 필터 규칙 | |
| * - /actuator/health/liveness : 인증 안함 | |
| * - /actuator/health/readiness : 인증 안함 | |
| * - /v3/** : 인증 안함 | |
| * - /swagger-ui/** : 인증 안함 | |
| * - /async-api/** : 인증 안함 | |
| * - auth host + /login : 인증 안함 | |
| * - auth host + /signup : 인증 안함 | |
| * - auth host + /refresh : 인증 안함 | |
| * - auth host + /login/** : 인증 안함 | |
| * - 그 외 : 인증 필요 | |
| * Current filter rules: | |
| * - /actuator/health/liveness : no authentication | |
| * - /actuator/health/readiness : no authentication | |
| * - /v3/** : no authentication | |
| * - /swagger-ui/** : no authentication | |
| * - /async-api/** : no authentication | |
| * - auth host + /login : no authentication | |
| * - auth host + /signup : no authentication | |
| * - auth host + /refresh : no authentication | |
| * - auth host + /login/** : no authentication | |
| * - all other requests : authentication required |
| defaultMode: 0444 | ||
| containers: | ||
| - name: app | ||
| image: ghcr.io/pinit-scheduler/pinit-gateway/app:${GITHUB_SHA} # GITHUB_SHA 환경변수는 GitHub Actions에서 설정되며 envsubst로 치환됩니다. |
Copilot
AI
Dec 30, 2025
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.
The comment is in Korean while the code uses English. For consistency, code comments should be in English.
| image: ghcr.io/pinit-scheduler/pinit-gateway/app:${GITHUB_SHA} # GITHUB_SHA 환경변수는 GitHub Actions에서 설정되며 envsubst로 치환됩니다. | |
| image: ghcr.io/pinit-scheduler/pinit-gateway/app:${GITHUB_SHA} # The GITHUB_SHA environment variable is set by GitHub Actions and substituted using envsubst. |
|
|
||
| private ServerWebExchange mutateHeader(ServerWebExchange exchange, String sub) { | ||
| var request = exchange.getRequest().mutate() | ||
| // 외부에서 X-Member-Id를 임의로 넣어오는 spoofing 방지 |
Copilot
AI
Dec 30, 2025
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.
The comment is in Korean while code and other comments use English. For consistency, code comments should be in English.
| } catch (Exception e) { | ||
| return false; | ||
| } |
Copilot
AI
Dec 30, 2025
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.
The token validation catches all exceptions and returns false, hiding potentially important error information. While this prevents information leakage, it makes debugging difficult. Consider logging the exception with appropriate detail (avoiding sensitive information) to help with troubleshooting authentication issues.
| .flatMap(auth -> { | ||
| String sub = extractSub(auth); | ||
|
|
||
| // JWT 검증은 SecurityWebFilterChain에서 이미 처리되었으므로, sub이 없으면 그대로 진행 |
Copilot
AI
Dec 30, 2025
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.
The comment is in Korean while code uses English. For consistency, code comments should be in English.
| - OPTIONS | ||
| allowed-headers: | ||
| - "*" | ||
| allow-credentials: true |
Copilot
AI
Dec 30, 2025
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.
The CORS configuration allows all origins with credentials enabled, which is a security vulnerability. When allow-credentials is true, allowed-origins cannot be "*" as this exposes the application to CSRF attacks from any origin. Either set specific origins for testing or set allow-credentials to false.
| allow-credentials: true | |
| allow-credentials: false |
| private Mono<Void> handleAuthenticationError(ServerWebExchange exchange) { | ||
| exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); | ||
| return exchange.getResponse().setComplete(); |
Copilot
AI
Dec 30, 2025
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.
The authentication error handler returns a generic 401 Unauthorized status without a response body. This provides no information about why authentication failed, making it difficult for API consumers to debug issues. Consider adding an error message body with details about the authentication failure.
| public boolean validateToken(String token) { | ||
| try { | ||
| Claims claims = parse(token); | ||
| return !claims.getExpiration().before(new Date()); |
Copilot
AI
Dec 30, 2025
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.
Missing null check for parsed claims before calling getExpiration(). If token parsing succeeds but the claims don't contain an expiration, this could throw a NullPointerException. The null check should be added or the exception should be explicitly handled.
| return !claims.getExpiration().before(new Date()); | |
| Date expiration = claims.getExpiration(); | |
| if (expiration == null) { | |
| return false; | |
| } | |
| return !expiration.before(new Date()); |
| @Override | ||
| public void setAuthenticated(boolean isAuthenticated) { | ||
| if (isAuthenticated) { | ||
| throw new IllegalArgumentException("인증 상태 설정은 생성자에서만 할 수 있습니다."); |
Copilot
AI
Dec 30, 2025
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.
The error message is in Korean while the rest of the codebase appears to use English. For consistency and international collaboration, error messages should be in English.
| throw new IllegalArgumentException("인증 상태 설정은 생성자에서만 할 수 있습니다."); | |
| throw new IllegalArgumentException("Authentication state can only be set in the constructor."); |
| } | ||
|
|
||
| public Long getMemberId(String token) { | ||
| return Long.parseLong(parse(token).getSubject()); |
Copilot
AI
Dec 30, 2025
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.
Potential uncaught 'java.lang.NumberFormatException'.
| return Long.parseLong(parse(token).getSubject()); | |
| String subject = parse(token).getSubject(); | |
| if (subject == null) { | |
| return null; | |
| } | |
| try { | |
| return Long.parseLong(subject); | |
| } catch (NumberFormatException e) { | |
| return null; | |
| } |
변경된 점