Skip to content

Commit 6f30cb0

Browse files
committed
refactor: JWT 로직 수정
1 parent 4f7243a commit 6f30cb0

File tree

4 files changed

+56
-31
lines changed

4 files changed

+56
-31
lines changed
Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
11
package com.financedoc.gateway_service.config;
22

3+
import com.financedoc.gateway_service.jwt.JwtFilter;
4+
import com.financedoc.gateway_service.jwt.JwtUtil;
5+
import lombok.RequiredArgsConstructor;
36
import org.springframework.context.annotation.Bean;
47
import org.springframework.context.annotation.Configuration;
8+
import org.springframework.core.annotation.Order;
59
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
10+
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
611
import org.springframework.security.config.web.server.ServerHttpSecurity;
712
import org.springframework.security.web.server.SecurityWebFilterChain;
813

914
@Configuration
1015
@EnableWebFluxSecurity
16+
@RequiredArgsConstructor
1117
public class SecurityConfig {
18+
19+
private final JwtFilter jwtFilter;
20+
21+
@Order(0)
1222
@Bean
1323
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
14-
return http
24+
http
1525
.csrf(ServerHttpSecurity.CsrfSpec::disable) // CSRF 비활성화
1626
.authorizeExchange(exchanges -> exchanges
17-
.anyExchange().permitAll() // 모든 요청 허용
27+
// 인증이 필요없는 경로
28+
.pathMatchers("/actuator/health", "/error", "/swagger-ui/**", "/v3/api-docs/**",
29+
"/test-token", "/user/auth/kakao", "/user/auth/login", "/user/auth/refresh"
30+
).permitAll()
31+
// 그외 필터에서 검증 후 X-User-Id 헤더가 없으면 차단
32+
.anyExchange().authenticated()
1833
)
19-
.build();
34+
.addFilterAt(jwtFilter, SecurityWebFiltersOrder.AUTHENTICATION) // JWT 검증 필터 적용
35+
.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
36+
.formLogin(ServerHttpSecurity.FormLoginSpec::disable);
37+
38+
return http.build();
2039
}
2140
}

src/main/java/com/financedoc/gateway_service/jwt/JwtFilter.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,24 @@
66
import io.jsonwebtoken.UnsupportedJwtException;
77
import lombok.RequiredArgsConstructor;
88
import lombok.extern.slf4j.Slf4j;
9-
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
10-
import org.springframework.cloud.gateway.filter.GlobalFilter;
11-
import org.springframework.core.Ordered;
129
import org.springframework.http.HttpMethod;
1310
import org.springframework.http.HttpStatus;
1411
import org.springframework.http.server.reactive.ServerHttpRequest;
1512
import org.springframework.stereotype.Component;
1613
import org.springframework.web.server.ServerWebExchange;
14+
import org.springframework.web.server.WebFilter;
15+
import org.springframework.web.server.WebFilterChain;
1716
import reactor.core.publisher.Mono;
1817

1918
@Component
2019
@RequiredArgsConstructor
2120
@Slf4j
22-
public class JwtFilter implements GlobalFilter, Ordered {
21+
public class JwtFilter implements WebFilter {
2322

2423
private final JwtUtil jwtUtil;
2524

2625
@Override
27-
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
26+
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
2827

2928
// CORS 프리플라이트는 바로 통과
3029
HttpMethod method = exchange.getRequest().getMethod();
@@ -36,6 +35,17 @@ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
3635
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
3736
log.info("[JwtFilter] 요청 path={}, Authorization={}", path, authHeader);
3837

38+
// 화이트리스트 경로 예외 처리
39+
if (path.startsWith("/actuator/health") ||
40+
path.startsWith("/test-token") ||
41+
path.startsWith("/user/auth/") ||
42+
path.startsWith("/swagger-ui") ||
43+
path.startsWith("/v3/api-docs") ||
44+
path.startsWith("/error")) {
45+
log.info("[JwtFilter] 통과 path={}", path);
46+
return chain.filter(exchange);
47+
}
48+
3949
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
4050
log.warn("[JwtFilter] Authorization 헤더 없음 또는 Bearer 형식 아님");
4151
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
@@ -75,9 +85,4 @@ public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
7585
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
7686
return exchange.getResponse().setComplete();
7787
}
78-
79-
@Override
80-
public int getOrder() {
81-
return -1; // 먼저 실행
82-
}
8388
}

src/main/java/com/financedoc/gateway_service/jwt/JwtUtil.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111

1212
import javax.crypto.SecretKey;
1313
import java.nio.charset.StandardCharsets;
14+
import java.util.Base64;
1415
import java.util.Date;
16+
import java.util.HashMap;
1517
import java.util.Map;
1618

1719
@Component
@@ -32,34 +34,34 @@ public void init() {
3234
}
3335

3436
private String ensureBase64(String value) {
35-
boolean looksBase64 = value.matches("^[A-Za-z0-9+/=]+$");
36-
if (!looksBase64) {
37-
return java.util.Base64.getEncoder().encodeToString(value.getBytes(java.nio.charset.StandardCharsets.UTF_8));
38-
}
39-
return value;
37+
if (value == null) return "";
38+
boolean looksBase64 = value.matches("^[A-Za-z0-9+/=]+={0,2}$");
39+
return looksBase64 ? value
40+
: Base64.getEncoder().encodeToString(value.getBytes(StandardCharsets.UTF_8));
4041
}
4142

42-
public String createAccessToken(String subject, Map<String, Object> claims) {
43+
public String createAccessToken(String userId, Map<String, Object> extraClaims) {
44+
Map<String, Object> claims = (extraClaims == null) ? new HashMap<>() : new HashMap<>(extraClaims);
45+
claims.putIfAbsent("user_id", userId);
46+
claims.put("category", "access");
47+
4348
Date now = new Date();
44-
Date expiry = new Date(now.getTime() + accessTokenValidityMs);
45-
return buildToken(subject, claims, now, expiry);
46-
}
49+
Date exp = new Date(now.getTime() + accessTokenValidityMs);
4750

48-
private String buildToken(String subject, Map<String, Object> claims, Date issuedAt, Date expiry) {
4951
return Jwts.builder()
5052
.setClaims(claims)
51-
.setSubject(subject)
52-
.setIssuedAt(issuedAt)
53-
.setExpiration(expiry)
53+
.setSubject(userId)
54+
.setIssuedAt(now)
55+
.setExpiration(exp)
5456
.signWith(secretKey)
5557
.compact();
5658
}
5759

5860
public Claims parseClaims(String token) {
59-
return Jwts.parser() // parserBuilder()가 아니라 parser()
60-
.verifyWith(secretKey) // setSigningKey(...) 대신 verifyWith(Key)
61+
return Jwts.parser() // parserBuilder() 아님
62+
.verifyWith(secretKey) // setSigningKey(...) 대신 verifyWith(Key)
6163
.build()
62-
.parseSignedClaims(token) // ← parseClaimsJws(...) → parseSignedClaims(...)
64+
.parseSignedClaims(token)
6365
.getPayload();
6466
}
6567
}

src/main/resources/application.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ server:
44
spring:
55
application:
66
name: gateway-service
7-
87
cloud:
98
gateway:
109
server:
@@ -38,5 +37,5 @@ management:
3837

3938
jwt:
4039
secret: ${JWT_SECRET}
41-
access-token-validity: 3600000 # 엑세스 토큰 만료 시간
40+
access-token-validity: 180000 # 3분(3 * 60 * 1000)
4241
refresh-token-validity: 120960000 # 리프레시 토큰 만료 시간

0 commit comments

Comments
 (0)