From f3f8588e2293d698a7d345c320386891e655bbe3 Mon Sep 17 00:00:00 2001 From: yeseul106 <20191037@sungshin.ac.kr> Date: Mon, 22 Jan 2024 21:01:23 +0900 Subject: [PATCH] =?UTF-8?q?[FIX]=20=ED=86=A0=ED=81=B0=20=EC=9E=AC=EB=B0=9C?= =?UTF-8?q?=EA=B8=89=20API=20=EC=97=90=EB=9F=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/Impl/AuthServiceImpl.java | 4 +- .../member/repository/MemberRepository.java | 8 +- .../jwt/JwtAuthenticationEntryPoint.java | 13 +++- .../config/jwt/JwtAuthenticationFilter.java | 75 +++++++++++++------ .../common/config/jwt/JwtTokenProvider.java | 22 ++++-- 5 files changed, 87 insertions(+), 35 deletions(-) diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java index 18b4ff2..647c533 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/auth/service/Impl/AuthServiceImpl.java @@ -69,9 +69,7 @@ public AuthResponseDto socialLogin(AuthRequestDto authRequestDto) throws NoSuchA // socialId를 통해서 등록된 유저 찾기 Member signedMember = memberRepository.findMemberBySocialIdOrThrow(socialData.getId()); - Authentication authentication = new UserAuthentication(signedMember.getId(), null, null); - - String accessToken = jwtTokenProvider.generateAccessToken(authentication); + String accessToken = jwtTokenProvider.generateAccessToken(signedMember.getId()); return AuthResponseDto.of(signedMember.getNickname(), signedMember.getId(), accessToken, signedMember.getRefreshToken()); diff --git a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java index 8c63c37..b2ad88f 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java +++ b/growthookServer/src/main/java/com/example/growthookserver/api/member/repository/MemberRepository.java @@ -3,6 +3,7 @@ import com.example.growthookserver.api.member.domain.Member; import com.example.growthookserver.common.exception.BadRequestException; import com.example.growthookserver.common.exception.NotFoundException; +import com.example.growthookserver.common.exception.UnAuthorizedException; import com.example.growthookserver.common.response.ErrorStatus; import org.springframework.data.jpa.repository.JpaRepository; @@ -12,7 +13,7 @@ public interface MemberRepository extends JpaRepository { Optional findMemberById(Long id); boolean existsBySocialId(String socialId); - Optional findByIdAndRefreshToken(Long memberId, String refreshToken); + Optional findByRefreshToken(String refreshToken); Optional findBySocialId(String socialId); @@ -25,4 +26,9 @@ default Member findMemberBySocialIdOrThrow(String socialId) { return findBySocialId(socialId) .orElseThrow(() -> new BadRequestException(ErrorStatus.INVALID_MEMBER.getMessage())); } + + default Member findByRefreshTokenOrThrow(String refreshToken) { + return findByRefreshToken(refreshToken) + .orElseThrow(() -> new UnAuthorizedException(ErrorStatus.INVALID_MEMBER.getMessage())); + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java index 01869f6..fa0ee8b 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationEntryPoint.java @@ -1,8 +1,12 @@ package com.example.growthookserver.common.config.jwt; +import com.example.growthookserver.common.response.ApiResponse; +import com.example.growthookserver.common.response.ErrorStatus; +import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; +import org.springframework.http.HttpStatus; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.stereotype.Component; @@ -10,16 +14,21 @@ @Component public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { + private final ObjectMapper mapper = new ObjectMapper(); + @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException { - setResponse(response); + setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.UNAUTHORIZED_TOKEN); } - public void setResponse(HttpServletResponse response) throws IOException { + public void setResponse(HttpServletResponse response, HttpStatus statusCode, ErrorStatus status) throws IOException { response.setContentType("application/json;charset=UTF-8"); response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + + ApiResponse apiResponse = ApiResponse.fail(statusCode.value(), status.getMessage()); + response.getWriter().println(mapper.writeValueAsString(apiResponse)); } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java index b8beb7f..217c0a8 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtAuthenticationFilter.java @@ -1,9 +1,8 @@ package com.example.growthookserver.common.config.jwt; - -import static com.example.growthookserver.common.config.jwt.JwtExceptionType.VALID_JWT_TOKEN; - +import com.example.growthookserver.common.exception.UnAuthorizedException; import com.example.growthookserver.common.response.ErrorStatus; +import io.jsonwebtoken.Claims; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -11,9 +10,9 @@ import java.io.IOException; import lombok.RequiredArgsConstructor; -import org.springframework.security.core.AuthenticationException; +import org.springframework.http.HttpStatus; +import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; @@ -24,28 +23,60 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint; + private static final String ISSUE_TOKEN_API_URL = "/api/v1/auth/token"; + @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { - String accessToken = jwtTokenProvider.resolveToken(request); - - if (accessToken != null) { - // 토큰 검증 - if (jwtTokenProvider.validateToken(accessToken) - == VALID_JWT_TOKEN) { // 토큰이 존재하고 유효한 토큰일 때만 - Integer userId = jwtTokenProvider.getAccessTokenPayload(accessToken); - UserAuthentication authentication = new UserAuthentication(userId, null, - null); //사용자 객체 생성 - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails( - request)); // request 정보로 사용자 객체 디테일 설정 - SecurityContextHolder.getContext().setAuthentication(authentication); - } else { - jwtAuthenticationEntryPoint.commence(request, response, - new AuthenticationException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()) { - }); - return; + try { + String accessToken = jwtTokenProvider.resolveToken(request); + if (ISSUE_TOKEN_API_URL.equals(request.getRequestURI())) { + String refreshToken = jwtTokenProvider.resolveRefreshToken(request); + + if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.EMPTY_JWT || jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.EMPTY_JWT) { + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.BAD_REQUEST, ErrorStatus.NO_TOKEN); + return; + } else if (jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.EXPIRED_JWT_TOKEN) { + if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.EXPIRED_JWT_TOKEN) { + // access, refresh 둘 다 만료 + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.SIGNIN_REQUIRED); + return; + } else if (jwtTokenProvider.validateToken(refreshToken) == JwtExceptionType.VALID_JWT_TOKEN) { + // 토큰 재발급 + Long memberId = jwtTokenProvider.validateMemberRefreshToken(refreshToken); + + String newAccessToken = jwtTokenProvider.generateAccessToken(memberId); + + setAuthentication(newAccessToken); + request.setAttribute("newAccessToken", newAccessToken); + } + } else if (jwtTokenProvider.validateToken(accessToken) == JwtExceptionType.VALID_JWT_TOKEN) { + jwtAuthenticationEntryPoint.setResponse(response, HttpStatus.UNAUTHORIZED, ErrorStatus.VALID_ACCESS_TOKEN); + return; + } else { + throw new UnAuthorizedException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()); + } } + else { + JwtExceptionType jwtException = jwtTokenProvider.validateToken(accessToken); + + if (accessToken != null) { + // 토큰 검증 + if (jwtException == JwtExceptionType.VALID_JWT_TOKEN) { + setAuthentication(accessToken); + } + } + } + } catch (Exception e) { + throw new UnAuthorizedException(ErrorStatus.UNAUTHORIZED_TOKEN.getMessage()); } + chain.doFilter(request, response); } + + private void setAuthentication(String token) { + Claims claims = jwtTokenProvider.getAccessTokenPayload(token); + Authentication authentication = new UserAuthentication(Long.valueOf(String.valueOf(claims.get("id"))), null, null); + SecurityContextHolder.getContext().setAuthentication(authentication); + } } diff --git a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java index 7c3d867..42f5490 100644 --- a/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java +++ b/growthookServer/src/main/java/com/example/growthookserver/common/config/jwt/JwtTokenProvider.java @@ -1,5 +1,7 @@ package com.example.growthookserver.common.config.jwt; +import com.example.growthookserver.api.member.domain.Member; +import com.example.growthookserver.api.member.repository.MemberRepository; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Header; @@ -23,6 +25,8 @@ @RequiredArgsConstructor public class JwtTokenProvider { + private final MemberRepository memberRepository; + @Value("${jwt.secret}") private String secretKey; @@ -33,9 +37,9 @@ public class JwtTokenProvider { private Long refreshTokenExpireLength; private static final String AUTHORIZATION_HEADER = "Authorization"; - private static final String REFRESH_AUTHORIZATION_HEADER = "Refresh"; + private static final String REFRESH_AUTHORIZATION_HEADER = "refreshToken"; - public String generateAccessToken(Authentication authentication) { + public String generateAccessToken(Long memberId) { Date now = new Date(); Date expiration = new Date(now.getTime() + accessTokenExpireLength); @@ -43,7 +47,7 @@ public String generateAccessToken(Authentication authentication) { .setIssuedAt(now) .setExpiration(expiration); - claims.put("id", authentication.getPrincipal()); + claims.put("id", memberId); return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) @@ -63,10 +67,9 @@ public String generateRefreshToken() { .compact(); } - public Integer getAccessTokenPayload(String token) { - return Integer.parseInt( - Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) - .getBody().get("id").toString()); + public Claims getAccessTokenPayload(String token) { + return Jwts.parserBuilder().setSigningKey(getSignKey()).build().parseClaimsJws(token) + .getBody(); } public String resolveToken(HttpServletRequest request) { @@ -117,4 +120,9 @@ private Key getSignKey() { byte[] keyBytes = secretKey.getBytes(StandardCharsets.UTF_8); return new SecretKeySpec(keyBytes, "HmacSHA256"); } + + public Long validateMemberRefreshToken(String refreshToken) { + Member member = memberRepository.findByRefreshTokenOrThrow(refreshToken); + return member.getId(); + } }