From 82425cc215c2217e133f305944646fea58b5f6e7 Mon Sep 17 00:00:00 2001 From: TueBack Date: Sat, 20 Dec 2025 21:09:53 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[Refactor]=20Trip=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=9D=B8=EC=A6=9D=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C?= =?UTF-8?q?=ED=8E=B8:=20Spring=20Security=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20JWT=20=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----- + + From ca546dbdd19d43c01b333e7adbb5ebeeaba8a81e Mon Sep 17 00:00:00 2001 From: TueBack Date: Mon, 22 Dec 2025 16:56:34 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[Refactor]=20Trip=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=9D=B8=EC=A6=9D=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C?= =?UTF-8?q?=ED=8E=B8:=20Spring=20Security=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20JWT=20=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 --- .../filter/JwtAuthenticationFilter.java | 272 +++++++++--------- .../trip/infra/config/SecurityConfig.java | 33 --- 2 files changed, 136 insertions(+), 169 deletions(-) diff --git a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/JwtAuthenticationFilter.java b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/JwtAuthenticationFilter.java index 6919595..5451098 100644 --- a/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/retrip/trip/infra/adapter/in/presentation/filter/JwtAuthenticationFilter.java @@ -1,136 +1,136 @@ -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.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.context.SecurityContextHolder; -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.Collections; -import java.util.List; -import java.util.UUID; - -@Slf4j -@Component -public class JwtAuthenticationFilter 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(); - - if (isExcludedPath(path)) { - filterChain.doFilter(request, response); - return; - } - - String token = resolveToken(request); - - if (token != null && validateToken(token)) { - try { - - Claims claims = getClaims(token); - - - String subject = claims.getSubject(); - UUID memberId = UUID.fromString(subject); - - - String role = claims.get("auth", String.class); - - 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); - - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( - memberId, - null, - role != null ? List.of(new SimpleGrantedAuthority(role)) : Collections.emptyList() - ); - SecurityContextHolder.getContext().setAuthentication(authentication); - - } catch (Exception e) { - log.error("Token processing error: {}", e.getMessage()); - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - return; - } - } else { - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - return; - } - - filterChain.doFilter(request, response); - } - - private boolean isExcludedPath(String path) { - String pathLowercase = path.toLowerCase(); - return path.equals("/") || - pathLowercase.contains("swagger") || - pathLowercase.contains("api-docs") || - pathLowercase.contains("actuator") || - pathLowercase.contains("robots.txt") || - pathLowercase.contains("status-check"); - } - - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (bearerToken != null && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); - } - return null; - } - - private boolean validateToken(String token) { - try { - getClaims(token); - return true; - } catch (Exception e) { - return false; - } - } - - 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 +//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.security.authentication.UsernamePasswordAuthenticationToken; +//import org.springframework.security.core.authority.SimpleGrantedAuthority; +//import org.springframework.security.core.context.SecurityContextHolder; +//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.Collections; +//import java.util.List; +//import java.util.UUID; +// +//@Slf4j +//@Component +//public class JwtAuthenticationFilter 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(); +// +// if (isExcludedPath(path)) { +// filterChain.doFilter(request, response); +// return; +// } +// +// String token = resolveToken(request); +// +// if (token != null && validateToken(token)) { +// try { +// +// Claims claims = getClaims(token); +// +// +// String subject = claims.getSubject(); +// UUID memberId = UUID.fromString(subject); +// +// +// String role = claims.get("auth", String.class); +// +// 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); +// +// UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( +// memberId, +// null, +// role != null ? List.of(new SimpleGrantedAuthority(role)) : Collections.emptyList() +// ); +// SecurityContextHolder.getContext().setAuthentication(authentication); +// +// } catch (Exception e) { +// log.error("Token processing error: {}", e.getMessage()); +// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); +// return; +// } +// } else { +// response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); +// return; +// } +// +// filterChain.doFilter(request, response); +// } +// +// private boolean isExcludedPath(String path) { +// String pathLowercase = path.toLowerCase(); +// return path.equals("/") || +// pathLowercase.contains("swagger") || +// pathLowercase.contains("api-docs") || +// pathLowercase.contains("actuator") || +// pathLowercase.contains("robots.txt") || +// pathLowercase.contains("status-check"); +// } +// +// private String resolveToken(HttpServletRequest request) { +// String bearerToken = request.getHeader("Authorization"); +// if (bearerToken != null && bearerToken.startsWith("Bearer ")) { +// return bearerToken.substring(7); +// } +// return null; +// } +// +// private boolean validateToken(String token) { +// try { +// getClaims(token); +// return true; +// } catch (Exception e) { +// return false; +// } +// } +// +// 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/java/com/retrip/trip/infra/config/SecurityConfig.java b/src/main/java/com/retrip/trip/infra/config/SecurityConfig.java index 38d0276..5fddc7b 100644 --- a/src/main/java/com/retrip/trip/infra/config/SecurityConfig.java +++ b/src/main/java/com/retrip/trip/infra/config/SecurityConfig.java @@ -1,46 +1,13 @@ package com.retrip.trip.infra.config; -import com.retrip.trip.infra.adapter.in.presentation.filter.JwtAuthenticationFilter; -import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration -@EnableWebSecurity -@RequiredArgsConstructor public class SecurityConfig { - - private final JwtAuthenticationFilter jwtAuthenticationFilter; - @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } - - @Bean - public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) - .formLogin(AbstractHttpConfigurer::disable) - .httpBasic(AbstractHttpConfigurer::disable) - .sessionManagement(session -> - session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> auth - - .requestMatchers( - "/", "/swagger-ui/**", "/v3/api-docs/**", "/actuator/**", "/status-check" - ).permitAll() - .anyRequest().authenticated() - ) - .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); - - return http.build(); - } } \ No newline at end of file