From 82425cc215c2217e133f305944646fea58b5f6e7 Mon Sep 17 00:00:00 2001 From: TueBack Date: Sat, 20 Dec 2025 21:09:53 +0900 Subject: [PATCH] =?UTF-8?q?[Refactor]=20Trip=20=EC=84=9C=EB=B9=84=EC=8A=A4?= =?UTF-8?q?=20=EC=9D=B8=EC=A6=9D=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=ED=8E=B8?= =?UTF-8?q?:=20Spring=20Security=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F=20JWT=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EA=B2=BD=EB=9F=89=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 4 + .../filter/AuthenticationFilter.java | 78 +++++++++++++++++-- src/main/resources/application.yml | 15 +++- 3 files changed, 89 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index 1cf2573..fbccb3b 100644 --- a/build.gradle +++ b/build.gradle @@ -40,6 +40,10 @@ dependencies { implementation 'com.querydsl:querydsl-jpa:5.1.0:jakarta' annotationProcessor 'com.querydsl:querydsl-apt:5.1.0:jakarta' annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' } tasks.named('test') { diff --git a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/AuthenticationFilter.java b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/AuthenticationFilter.java index 778c411..97e0cb3 100644 --- a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/AuthenticationFilter.java +++ b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/AuthenticationFilter.java @@ -1,41 +1,105 @@ package com.retrip.trip.infra.adapter.in.presentation.filter; import com.retrip.trip.application.in.request.context.UserContext; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jws; +import io.jsonwebtoken.Jwts; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.util.Base64; +import java.util.UUID; +@Slf4j @Component public class AuthenticationFilter extends OncePerRequestFilter { + @Value("${jwt.public-key}") + private String publicKeyString; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String path = request.getRequestURI(); String pathLowercase = path.toLowerCase(); - // 제외할 URL 체크 if (path.equals("/") || pathLowercase.contains("swagger") || pathLowercase.contains("api-docs") || pathLowercase.contains("actuator") || pathLowercase.contains("robots.txt") || - pathLowercase.contains("status-check")) { + pathLowercase.contains("status-check") || + pathLowercase.contains("/h2-console")) { filterChain.doFilter(request, response); - return; // 필터 종료 + return; } - String token = request.getHeader("Authorization"); + String token = resolveToken(request); if (token == null || token.isEmpty()) { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); return; } - request.setAttribute("userContext", UserContext.mockOf()); - filterChain.doFilter(request, response); + + try { + Claims claims = getClaims(token); + + String subject = claims.getSubject(); + UUID memberId = UUID.fromString(subject); + + UserContext userContext = new UserContext( + memberId, + claims.get("nickname", String.class), + claims.get("email", String.class), + claims.get("name", String.class), + null, + 0 + ); + + request.setAttribute("userContext", userContext); + + filterChain.doFilter(request, response); + + } catch (Exception e) { + log.error("Token validation failed: {}", e.getMessage()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + } + } + + private String resolveToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (bearerToken != null && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; + } + + private Claims getClaims(String token) throws Exception { + String sanitizedKey = publicKeyString + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replaceAll("\\s", ""); + + byte[] publicBytes = Base64.getDecoder().decode(sanitizedKey); + X509EncodedKeySpec keySpec = new X509EncodedKeySpec(publicBytes); + KeyFactory keyFactory = KeyFactory.getInstance("RSA"); + PublicKey publicKey = keyFactory.generatePublic(keySpec); + + Jws jws = Jwts.parser() + .verifyWith(publicKey) + .build() + .parseSignedClaims(token); + + return jws.getPayload(); } -} +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 607313c..6348ec8 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,7 +4,7 @@ spring: datasource: driver-class-name: org.h2.Driver - url: jdbc:h2:tcp://localhost/~/trip;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL + url: jdbc:h2:mem:trip;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=false;MODE=MySQL username: sa password: @@ -30,3 +30,16 @@ springdoc: enabled: true api-docs: enabled: true + +jwt: + public-key: | + -----BEGIN PUBLIC KEY----- + MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlfO/CoX9EzhskpAKX9ADH0wfEQZP4rAq + ptqq80W2YaOHAnXu+oU1UrP0b9ccKKSzMCVDwdmXrecZB0dFLPnoazMEbVOf6MSwrhGfxupPRmmJ + sIYgmQwo8/vnjaq/GYFfnHyCy6yKL41G+GZVqgeKdhr+w1jUw4L9Fs0l2J/AYqwTxZOnzxrU5erP + GSE5Dd3AwWt/brxuwA7sRfVS3mbbsYyYExjUrEbst8VtF3Pis35T8YfSDKMOUgiDnp30EAdGU1Up + u59J3+ToLRrIqIszRZqmasrWTL2/ihPO76PSTIMAsJMScjAwjUXA47YOOy6Vkzy8r3bPTYHp5C1N + KucWwwIDAQAB + -----END PUBLIC KEY----- + +