diff --git a/src/main/java/KGUcapstone/OutDecision/domain/user/controller/TokenController.java b/src/main/java/KGUcapstone/OutDecision/domain/user/controller/TokenController.java index 3259f8b..d56fb1e 100644 --- a/src/main/java/KGUcapstone/OutDecision/domain/user/controller/TokenController.java +++ b/src/main/java/KGUcapstone/OutDecision/domain/user/controller/TokenController.java @@ -9,6 +9,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RestController; @@ -34,13 +35,14 @@ public ApiResponse logout(HttpServletResponse response) { return ApiResponse.onSuccess(null); } - @PostMapping("/token/refresh") + @GetMapping("/token/refresh") public ResponseEntity> refresh(HttpServletResponse response) { String accessToken = findMemberService.getTokenFromCookies(); - String newAccessToken = tokenService.republishAccessToken(accessToken); + System.out.println("accessToken = " + accessToken); + String newAccessToken = tokenService.republishAccessToken(accessToken, response); + System.out.println("newAccessToken = " + newAccessToken); if (StringUtils.hasText(newAccessToken)) { - // 클라이언트에게 응답할 때 쿠키를 변경한다. - addCookie(response, "Authorization", newAccessToken, 60*60); + System.out.println("변경 완"); return ResponseEntity.ok(ApiResponse.onSuccess(newAccessToken)); } diff --git a/src/main/java/KGUcapstone/OutDecision/domain/user/service/auth/TokenService.java b/src/main/java/KGUcapstone/OutDecision/domain/user/service/auth/TokenService.java index 42f07d0..b86dfc2 100644 --- a/src/main/java/KGUcapstone/OutDecision/domain/user/service/auth/TokenService.java +++ b/src/main/java/KGUcapstone/OutDecision/domain/user/service/auth/TokenService.java @@ -3,6 +3,7 @@ import KGUcapstone.OutDecision.domain.user.dto.RefreshToken; import KGUcapstone.OutDecision.domain.user.repository.TokenRepository; import KGUcapstone.OutDecision.global.common.util.JwtUtil; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -10,6 +11,8 @@ import java.util.Optional; +import static KGUcapstone.OutDecision.global.common.util.CookieUtil.addCookie; + @Slf4j @Service @RequiredArgsConstructor @@ -38,7 +41,7 @@ public void removeRefreshToken(String accessToken) { // AccessToken 재생성 @Transactional - public String republishAccessToken(String accessToken) { + public String republishAccessToken(String accessToken, HttpServletResponse response) { // 액세스 토큰으로 Refresh 토큰 객체를 조회 Optional refreshToken = tokenRepository.findByAccessToken(accessToken.replace("Bearer ", "")); diff --git a/src/main/java/KGUcapstone/OutDecision/global/common/util/CookieUtil.java b/src/main/java/KGUcapstone/OutDecision/global/common/util/CookieUtil.java index b85703b..761ded1 100644 --- a/src/main/java/KGUcapstone/OutDecision/global/common/util/CookieUtil.java +++ b/src/main/java/KGUcapstone/OutDecision/global/common/util/CookieUtil.java @@ -18,6 +18,7 @@ public static void addCookie(HttpServletResponse response, String name, String v .maxAge(maxAge) .build(); + System.out.println("cookie = " + cookie); response.addHeader("Set-Cookie", cookie.toString()); } diff --git a/src/main/java/KGUcapstone/OutDecision/global/common/util/JwtUtil.java b/src/main/java/KGUcapstone/OutDecision/global/common/util/JwtUtil.java index 34efeb5..6476026 100644 --- a/src/main/java/KGUcapstone/OutDecision/global/common/util/JwtUtil.java +++ b/src/main/java/KGUcapstone/OutDecision/global/common/util/JwtUtil.java @@ -66,8 +66,8 @@ public String generateRefreshToken(String email, String role) { public String generateAccessToken(String email, String role) { - long tokenPeriod = 1000L * 60L * 30L; // 30분 - +// long tokenPeriod = 1000L * 60L * 30L; // 30분 + long tokenPeriod = 1000L * 10L; Claims claims = Jwts.claims().setSubject(email); claims.put("role", role); diff --git a/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtAuthFilter.java b/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtAuthFilter.java index 20bbc89..8428eaf 100644 --- a/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtAuthFilter.java +++ b/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtAuthFilter.java @@ -2,8 +2,10 @@ import KGUcapstone.OutDecision.domain.user.domain.Member; import KGUcapstone.OutDecision.domain.user.service.FindMemberService; +import KGUcapstone.OutDecision.domain.user.service.auth.TokenService; import KGUcapstone.OutDecision.global.security.dto.SecurityUserDto; import KGUcapstone.OutDecision.global.common.util.JwtUtil; +import io.jsonwebtoken.ExpiredJwtException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -14,7 +16,6 @@ import org.springframework.security.core.Authentication; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; @@ -22,6 +23,8 @@ import java.io.IOException; import java.util.List; +import static KGUcapstone.OutDecision.global.common.util.CookieUtil.addCookie; + @RequiredArgsConstructor @Slf4j @Component @@ -29,45 +32,56 @@ public class JwtAuthFilter extends OncePerRequestFilter { private final JwtUtil jwtUtil; private final FindMemberService findMemberService; + private final TokenService tokenService; @Override - protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException{ + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { return request.getRequestURI().contains("/token/"); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - // request Header에서 AccessToken을 가져온다. + log.info("JwtAuthFilter is called for request URI: {}", request.getRequestURI()); String atc = findMemberService.getTokenFromCookies(); - // 토큰 검사 생략(모두 허용 URL의 경우 토큰 검사 통과) if (!StringUtils.hasText(atc)) { - doFilter(request, response, filterChain); + filterChain.doFilter(request, response); return; } - // AccessToken을 검증하고, 만료되었을경우 예외를 발생시킨다. - if (!jwtUtil.verifyToken(atc)) { - throw new JwtException("Access Token 만료!"); + boolean isTokenValid = jwtUtil.verifyToken(atc); + + if (!isTokenValid) { + log.info("토큰 만료 -> 재발급"); + String newAccessToken = tokenService.republishAccessToken(atc, response); + + if (newAccessToken != null) { + addCookie(response, "Authorization", newAccessToken, 60 * 60); + atc = newAccessToken; + log.info("토큰 발급 완료 필터 newAccessToken = {}", newAccessToken); + return; + } else { + log.error("새로운 토큰 발급 실패"); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "토큰 재발급 실패"); + return; + } } - // AccessToken의 값이 있고, 유효한 경우에 진행한다. + // 아래 코드는 AccessToken이 유효할 때만 실행됨 if (jwtUtil.verifyToken(atc)) { + Member findMember = findMemberService.findByEmail(jwtUtil.getUid(atc)).orElse(null); - // AccessToken 내부의 payload에 있는 email로 user를 조회한다. - Member findMember = findMemberService.findByEmail(jwtUtil.getUid(atc)).get(); - - // SecurityContext에 등록할 User 객체를 만들어준다. - SecurityUserDto userDto = SecurityUserDto.builder() - .memberId(findMember.getId()) - .email(findMember.getEmail()) - .role("ROLE_".concat(findMember.getUserRole())) - .nickname(findMember.getNickname()) - .build(); + if (findMember != null) { + SecurityUserDto userDto = SecurityUserDto.builder() + .memberId(findMember.getId()) + .email(findMember.getEmail()) + .role("ROLE_".concat(findMember.getUserRole())) + .nickname(findMember.getNickname()) + .build(); - // SecurityContext에 인증 객체를 등록해준다. - Authentication auth = getAuthentication(userDto); - SecurityContextHolder.getContext().setAuthentication(auth); + Authentication auth = getAuthentication(userDto); + SecurityContextHolder.getContext().setAuthentication(auth); + } } filterChain.doFilter(request, response); @@ -77,4 +91,4 @@ public Authentication getAuthentication(SecurityUserDto member) { return new UsernamePasswordAuthenticationToken(member, "", List.of(new SimpleGrantedAuthority(member.getRole()))); } -} \ No newline at end of file +} diff --git a/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtExceptionFilter.java b/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtExceptionFilter.java index ff5fa46..5ff732a 100644 --- a/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtExceptionFilter.java +++ b/src/main/java/KGUcapstone/OutDecision/global/security/filter/JwtExceptionFilter.java @@ -7,6 +7,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.http.MediaType; import org.springframework.security.oauth2.jwt.JwtException; import org.springframework.stereotype.Component; @@ -14,6 +15,7 @@ import java.io.IOException; +@Slf4j @RequiredArgsConstructor @Component public class JwtExceptionFilter extends OncePerRequestFilter { @@ -22,6 +24,8 @@ public class JwtExceptionFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + log.info("JwtExceptionFilter is called for request URI: {}", request.getRequestURI()); + try { filterChain.doFilter(request, response); } catch (JwtException e) { diff --git a/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomAuthenticationSuccessHandler.java b/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomAuthenticationSuccessHandler.java index dba0516..1bc4107 100644 --- a/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomAuthenticationSuccessHandler.java +++ b/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomAuthenticationSuccessHandler.java @@ -55,7 +55,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo log.info("jwtToken = {}", token.getAccessToken()); // 쿠키로 accessToken 전달 - addCookie(response, "accessToken", token.getAccessToken(), 60*60); + addCookie(response, "Authorization", token.getAccessToken(), 60*60); // 로그인 확인 페이지로 리다이렉트 시킨다. log.info("소셜 로그인 redirect 준비"); diff --git a/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomLoginSuccessHandler.java b/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomLoginSuccessHandler.java index d6b6587..d2bbb02 100644 --- a/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomLoginSuccessHandler.java +++ b/src/main/java/KGUcapstone/OutDecision/global/security/handler/CustomLoginSuccessHandler.java @@ -35,7 +35,7 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo log.info("jwtToken = {}", token.getAccessToken()); // 쿠키로 accessToken 전달 - addCookie(response, "Authorization", token.getAccessToken(), 60*60); + addCookie(response, "Authorization", token.getAccessToken(), 60*5); // 로그인 확인 페이지로 리다이렉트 시킨다. log.info("일반 로그인 redirect 준비");