From 970050a1762ee25806283ebf10a8ebaefebbb4f4 Mon Sep 17 00:00:00 2001 From: Fluteen Date: Tue, 2 Sep 2025 16:42:35 +0900 Subject: [PATCH 1/2] =?UTF-8?q?chore:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20=ED=95=84=ED=84=B0=20url=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EB=B0=8F=20=EB=A6=AC=ED=94=84=EB=A0=88=EC=8B=9C=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=20=EA=B0=92=20=EC=9D=BC=EC=A0=95=20=EB=AC=B8=EC=A0=9C?= =?UTF-8?q?=20=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../global/config/SecurityConfig.java | 5 +- .../security/JwtAuthenticationFilter.java | 100 +++++++++++++++--- .../keunsoriserver/global/util/TokenUtil.java | 2 - 3 files changed, 87 insertions(+), 20 deletions(-) diff --git a/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java b/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java index 300d1920..7e4a8193 100644 --- a/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java +++ b/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java @@ -44,9 +44,12 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationFilte .authorizeHttpRequests(auth -> auth // 인증 없이 로그인,회원가입은 가능. .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/auth/**", "/email/**").permitAll() + .requestMatchers("/auth/login", "/email/**").permitAll() .requestMatchers("/signup").permitAll() + // 로그아웃은 로그인 이후 가능 + .requestMatchers("members/me/logout").hasAnyAuthority("일반", "관리자") + // 회원 관련된 건 일반 권한 필요 .requestMatchers("/members/**").hasAuthority("일반") diff --git a/src/main/java/com/keunsori/keunsoriserver/global/security/JwtAuthenticationFilter.java b/src/main/java/com/keunsori/keunsoriserver/global/security/JwtAuthenticationFilter.java index 80f9f68a..2c8c8c9f 100644 --- a/src/main/java/com/keunsori/keunsoriserver/global/security/JwtAuthenticationFilter.java +++ b/src/main/java/com/keunsori/keunsoriserver/global/security/JwtAuthenticationFilter.java @@ -4,6 +4,7 @@ import com.keunsori.keunsoriserver.domain.member.domain.Member; import com.keunsori.keunsoriserver.domain.member.repository.MemberRepository; import com.keunsori.keunsoriserver.global.exception.AuthException; +import com.keunsori.keunsoriserver.global.properties.JwtProperties; import com.keunsori.keunsoriserver.global.util.TokenUtil; import com.keunsori.keunsoriserver.global.util.CookieUtil; import jakarta.servlet.FilterChain; @@ -32,8 +33,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected boolean shouldNotFilter(HttpServletRequest request) { + // 프리플라이트는 통과 + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return true; + } String path = request.getServletPath(); - return path.startsWith("/auth/login"); + return path.equals("/auth/login") || path.startsWith("/signup") || path.startsWith("/email/") + || path.startsWith("/swagger-ui/") || path.startsWith("/v3/api-docs/"); } @Override @@ -42,27 +48,64 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse String accessToken = CookieUtil.getCookieValue(request, "Access-Token"); String refreshToken = CookieUtil.getCookieValue(request, "Refresh-Token"); - if (accessToken == null) { - String authHeader = request.getHeader("Authorization"); - if (authHeader != null && authHeader.startsWith("Bearer ")) { - accessToken = authHeader.substring(7); // "Bearer " 뒷부분만 추출 - } + // Swagger 전용 + String bearerAccessToken = null; + String authHeader = request.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + bearerAccessToken = authHeader.substring(7); } + boolean authenticated = false; + + // Access Token 검사 + if (accessToken != null){ + try { + tokenUtil.validateToken(accessToken); + + String studentId = tokenUtil.getStudentIdFromToken(accessToken); + String status = tokenUtil.getStatusFromToken(accessToken); + + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken( + studentId, + null, + List.of(new SimpleGrantedAuthority(status)) + ) + ); + authenticated = true; + + // Refresh-Token 만료 시 재발급 + boolean neednewRefreshToken = (refreshToken == null); + + if (!neednewRefreshToken) { + try { + tokenUtil.validateToken(refreshToken); + } catch (AuthException e) { + neednewRefreshToken = true; + } + } + + if (neednewRefreshToken) { + Member member = memberRepository.findByStudentIdIgnoreCase(studentId) + .orElseThrow(() -> new AuthException(STUDENT_ID_NOT_EXISTS)); + String newRefreshToken = tokenUtil.generateRefreshToken(member.getStudentId(), member.getName(), member.getStatus()); + refreshTokenRepository.saveRefreshToken(member.getStudentId(), newRefreshToken, JwtProperties.REFRESH_TOKEN_VALIDITY_TIME); + CookieUtil.addRefreshTokenCookie(response,newRefreshToken); + } + } catch (AuthException ignored) { + // Access-Token 존재 X -> Refresh-Token 검사로 넘어감 + } + } - if (accessToken != null) { - tokenUtil.validateToken(accessToken); - - String studentId = tokenUtil.getStudentIdFromToken(accessToken); - String status = tokenUtil.getStatusFromToken(accessToken); + // Refresh Token 검사 + if (!authenticated && refreshToken != null) { + tokenUtil.validateToken(refreshToken); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(studentId, null, List.of(() -> status)); - SecurityContextHolder.getContext().setAuthentication(authentication); - } else if (refreshToken != null) { String studentId = tokenUtil.getStudentIdFromToken(refreshToken); String storedRefreshToken = refreshTokenRepository.getRefreshToken(studentId); - if (storedRefreshToken == null || !storedRefreshToken.equals(refreshToken)) { + CookieUtil.deleteCookie(response, "Access-Token"); + CookieUtil.deleteCookie(response, "Refresh-Token"); throw new AuthException(INVALID_REFRESH_TOKEN); } @@ -70,10 +113,33 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse .orElseThrow(() -> new AuthException(STUDENT_ID_NOT_EXISTS)); String newAccessToken = tokenUtil.generateAccessToken(member.getStudentId(), member.getName(), member.getStatus()); + CookieUtil.addAccessTokenCookie(response, newAccessToken); - UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(studentId, null, List.of(new SimpleGrantedAuthority(member.getStatus().name()))); - SecurityContextHolder.getContext().setAuthentication(authentication); + SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken( + studentId, + null, + List.of(new SimpleGrantedAuthority(member.getStatus().name())) + ) + ); + authenticated = true; + } + + // Swagger 전용 다시 시도 + if (!authenticated && bearerAccessToken != null) { + tokenUtil.validateToken(bearerAccessToken); + + String studentId = tokenUtil.getStudentIdFromToken(bearerAccessToken); + String status = tokenUtil.getStatusFromToken(bearerAccessToken); + + SecurityContextHolder.getContext().setAuthentication( + new UsernamePasswordAuthenticationToken( + studentId, + null, + List.of(new SimpleGrantedAuthority(status)) + ) + ); + authenticated = true; } chain.doFilter(request, response); diff --git a/src/main/java/com/keunsori/keunsoriserver/global/util/TokenUtil.java b/src/main/java/com/keunsori/keunsoriserver/global/util/TokenUtil.java index d210ea12..3a782b14 100644 --- a/src/main/java/com/keunsori/keunsoriserver/global/util/TokenUtil.java +++ b/src/main/java/com/keunsori/keunsoriserver/global/util/TokenUtil.java @@ -1,7 +1,6 @@ package com.keunsori.keunsoriserver.global.util; import com.keunsori.keunsoriserver.domain.member.domain.vo.MemberStatus; -import com.keunsori.keunsoriserver.global.constant.TokenConstant; import com.keunsori.keunsoriserver.global.exception.AuthException; import com.keunsori.keunsoriserver.global.properties.JwtProperties; import io.jsonwebtoken.Claims; @@ -9,7 +8,6 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.Date; From 12082e33c543a9b11abcfdbba3fa0bf04ac200cb Mon Sep 17 00:00:00 2001 From: Fluteen Date: Mon, 8 Sep 2025 11:25:53 +0900 Subject: [PATCH 2/2] =?UTF-8?q?permitAll()=20url=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=A1=9C=EA=B7=B8=EC=95=84=EC=9B=83=20url=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../keunsoriserver/global/config/SecurityConfig.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java b/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java index 7e4a8193..274086dd 100644 --- a/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java +++ b/src/main/java/com/keunsori/keunsoriserver/global/config/SecurityConfig.java @@ -44,11 +44,12 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthenticationFilte .authorizeHttpRequests(auth -> auth // 인증 없이 로그인,회원가입은 가능. .requestMatchers("/swagger-ui/**", "/v3/api-docs/**").permitAll() - .requestMatchers("/auth/login", "/email/**").permitAll() + .requestMatchers("/auth/login", "/auth/password/update-link/send", + "/auth/password", "/email/**").permitAll() .requestMatchers("/signup").permitAll() // 로그아웃은 로그인 이후 가능 - .requestMatchers("members/me/logout").hasAnyAuthority("일반", "관리자") + .requestMatchers("/auth/logout").hasAnyAuthority("일반", "관리자") // 회원 관련된 건 일반 권한 필요 .requestMatchers("/members/**").hasAuthority("일반")