From 5bdf659c025d9827db85f605d98187ed92f4abcf Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 24 May 2024 21:33:50 +0900 Subject: [PATCH 01/17] =?UTF-8?q?[Feat]=20RefreshToken=20=EB=8F=84?= =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=EA=B5=AC=ED=98=84(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/jwt/auth/RefreshToken.java | 34 +++++++++++++++++++ .../common/jwt/auth/redis/Token.java | 29 ---------------- .../repository/RefreshTokenRepository.java | 9 +++++ .../redis/repository/TokenRepository.java | 11 ------ .../service/MemberService.java | 4 +-- 5 files changed, 44 insertions(+), 43 deletions(-) create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java delete mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/Token.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RefreshTokenRepository.java delete mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/TokenRepository.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java new file mode 100644 index 0000000..32fa3b1 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java @@ -0,0 +1,34 @@ +package org.sopt.springFirstSeminar.common.jwt.auth; + + +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import org.springframework.data.redis.core.RedisHash; +import org.springframework.data.redis.core.index.Indexed; + +@RedisHash(value = "", timeToLive = 60 * 60 * 24 * 1000L) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Getter +@Builder(access = AccessLevel.PRIVATE) +public class RefreshToken { + + @Id + private Long id; + + @Indexed + private String refreshToken; + + public static RefreshToken of(final Long userId, final String refreshToken) { + return RefreshToken.builder() + .id(userId) + .refreshToken(refreshToken) + .build(); + } + + public void updateRefreshToken(final String refreshToken) { + this.refreshToken = refreshToken; + } +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/Token.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/Token.java deleted file mode 100644 index a97c803..0000000 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/Token.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.sopt.springFirstSeminar.common.jwt.auth.redis; - - -import jakarta.persistence.Id; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import org.springframework.data.redis.core.RedisHash; -import org.springframework.data.redis.core.index.Indexed; - -@RedisHash(value = "", timeToLive = 60 * 60 * 24 * 1000L * 1) -@AllArgsConstructor -@Getter -@Builder -public class Token { - - @Id - private Long id; - - @Indexed - private String refreshToken; - - public static Token of(final Long id, final String refreshToken) { - return Token.builder() - .id(id) - .refreshToken(refreshToken) - .build(); - } -} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RefreshTokenRepository.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RefreshTokenRepository.java new file mode 100644 index 0000000..ca22190 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RefreshTokenRepository.java @@ -0,0 +1,9 @@ +package org.sopt.springFirstSeminar.common.jwt.auth.redis.repository; + +import org.sopt.springFirstSeminar.common.jwt.auth.RefreshToken; +import org.springframework.data.repository.CrudRepository; + +import java.util.Optional; + +public interface RefreshTokenRepository extends CrudRepository { +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/TokenRepository.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/TokenRepository.java deleted file mode 100644 index 9f61d70..0000000 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/TokenRepository.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sopt.springFirstSeminar.common.jwt.auth.redis.repository; - -import org.sopt.springFirstSeminar.common.jwt.auth.redis.Token; -import org.springframework.data.repository.CrudRepository; - -import java.util.Optional; - -public interface TokenRepository extends CrudRepository { - Optional findByRefreshToken(final String refreshToken); - Optional findById(final Long id); -} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java index f70035a..73ffc19 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java @@ -26,9 +26,7 @@ public class MemberService { private final JwtTokenProvider jwtTokenProvider; @Transactional - public UserJoinResponse createMember( - MemberCreateDTO memberCreate - ) { + public UserJoinResponse createMember(MemberCreateDTO memberCreate) { Member member = memberRepository.save( Member.create(memberCreate.name(), memberCreate.part(), memberCreate.age()) ); From 5833c274bad7256be7eb2f24c17dfc69b7067554 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Sat, 25 May 2024 03:55:51 +0900 Subject: [PATCH 02/17] =?UTF-8?q?[Feat]=20TokenProvider,=20TokenGenerator?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/jwt/JwtTokenGenerator.java | 57 ++++++++++++++ .../common/jwt/JwtTokenProvider.java | 75 ++----------------- .../common/jwt/JwtTokenValidator.java | 41 ++++++++++ .../auth/filter/JwtAuthenticationFilter.java | 4 +- .../common/jwt/dto/Token.java | 17 +++++ .../common/jwt/dto/TokenResponse.java | 18 +++++ .../common/jwt/dto/UserJoinResponse.java | 16 ---- .../controller/MemberController.java | 7 +- .../service/MemberService.java | 8 +- 9 files changed, 151 insertions(+), 92 deletions(-) create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/Token.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java delete mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/UserJoinResponse.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java new file mode 100644 index 0000000..423f5b9 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java @@ -0,0 +1,57 @@ +package org.sopt.springFirstSeminar.common.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Header; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.core.Authentication; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.Base64; +import java.util.Date; + +@Component +public class JwtTokenGenerator { + @Value("${jwt.secret}") + private String secretKey; + @Value("${jwt.access-token-expire-time}") + private long ACCESS_TOKEN_EXPIRE_TIME; + @Value("${jwt.refresh-token-expire-time}") + private long REFRESH_TOKEN_EXPIRE_TIME; + + public String generateToken(final Long userId, boolean isAccessToken) { + final Date presentDate = new Date(); + final Date expireDate = generateExpireDataByToken(isAccessToken, presentDate); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setSubject(String.valueOf(userId)) + .setIssuedAt(presentDate) + .setExpiration(expireDate) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) //여기서 어떤 알고리즘을 사용할 지를 명시적으로 적어주는게 좋음, 안 적어주면 라이브러리 기본 설정에 의존하게됨 + .compact(); + } + + private Date generateExpireDataByToken(final boolean isAccessToken, Date presentDate) { + return new Date(presentDate.getTime() + setExpireTimeByToken(isAccessToken)); + } + + //토근에 따라 만료시간 다름 + private long setExpireTimeByToken(final boolean isAccessToken) { + if (isAccessToken) { + return ACCESS_TOKEN_EXPIRE_TIME; + } else { + return REFRESH_TOKEN_EXPIRE_TIME; + } + } + + public SecretKey getSigningKey() { + String encodedKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); //SecretKey 통해 서명 생성 + return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용 + } + +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java index ed633d4..70e2724 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java @@ -1,84 +1,25 @@ package org.sopt.springFirstSeminar.common.jwt; -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; import lombok.RequiredArgsConstructor; +import org.sopt.springFirstSeminar.common.jwt.dto.Token; +import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; -import javax.crypto.SecretKey; -import java.util.Base64; -import java.util.Date; - -@Component @RequiredArgsConstructor +@Component public class JwtTokenProvider { - private static final String USER_ID = "userId"; - - private static final Long ACCESS_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 100L * 14; - private static final Long REFRESH_TOKEN_EXPIRATION_TIME = 24 * 60 * 60 * 1000L * 14; - + private final JwtTokenGenerator jwtTokenGenerator; - @Value("${jwt.secret}") - private String JWT_SECRET; - - - public String issueAccessToken(final Authentication authentication) { - return generateToken(authentication, ACCESS_TOKEN_EXPIRATION_TIME); + public Token issueTokens(final Long userId) { + return Token.of( + jwtTokenGenerator.generateToken(userId, true), + jwtTokenGenerator.generateToken(userId, false)); } -// public String issueRefreshToken(final Authentication authentication) { -// return issueToken(authentication, REFRESH_TOKEN_EXPIRATION_TIME); -// } - public String generateToken(Authentication authentication, Long tokenExpirationTime) { - final Date now = new Date(); - final Claims claims = Jwts.claims() - .setIssuedAt(now) - .setExpiration(new Date(now.getTime() + tokenExpirationTime)); // 만료 시간 - claims.put(USER_ID, authentication.getPrincipal()); - - return Jwts.builder() - .setHeaderParam(Header.TYPE, Header.JWT_TYPE) // Header - .setClaims(claims) // Claim - .signWith(getSigningKey()) // Signature - .compact(); - } - - private SecretKey getSigningKey() { - String encodedKey = Base64.getEncoder().encodeToString(JWT_SECRET.getBytes()); //SecretKey 통해 서명 생성 - return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용 - } - - public JwtValidationType validateToken(String token) { - try { - final Claims claims = getBody(token); - return JwtValidationType.VALID_JWT; - } catch (MalformedJwtException ex) { - return JwtValidationType.INVALID_JWT_TOKEN; - } catch (ExpiredJwtException ex) { - return JwtValidationType.EXPIRED_JWT_TOKEN; - } catch (UnsupportedJwtException ex) { - return JwtValidationType.UNSUPPORTED_JWT_TOKEN; - } catch (IllegalArgumentException ex) { - return JwtValidationType.EMPTY_JWT; - } - } - - private Claims getBody(final String token) { - return Jwts.parserBuilder() - .setSigningKey(getSigningKey()) - .build() - .parseClaimsJws(token) - .getBody(); - } - - public Long getUserFromJwt(String token) { - Claims claims = getBody(token); - return Long.valueOf(claims.get(USER_ID).toString()); - } } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java new file mode 100644 index 0000000..7ec2991 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java @@ -0,0 +1,41 @@ +package org.sopt.springFirstSeminar.common.jwt; + +import io.jsonwebtoken.*; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + + +@RequiredArgsConstructor +@Component +public class JwtTokenValidator { + + private final JwtTokenGenerator jwtTokenGenerator; + + public JwtValidationType validateToken(String token) { + try { + final Claims claims = getBody(token); + return JwtValidationType.VALID_JWT; + } catch (MalformedJwtException ex) { + return JwtValidationType.INVALID_JWT_TOKEN; + } catch (ExpiredJwtException ex) { + return JwtValidationType.EXPIRED_JWT_TOKEN; + } catch (UnsupportedJwtException ex) { + return JwtValidationType.UNSUPPORTED_JWT_TOKEN; + } catch (IllegalArgumentException ex) { + return JwtValidationType.EMPTY_JWT; + } + } + + private Claims getBody(final String token) { + return Jwts.parserBuilder() + .setSigningKey(jwtTokenGenerator.getSigningKey()) + .build() + .parseClaimsJws(token) + .getBody(); + } + + public Long getUserFromJwt(String token) { + Claims claims = getBody(token); + return Long.valueOf(claims.get(USER_ID).toString()); + } +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java index 9e50059..cc4c8f2 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java @@ -8,6 +8,7 @@ import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.dto.ErrorMessage; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; +import org.sopt.springFirstSeminar.common.jwt.JwtTokenValidator; import org.sopt.springFirstSeminar.common.jwt.UserAuthentication; import org.sopt.springFirstSeminar.exception.UnauthorizedException; import org.springframework.security.core.context.SecurityContextHolder; @@ -25,6 +26,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenValidator jwtTokenValidator; @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @@ -32,7 +34,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull FilterChain filterChain) throws ServletException, IOException { try { final String token = getJwtFromRequest(request); - if (jwtTokenProvider.validateToken(token) == VALID_JWT) { + if (jwtTokenValidator.validateToken(token) == VALID_JWT) { Long memberId = jwtTokenProvider.getUserFromJwt(token); UserAuthentication authentication = UserAuthentication.createUserAuthentication(memberId); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/Token.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/Token.java new file mode 100644 index 0000000..0e9dab8 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/Token.java @@ -0,0 +1,17 @@ +package org.sopt.springFirstSeminar.common.jwt.dto; + +import lombok.AccessLevel; +import lombok.Builder; + +@Builder(access = AccessLevel.PRIVATE) +public record Token( + String accessToken, + String refreshToken +) { + public static Token of(String accessToken, String refreshToken) { + return Token.builder() + .accessToken(accessToken) + .refreshToken(refreshToken) + .build(); + } +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java new file mode 100644 index 0000000..e498f0a --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java @@ -0,0 +1,18 @@ +package org.sopt.springFirstSeminar.common.jwt.dto; + +public record TokenResponse( + String accessToken, + String refreshToken, + String userId +) { + + public static TokenResponse of( + String accessToken, + String refreshToken, + String userId + ) { + return new TokenResponse(accessToken, refreshToken, userId); + } +} + + diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/UserJoinResponse.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/UserJoinResponse.java deleted file mode 100644 index 812649d..0000000 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/UserJoinResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.sopt.springFirstSeminar.common.jwt.dto; - -public record UserJoinResponse( - String accessToken, - String userId -) { - - public static UserJoinResponse of( - String accessToken, - String userId - ) { - return new UserJoinResponse(accessToken, userId); - } -} - - diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java index 15b2dbe..3415c6a 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java @@ -2,7 +2,7 @@ import lombok.RequiredArgsConstructor; -import org.sopt.springFirstSeminar.common.jwt.dto.UserJoinResponse; +import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.sopt.springFirstSeminar.service.MemberService; import org.sopt.springFirstSeminar.service.dto.MemberCreateDTO; import org.sopt.springFirstSeminar.service.dto.MemberFindDTO; @@ -11,7 +11,6 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.net.URI; import java.util.List; @RestController @@ -22,10 +21,10 @@ public class MemberController { private final MemberService memberService; @PostMapping - public ResponseEntity postMember( + public ResponseEntity postMember( @RequestBody MemberCreateDTO memberCreate ) { - UserJoinResponse userJoinResponse = memberService.createMember(memberCreate); + TokenResponse userJoinResponse = memberService.createMember(memberCreate); return ResponseEntity.status(HttpStatus.CREATED) .header("Location", userJoinResponse.userId()) .body( diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java index 73ffc19..a144d5b 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java @@ -4,7 +4,7 @@ import org.sopt.springFirstSeminar.common.dto.ErrorMessage; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; import org.sopt.springFirstSeminar.common.jwt.UserAuthentication; -import org.sopt.springFirstSeminar.common.jwt.dto.UserJoinResponse; +import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.sopt.springFirstSeminar.domain.Member; import org.sopt.springFirstSeminar.exception.NotFoundException; import org.sopt.springFirstSeminar.repository.MemberRepository; @@ -26,15 +26,15 @@ public class MemberService { private final JwtTokenProvider jwtTokenProvider; @Transactional - public UserJoinResponse createMember(MemberCreateDTO memberCreate) { + public TokenResponse createMember(MemberCreateDTO memberCreate) { Member member = memberRepository.save( Member.create(memberCreate.name(), memberCreate.part(), memberCreate.age()) ); Long memberId = member.getId(); - String accessToken = jwtTokenProvider.issueAccessToken( + String accessToken = jwtTokenProvider.issueTokens( UserAuthentication.createUserAuthentication(memberId) ); - return UserJoinResponse.of(accessToken, memberId.toString()); + return TokenResponse.of(accessToken, memberId.toString()); } public void findById(final Long memberId) { From 76e4c8987a455d3f77ecabf892348e768f870d9a Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Sat, 25 May 2024 04:11:25 +0900 Subject: [PATCH 03/17] =?UTF-8?q?[Chore]=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springFirstSeminar/common/jwt/JwtTokenProvider.java | 6 ++++++ .../springFirstSeminar/common/jwt/JwtTokenValidator.java | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java index 70e2724..bd80632 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java @@ -1,5 +1,6 @@ package org.sopt.springFirstSeminar.common.jwt; +import io.jsonwebtoken.Claims; import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.jwt.dto.Token; import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; @@ -19,6 +20,11 @@ public Token issueTokens(final Long userId) { jwtTokenGenerator.generateToken(userId, false)); } + public Long getUserFromJwt(String token) { + Claims claims = getBody(token); + return Long.valueOf(claims.get(USER_ID).toString()); + } + diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java index 7ec2991..d5bc964 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java @@ -34,8 +34,4 @@ private Claims getBody(final String token) { .getBody(); } - public Long getUserFromJwt(String token) { - Claims claims = getBody(token); - return Long.valueOf(claims.get(USER_ID).toString()); - } } From e4d1f5a4df6419aa8a2563f7d35c2dc989937721 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Mon, 27 May 2024 16:53:42 +0900 Subject: [PATCH 04/17] =?UTF-8?q?[Feat]=20JwtAuthenticationFilter=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springFirstSeminar/common/Constant.java | 6 +++ .../common/jwt/JwtTokenGenerator.java | 12 +++-- .../auth/filter/JwtAuthenticationFilter.java | 51 +++++++++++-------- 3 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/Constant.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/Constant.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/Constant.java new file mode 100644 index 0000000..9f22ce4 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/Constant.java @@ -0,0 +1,6 @@ +package org.sopt.springFirstSeminar.common; + +public class Constant { + public static final String AUTHORIZATION = "Authorization"; + public static final String BEARER = "Bearer "; +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java index 423f5b9..c8a5fdb 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java @@ -1,9 +1,6 @@ package org.sopt.springFirstSeminar.common.jwt; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Header; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.springframework.beans.factory.annotation.Value; @@ -36,6 +33,12 @@ public String generateToken(final Long userId, boolean isAccessToken) { .compact(); } + public JwtParser getJwtParser() { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build(); + } + private Date generateExpireDataByToken(final boolean isAccessToken, Date presentDate) { return new Date(presentDate.getTime() + setExpireTimeByToken(isAccessToken)); } @@ -53,5 +56,4 @@ public SecretKey getSigningKey() { String encodedKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); //SecretKey 통해 서명 생성 return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용 } - } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java index cc4c8f2..7986701 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java @@ -6,12 +6,15 @@ import jakarta.servlet.http.HttpServletResponse; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.sopt.springFirstSeminar.common.Constant; import org.sopt.springFirstSeminar.common.dto.ErrorMessage; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; import org.sopt.springFirstSeminar.common.jwt.JwtTokenValidator; import org.sopt.springFirstSeminar.common.jwt.UserAuthentication; import org.sopt.springFirstSeminar.exception.UnauthorizedException; +import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.authentication.WebAuthenticationDetails; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; @@ -19,7 +22,7 @@ import java.io.IOException; -import static org.sopt.springFirstSeminar.common.jwt.JwtValidationType.VALID_JWT; +import static org.sopt.springFirstSeminar.common.jwt.UserAuthentication.createUserAuthentication; @Component @RequiredArgsConstructor @@ -28,29 +31,35 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; private final JwtTokenValidator jwtTokenValidator; + @Override - protected void doFilterInternal(@NonNull HttpServletRequest request, - @NonNull HttpServletResponse response, - @NonNull FilterChain filterChain) throws ServletException, IOException { - try { - final String token = getJwtFromRequest(request); - if (jwtTokenValidator.validateToken(token) == VALID_JWT) { - Long memberId = jwtTokenProvider.getUserFromJwt(token); - UserAuthentication authentication = UserAuthentication.createUserAuthentication(memberId); - authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authentication); - } - } catch (Exception exception) { - throw new UnauthorizedException(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION); - } + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + final String accessToken = getAccessToken(request); + jwtTokenValidator.validateAccessToken(accessToken); + doAuthentication(request, jwtTokenProvider.getSubject(accessToken)); filterChain.doFilter(request, response); } - private String getJwtFromRequest(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring("Bearer ".length()); + //userId로 UserAuthentication 객체 생성 + private void doAuthentication(HttpServletRequest request, Long userId) { + UserAuthentication authentication = createUserAuthentication(userId); + createAndSetWebAuthenticationDetails(request, authentication); + SecurityContext securityContext = SecurityContextHolder.getContext(); + securityContext.setAuthentication(authentication); + } + + private void createAndSetWebAuthenticationDetails(HttpServletRequest request, UserAuthentication authentication) { + WebAuthenticationDetailsSource webAuthenticationDetailsSource = new WebAuthenticationDetailsSource(); + WebAuthenticationDetails webAuthenticationDetails = webAuthenticationDetailsSource.buildDetails(request); + authentication.setDetails(webAuthenticationDetails); + } + + //accessToken 가져오기 + private String getAccessToken(final HttpServletRequest request) { + String accessToken = request.getHeader(Constant.AUTHORIZATION); + if (StringUtils.hasText(accessToken) && accessToken.startsWith(Constant.BEARER)) { + return accessToken.substring(Constant.BEARER.length()); } - return null; + throw new UnauthorizedException(ErrorMessage.INVALID_ACCESS_TOKEN); } -} \ No newline at end of file +} From f7d80fce7940a424f3eae5791896b283ab0b0fb1 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Mon, 27 May 2024 16:54:02 +0900 Subject: [PATCH 05/17] =?UTF-8?q?[Feat]=20Jwt=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20=EB=B6=84=EB=A6=AC(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dto/ErrorMessage.java | 16 ++++++- .../common/jwt/JwtTokenProvider.java | 13 +++--- .../common/jwt/JwtTokenValidator.java | 43 +++++++++++++------ .../common/jwt/JwtValidationType.java | 20 ++++----- 4 files changed, 60 insertions(+), 32 deletions(-) diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java index 966fae2..263f9d2 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java @@ -13,7 +13,21 @@ public enum ErrorMessage { BLOG_NOT_MATCH_MEMBER(HttpStatus.NOT_FOUND.value(), "해당 멤버 ID에 해당하는 블로그ID가 아닙니다."), CONTENT_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "POST ID에 해당하는 글이 존재하지 않습니다"), MAX_BLOG_CONTENT(HttpStatus.BAD_REQUEST.value(), "블로그 글이 최대 글자 수(20)를 초과했습니다"), - JWT_UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "사용자의 로그인 검증을 실패했습니다.") + + //jwt + JWT_UNAUTHORIZED_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "사용자의 로그인 검증을 실패했습니다."), + INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰의 형식이 올바르지 않습니다. Bearer 타입을 확인해 주세요."), + EXPIRED_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED.value(), "액세스 토큰이 만료되었습니다. 재발급 받아주세요."), + UNSUPPORTED_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED.value(), "지원하지 않는 JWT 형식입니다."), + EMPTY_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED.value(), "JWT Claim이 비어있습니다"), + JWT_SIGNATURE_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "JWT의 기존 서명과 다릅니다."), + + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰의 형식이 올바르지 않습니다."), + INVALID_REFRESH_TOKEN_VALUE(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰의 값이 올바르지 않습니다."), + EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(),"리프레시 토큰이 만료되었습니다. 다시 로그인해 주세요."), + MISMATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 일치하지 않습니다."), + + ; private final int status; diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java index bd80632..79cd7b9 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java @@ -1,6 +1,7 @@ package org.sopt.springFirstSeminar.common.jwt; import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtParser; import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.jwt.dto.Token; import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; @@ -20,12 +21,10 @@ public Token issueTokens(final Long userId) { jwtTokenGenerator.generateToken(userId, false)); } - public Long getUserFromJwt(String token) { - Claims claims = getBody(token); - return Long.valueOf(claims.get(USER_ID).toString()); + public Long getSubject(String accessToken) { + JwtParser jwtParser = jwtTokenGenerator.getJwtParser(); + return Long.valueOf(jwtParser.parseClaimsJws(accessToken) + .getBody() + .getSubject()); } - - - - } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java index d5bc964..bc7febb 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java @@ -2,6 +2,8 @@ import io.jsonwebtoken.*; import lombok.RequiredArgsConstructor; +import org.sopt.springFirstSeminar.common.dto.ErrorMessage; +import org.sopt.springFirstSeminar.exception.UnauthorizedException; import org.springframework.stereotype.Component; @@ -11,27 +13,40 @@ public class JwtTokenValidator { private final JwtTokenGenerator jwtTokenGenerator; - public JwtValidationType validateToken(String token) { + public void validateAccessToken(String accessToken) { try { - final Claims claims = getBody(token); - return JwtValidationType.VALID_JWT; + parseToken(accessToken); + } catch (ExpiredJwtException e) { + throw new UnauthorizedException(ErrorMessage.EXPIRED_ACCESS_TOKEN); } catch (MalformedJwtException ex) { - return JwtValidationType.INVALID_JWT_TOKEN; - } catch (ExpiredJwtException ex) { - return JwtValidationType.EXPIRED_JWT_TOKEN; + throw new UnauthorizedException(ErrorMessage.INVALID_ACCESS_TOKEN); } catch (UnsupportedJwtException ex) { - return JwtValidationType.UNSUPPORTED_JWT_TOKEN; + throw new UnauthorizedException(ErrorMessage.UNSUPPORTED_ACCESS_TOKEN); } catch (IllegalArgumentException ex) { - return JwtValidationType.EMPTY_JWT; + throw new UnauthorizedException(ErrorMessage.EMPTY_ACCESS_TOKEN); + } catch (SignatureException ex) { + throw new UnauthorizedException(ErrorMessage.JWT_SIGNATURE_EXCEPTION); } } - private Claims getBody(final String token) { - return Jwts.parserBuilder() - .setSigningKey(jwtTokenGenerator.getSigningKey()) - .build() - .parseClaimsJws(token) - .getBody(); + public void validateRefreshToken(String refreshToken) { + try { + parseToken(refreshToken); + } catch (ExpiredJwtException e) { + throw new UnauthorizedException(ErrorMessage.EXPIRED_REFRESH_TOKEN); + } catch (Exception e) { + throw new UnauthorizedException(ErrorMessage.INVALID_REFRESH_TOKEN_VALUE); + } + } + + public void equalsRefreshToken(String refreshToken, String storedRefreshToken) { + if (!refreshToken.equals(storedRefreshToken)) { + throw new UnauthorizedException(ErrorMessage.MISMATCH_REFRESH_TOKEN); + } } + private void parseToken(String token) { + JwtParser jwtParser = jwtTokenGenerator.getJwtParser(); + jwtParser.parseClaimsJws(token); + } } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java index e2b98b4..f67a7b9 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java @@ -1,10 +1,10 @@ -package org.sopt.springFirstSeminar.common.jwt; - -public enum JwtValidationType { - VALID_JWT, // 유효한 JWT - INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 - INVALID_JWT_TOKEN, // 유효하지 않은 토큰 - EXPIRED_JWT_TOKEN, // 만료된 토큰 - UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 - EMPTY_JWT // 빈 JWT -} +//package org.sopt.springFirstSeminar.common.jwt; +// +//public enum JwtValidationType { +// VALID_JWT, // 유효한 JWT +// INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 +// INVALID_JWT_TOKEN, // 유효하지 않은 토큰 +// EXPIRED_JWT_TOKEN, // 만료된 토큰 +// UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 +// EMPTY_JWT // 빈 JWT +//} From ae8e95ee3841a8845a42aef19ad059d4db4c5925 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Mon, 27 May 2024 19:38:56 +0900 Subject: [PATCH 06/17] =?UTF-8?q?[Feat]=20=EB=A9=A4=EB=B2=84=EB=93=B1?= =?UTF-8?q?=EB=A1=9D=20api=20=EA=B5=AC=ED=98=84(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dto/SuccessMessage.java | 2 ++ .../common/jwt/JwtTokenGenerator.java | 15 +++++++----- .../common/jwt/JwtValidationType.java | 10 -------- .../auth/filter/JwtAuthenticationFilter.java | 5 ++-- .../jwt/auth/filter/SecurityConfig.java | 9 ++++++- .../common/jwt/dto/TokenResponse.java | 4 ++-- .../controller/MemberController.java | 21 ++++++++-------- .../service/MemberService.java | 24 +++++++++++++------ 8 files changed, 51 insertions(+), 39 deletions(-) delete mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java index 543c1c8..df6d815 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java @@ -10,6 +10,8 @@ @Getter public enum SuccessMessage { + MEMBER_CREATE_SUCCESS(HttpStatus.CREATED.value(), "유저 생성이 완료되었습니다."), + BLOG_CREATE_SUCCESS(HttpStatus.CREATED.value(), "블로그 생성이 완료되었습니다."), BLOG_CONTENT_CREATE_SUCCESS(HttpStatus.CREATED.value(), "블로그에 글 작성이 완료되었습니다."), GET_BLOG_CONTENT_SUCCESS(HttpStatus.OK.value(), "블로그 글 가져오기가 완료되었습니다."), diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java index c8a5fdb..879ca44 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java @@ -15,10 +15,13 @@ public class JwtTokenGenerator { @Value("${jwt.secret}") private String secretKey; - @Value("${jwt.access-token-expire-time}") - private long ACCESS_TOKEN_EXPIRE_TIME; - @Value("${jwt.refresh-token-expire-time}") - private long REFRESH_TOKEN_EXPIRE_TIME; +// @Value("${jwt.access-token-expire-time}") +// private long ACCESS_TOKEN_EXPIRE_TIME; +// @Value("${jwt.refresh-token-expire-time}") +// private long REFRESH_TOKEN_EXPIRE_TIME; + + private final long accessExpiration = 24 * 60 * 60 * 100L * 14; + private final long refreshExpiration = 24 * 60 * 60 * 1000L * 14; public String generateToken(final Long userId, boolean isAccessToken) { final Date presentDate = new Date(); @@ -46,9 +49,9 @@ private Date generateExpireDataByToken(final boolean isAccessToken, Date present //토근에 따라 만료시간 다름 private long setExpireTimeByToken(final boolean isAccessToken) { if (isAccessToken) { - return ACCESS_TOKEN_EXPIRE_TIME; + return accessExpiration; } else { - return REFRESH_TOKEN_EXPIRE_TIME; + return refreshExpiration; } } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java deleted file mode 100644 index f67a7b9..0000000 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtValidationType.java +++ /dev/null @@ -1,10 +0,0 @@ -//package org.sopt.springFirstSeminar.common.jwt; -// -//public enum JwtValidationType { -// VALID_JWT, // 유효한 JWT -// INVALID_JWT_SIGNATURE, // 유효하지 않은 서명 -// INVALID_JWT_TOKEN, // 유효하지 않은 토큰 -// EXPIRED_JWT_TOKEN, // 만료된 토큰 -// UNSUPPORTED_JWT_TOKEN, // 지원하지 않는 형식의 토큰 -// EMPTY_JWT // 빈 JWT -//} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java index 7986701..5875439 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java @@ -4,7 +4,6 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.Constant; import org.sopt.springFirstSeminar.common.dto.ErrorMessage; @@ -24,8 +23,8 @@ import static org.sopt.springFirstSeminar.common.jwt.UserAuthentication.createUserAuthentication; -@Component @RequiredArgsConstructor +@Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; @@ -54,7 +53,7 @@ private void createAndSetWebAuthenticationDetails(HttpServletRequest request, Us authentication.setDetails(webAuthenticationDetails); } - //accessToken 가져오기 + //토큰 추출 private String getAccessToken(final HttpServletRequest request) { String accessToken = request.getHeader(Constant.AUTHORIZATION); if (StringUtils.hasText(accessToken) && accessToken.startsWith(Constant.BEARER)) { diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/SecurityConfig.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/SecurityConfig.java index b8c4b2b..9fcecc4 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/SecurityConfig.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/SecurityConfig.java @@ -1,6 +1,8 @@ package org.sopt.springFirstSeminar.common.jwt.auth.filter; import lombok.RequiredArgsConstructor; +import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; +import org.sopt.springFirstSeminar.common.jwt.JwtTokenValidator; import org.sopt.springFirstSeminar.common.jwt.auth.CustomAccessDeniedHandler; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -15,10 +17,13 @@ @RequiredArgsConstructor @EnableWebSecurity //web Security를 사용할 수 있게 public class SecurityConfig { - private final JwtAuthenticationFilter jwtAuthenticationFilter; private final CustomJwtAuthenticationEntryPoint customJwtAuthenticationEntryPoint; private final CustomAccessDeniedHandler customAccessDeniedHandler; + private final JwtAuthenticationFilter jwtAuthenticationFilter; + private final JwtTokenProvider jwtTokenProvider; + private final JwtTokenValidator jwtTokenValidator; + private static final String[] AUTH_WHITE_LIST = {"/api/v1/member"}; @@ -35,10 +40,12 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { }); + //유저 가입이나 로그인 등 인증 전 단계의 api 허용, 그 외는 인증! http.authorizeHttpRequests(auth -> { auth.requestMatchers(AUTH_WHITE_LIST).permitAll(); auth.anyRequest().authenticated(); }) +// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, jwtTokenValidator), UsernamePasswordAuthenticationFilter.class); .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java index e498f0a..08ff274 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java @@ -3,13 +3,13 @@ public record TokenResponse( String accessToken, String refreshToken, - String userId + Long userId ) { public static TokenResponse of( String accessToken, String refreshToken, - String userId + Long userId ) { return new TokenResponse(accessToken, refreshToken, userId); } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java index 3415c6a..25e1d8a 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java @@ -2,6 +2,11 @@ import lombok.RequiredArgsConstructor; +import org.apache.tomcat.util.http.parser.Authorization; +import org.sopt.springFirstSeminar.common.ApiResponseUtil; +import org.sopt.springFirstSeminar.common.BaseResponse; +import org.sopt.springFirstSeminar.common.Constant; +import org.sopt.springFirstSeminar.common.dto.SuccessMessage; import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.sopt.springFirstSeminar.service.MemberService; import org.sopt.springFirstSeminar.service.dto.MemberCreateDTO; @@ -21,15 +26,12 @@ public class MemberController { private final MemberService memberService; @PostMapping - public ResponseEntity postMember( - @RequestBody MemberCreateDTO memberCreate - ) { - TokenResponse userJoinResponse = memberService.createMember(memberCreate); - return ResponseEntity.status(HttpStatus.CREATED) - .header("Location", userJoinResponse.userId()) - .body( - userJoinResponse - ); + public ResponseEntity> postMember(@RequestHeader(Constant.AUTHORIZATION) final String token, + @RequestBody MemberCreateDTO memberCreate) + { + final TokenResponse userJoinResponse = memberService.createMember(token, memberCreate); + + return ApiResponseUtil.success(SuccessMessage.MEMBER_CREATE_SUCCESS, userJoinResponse); } @GetMapping("/{memberId}") @@ -47,5 +49,4 @@ public ResponseEntity deleteMemberById(@PathVariable final Long memberId) { public ResponseEntity> getAllMemberList() { return ResponseEntity.ok(memberService.getAllMemberList()); } - } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java index a144d5b..2d27240 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java @@ -2,8 +2,12 @@ import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.dto.ErrorMessage; +import org.sopt.springFirstSeminar.common.jwt.JwtTokenGenerator; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; import org.sopt.springFirstSeminar.common.jwt.UserAuthentication; +import org.sopt.springFirstSeminar.common.jwt.auth.RefreshToken; +import org.sopt.springFirstSeminar.common.jwt.auth.redis.repository.RefreshTokenRepository; +import org.sopt.springFirstSeminar.common.jwt.dto.Token; import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.sopt.springFirstSeminar.domain.Member; import org.sopt.springFirstSeminar.exception.NotFoundException; @@ -24,17 +28,23 @@ public class MemberService { private final MemberRepository memberRepository; private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenRepository refreshTokenRepository; @Transactional - public TokenResponse createMember(MemberCreateDTO memberCreate) { - Member member = memberRepository.save( + public TokenResponse createMember(String token, MemberCreateDTO memberCreate) { + Member createdMember = memberRepository.save( Member.create(memberCreate.name(), memberCreate.part(), memberCreate.age()) ); - Long memberId = member.getId(); - String accessToken = jwtTokenProvider.issueTokens( - UserAuthentication.createUserAuthentication(memberId) - ); - return TokenResponse.of(accessToken, memberId.toString()); + + Long createdMemberId = createdMember.getId(); + Token issuedToken = jwtTokenProvider.issueTokens(createdMemberId); + updateRefreshToken(issuedToken.refreshToken(), createdMember); + + return TokenResponse.of(issuedToken.accessToken(), issuedToken.refreshToken(), createdMemberId); + } + + private void updateRefreshToken(String refreshToken, Member member) { + refreshTokenRepository.save(RefreshToken.of(member.getId(), refreshToken)); } public void findById(final Long memberId) { From 1b7f8a0a3066b3f2cf853528cae7eba623e719e6 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Tue, 28 May 2024 00:51:58 +0900 Subject: [PATCH 07/17] =?UTF-8?q?[Feat]=20UserIdArgumentResolver=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/HandlerMethodArgumentResolver.java | 14 +++++++++++ .../common/jwt/auth/UserId.java | 11 ++++++++ .../jwt/auth/UserIdArgumentResolver.java | 25 +++++++++++++++++++ 3 files changed, 50 insertions(+) create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserId.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java new file mode 100644 index 0000000..aea06f9 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java @@ -0,0 +1,14 @@ +package org.sopt.springFirstSeminar.common.jwt.auth; + +import org.springframework.core.MethodParameter; +import org.springframework.lang.Nullable; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +public interface HandlerMethodArgumentResolver { + boolean supportsParameter(MethodParameter parameter); + + @Nullable + Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserId.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserId.java new file mode 100644 index 0000000..5aff35d --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserId.java @@ -0,0 +1,11 @@ +package org.sopt.springFirstSeminar.common.jwt.auth; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +public @interface UserId { +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java new file mode 100644 index 0000000..881bceb --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java @@ -0,0 +1,25 @@ +package org.sopt.springFirstSeminar.common.jwt.auth; + +import org.springframework.core.MethodParameter; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; + +public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { + + // + @Override + public boolean supportsParameter(MethodParameter parameter) { + boolean hasUserIdAnnotation = parameter.hasParameterAnnotation(UserId.class); + boolean isLongType = Long.class.isAssignableFrom(parameter.getParameterType()); + return hasUserIdAnnotation && isLongType; + } + + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + return SecurityContextHolder.getContext() + .getAuthentication() + .getPrincipal(); + } +} From 58c68647a99cfbee6cbe67eedd09dd41c89b51a8 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Tue, 28 May 2024 17:31:48 +0900 Subject: [PATCH 08/17] =?UTF-8?q?[Feat]=20ArgumentResolver=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EB=B0=8F=20=EB=A9=A4=EB=B2=84=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?,=20=EB=93=B1=EB=A1=9D=20=EC=A0=81=EC=9A=A9(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dto/SuccessMessage.java | 4 +++- .../common/jwt/JwtTokenProvider.java | 3 ++- .../common/jwt/JwtTokenValidator.java | 3 ++- .../jwt/auth/{UserId.java => MemberId.java} | 2 +- .../common/jwt/auth/RefreshToken.java | 4 ++-- .../jwt/auth/{filter => }/SecurityConfig.java | 7 +++--- .../jwt/auth/UserIdArgumentResolver.java | 13 +++++++++-- .../common/jwt/auth/WebConfig.java | 20 +++++++++++++++++ .../auth/filter/JwtAuthenticationFilter.java | 12 +++++++--- .../auth/redis/repository/RedisConfig.java | 22 +++++++++++++++++++ .../controller/MemberController.java | 17 ++++++++------ .../service/MemberService.java | 16 +++++++++----- 12 files changed, 97 insertions(+), 26 deletions(-) rename springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/{UserId.java => MemberId.java} (90%) rename springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/{filter => }/SecurityConfig.java (90%) create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/WebConfig.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RedisConfig.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java index df6d815..b828272 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java @@ -10,7 +10,9 @@ @Getter public enum SuccessMessage { - MEMBER_CREATE_SUCCESS(HttpStatus.CREATED.value(), "유저 생성이 완료되었습니다."), + MEMBER_CREATE_SUCCESS(HttpStatus.CREATED.value(), "유저 생성에 성공했습니다."), + MEMBER_FIND_SUCCESS(HttpStatus.OK.value(), "유저 검색에 성공했습니다."), + BLOG_CREATE_SUCCESS(HttpStatus.CREATED.value(), "블로그 생성이 완료되었습니다."), BLOG_CONTENT_CREATE_SUCCESS(HttpStatus.CREATED.value(), "블로그에 글 작성이 완료되었습니다."), diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java index 79cd7b9..90c69ac 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java @@ -8,9 +8,10 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; @RequiredArgsConstructor -@Component +@Service public class JwtTokenProvider { private final JwtTokenGenerator jwtTokenGenerator; diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java index bc7febb..a3b46d7 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenValidator.java @@ -5,10 +5,11 @@ import org.sopt.springFirstSeminar.common.dto.ErrorMessage; import org.sopt.springFirstSeminar.exception.UnauthorizedException; import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; @RequiredArgsConstructor -@Component +@Service public class JwtTokenValidator { private final JwtTokenGenerator jwtTokenGenerator; diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserId.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/MemberId.java similarity index 90% rename from springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserId.java rename to springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/MemberId.java index 5aff35d..2e361a9 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserId.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/MemberId.java @@ -7,5 +7,5 @@ @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) -public @interface UserId { +public @interface MemberId { } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java index 32fa3b1..fa6b95e 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java @@ -21,9 +21,9 @@ public class RefreshToken { @Indexed private String refreshToken; - public static RefreshToken of(final Long userId, final String refreshToken) { + public static RefreshToken of(final Long memberId, final String refreshToken) { return RefreshToken.builder() - .id(userId) + .id(memberId) .refreshToken(refreshToken) .build(); } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/SecurityConfig.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java similarity index 90% rename from springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/SecurityConfig.java rename to springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java index 9fcecc4..f35ed0b 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/SecurityConfig.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java @@ -1,9 +1,10 @@ -package org.sopt.springFirstSeminar.common.jwt.auth.filter; +package org.sopt.springFirstSeminar.common.jwt.auth; import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; import org.sopt.springFirstSeminar.common.jwt.JwtTokenValidator; -import org.sopt.springFirstSeminar.common.jwt.auth.CustomAccessDeniedHandler; +import org.sopt.springFirstSeminar.common.jwt.auth.filter.CustomJwtAuthenticationEntryPoint; +import org.sopt.springFirstSeminar.common.jwt.auth.filter.JwtAuthenticationFilter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; @@ -25,7 +26,7 @@ public class SecurityConfig { private final JwtTokenValidator jwtTokenValidator; - private static final String[] AUTH_WHITE_LIST = {"/api/v1/member"}; + private static final String[] AUTH_WHITE_LIST = {"/api/v1/member", "/test"}; @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java index 881bceb..f8552f4 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java @@ -2,20 +2,29 @@ import org.springframework.core.MethodParameter; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +@Component public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { - // @Override public boolean supportsParameter(MethodParameter parameter) { - boolean hasUserIdAnnotation = parameter.hasParameterAnnotation(UserId.class); + + //요청받은 메소드의 파라미터에 @UserId 어노테이션이 붙어있는지 확인 + boolean hasUserIdAnnotation = parameter.hasParameterAnnotation(MemberId.class); + + //타입이 같은지 확인 boolean isLongType = Long.class.isAssignableFrom(parameter.getParameterType()); + + //둘 다 true면 아래 resolveArgument 메서드 실행 return hasUserIdAnnotation && isLongType; } + //SecurityContextHolder에 있는 UserId 추출 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return SecurityContextHolder.getContext() diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/WebConfig.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/WebConfig.java new file mode 100644 index 0000000..cbc78b5 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/WebConfig.java @@ -0,0 +1,20 @@ +package org.sopt.springFirstSeminar.common.jwt.auth; + + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +@RequiredArgsConstructor +@Configuration +public class WebConfig implements WebMvcConfigurer { + private final UserIdArgumentResolver userIdArgumentResolver; + + @Override + public void addArgumentResolvers(List resolvers) { + resolvers.add(userIdArgumentResolver); + } +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java index 5875439..71e3d23 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java @@ -5,6 +5,7 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.sopt.springFirstSeminar.common.Constant; import org.sopt.springFirstSeminar.common.dto.ErrorMessage; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; @@ -25,6 +26,7 @@ @RequiredArgsConstructor @Component +@Slf4j public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtTokenProvider jwtTokenProvider; @@ -33,9 +35,13 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { - final String accessToken = getAccessToken(request); - jwtTokenValidator.validateAccessToken(accessToken); - doAuthentication(request, jwtTokenProvider.getSubject(accessToken)); + try { + final String accessToken = getAccessToken(request); + jwtTokenValidator.validateAccessToken(accessToken); + doAuthentication(request, jwtTokenProvider.getSubject(accessToken)); + } catch(UnauthorizedException e){ + log.info("———"); + } filterChain.doFilter(request, response); } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RedisConfig.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RedisConfig.java new file mode 100644 index 0000000..8952a3a --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/redis/repository/RedisConfig.java @@ -0,0 +1,22 @@ +package org.sopt.springFirstSeminar.common.jwt.auth.redis.repository; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory; + +@Configuration +public class RedisConfig { + + @Value("${spring.data.redis.host}") + private String host; + + @Value("${spring.data.redis.port}") + private int port; + + @Bean + public RedisConnectionFactory redisConnectionFactory() { + return new LettuceConnectionFactory(host, port); + } +} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java index 25e1d8a..4395f0f 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java @@ -7,6 +7,7 @@ import org.sopt.springFirstSeminar.common.BaseResponse; import org.sopt.springFirstSeminar.common.Constant; import org.sopt.springFirstSeminar.common.dto.SuccessMessage; +import org.sopt.springFirstSeminar.common.jwt.auth.MemberId; import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.sopt.springFirstSeminar.service.MemberService; import org.sopt.springFirstSeminar.service.dto.MemberCreateDTO; @@ -26,17 +27,19 @@ public class MemberController { private final MemberService memberService; @PostMapping - public ResponseEntity> postMember(@RequestHeader(Constant.AUTHORIZATION) final String token, - @RequestBody MemberCreateDTO memberCreate) + public ResponseEntity> postMember(@RequestBody MemberCreateDTO memberCreate) { - final TokenResponse userJoinResponse = memberService.createMember(token, memberCreate); + final TokenResponse memberJoinResponse = memberService.createMember(memberCreate); - return ApiResponseUtil.success(SuccessMessage.MEMBER_CREATE_SUCCESS, userJoinResponse); + return ApiResponseUtil.success(SuccessMessage.MEMBER_CREATE_SUCCESS, memberJoinResponse); } - @GetMapping("/{memberId}") - public ResponseEntity findMemberById(@PathVariable final Long memberId) { - return ResponseEntity.ok(memberService.findMemberById(memberId)); + @GetMapping + public ResponseEntity> findMemberById(@MemberId final Long memberId) { + + final MemberFindDTO memberFindDTO = memberService.findMemberById(memberId); + + return ApiResponseUtil.success(SuccessMessage.MEMBER_FIND_SUCCESS, memberFindDTO); } @DeleteMapping("/{memberId}") diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java index 2d27240..a96efb3 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java @@ -23,6 +23,7 @@ @Service @RequiredArgsConstructor +@Transactional(readOnly = true) public class MemberService { private final MemberRepository memberRepository; @@ -30,21 +31,26 @@ public class MemberService { private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenRepository refreshTokenRepository; + //멤버가입 @Transactional - public TokenResponse createMember(String token, MemberCreateDTO memberCreate) { + public TokenResponse createMember(MemberCreateDTO memberCreate) { + Member createdMember = memberRepository.save( Member.create(memberCreate.name(), memberCreate.part(), memberCreate.age()) ); - Long createdMemberId = createdMember.getId(); Token issuedToken = jwtTokenProvider.issueTokens(createdMemberId); - updateRefreshToken(issuedToken.refreshToken(), createdMember); + updateRefreshToken(issuedToken.refreshToken(), createdMemberId); return TokenResponse.of(issuedToken.accessToken(), issuedToken.refreshToken(), createdMemberId); } - private void updateRefreshToken(String refreshToken, Member member) { - refreshTokenRepository.save(RefreshToken.of(member.getId(), refreshToken)); + + + + + private void updateRefreshToken(String refreshToken, Long memberId) { + refreshTokenRepository.save(RefreshToken.of(memberId, refreshToken)); } public void findById(final Long memberId) { From 712ec85acbbfa1db58091fbdbad3c3b565ff45df Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Thu, 30 May 2024 18:14:57 +0900 Subject: [PATCH 09/17] =?UTF-8?q?[Feat]=20=ED=86=A0=ED=81=B0=20=EC=9E=AC?= =?UTF-8?q?=EB=B0=9C=EA=B8=89=20api=20=EA=B5=AC=ED=98=84(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/dto/ErrorMessage.java | 1 + .../common/dto/SuccessMessage.java | 2 + .../common/jwt/JwtTokenGenerator.java | 6 +- .../common/jwt/JwtTokenProvider.java | 5 -- .../jwt/auth/CustomAccessDeniedHandler.java | 1 + .../common/jwt/auth/SecurityConfig.java | 4 +- .../jwt/dto/TokenAndUserIdResponse.java | 14 +++++ .../common/jwt/dto/TokenResponse.java | 18 ------ .../controller/MemberController.java | 21 +++++-- .../springFirstSeminar/domain/Member.java | 5 ++ .../service/MemberService.java | 63 +++++++++++++++++-- .../service/dto/ReissueRequest.java | 7 +++ 12 files changed, 107 insertions(+), 40 deletions(-) create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenAndUserIdResponse.java delete mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/dto/ReissueRequest.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java index 263f9d2..7d88d38 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/ErrorMessage.java @@ -26,6 +26,7 @@ public enum ErrorMessage { INVALID_REFRESH_TOKEN_VALUE(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰의 값이 올바르지 않습니다."), EXPIRED_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(),"리프레시 토큰이 만료되었습니다. 다시 로그인해 주세요."), MISMATCH_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰이 일치하지 않습니다."), + REFRESH_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED.value(), "리프레시 토큰을 찾을 수 없습니다."), ; diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java index b828272..b28f2f9 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/dto/SuccessMessage.java @@ -17,6 +17,8 @@ public enum SuccessMessage { BLOG_CREATE_SUCCESS(HttpStatus.CREATED.value(), "블로그 생성이 완료되었습니다."), BLOG_CONTENT_CREATE_SUCCESS(HttpStatus.CREATED.value(), "블로그에 글 작성이 완료되었습니다."), GET_BLOG_CONTENT_SUCCESS(HttpStatus.OK.value(), "블로그 글 가져오기가 완료되었습니다."), + + TOKEN_REISSUE_SUCCESS(HttpStatus.OK.value(), "토큰 재발급에 성공했습니다"), ; private final int status; private final String message; diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java index 879ca44..dc7ab46 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java @@ -2,9 +2,7 @@ import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; -import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; import javax.crypto.SecretKey; @@ -20,8 +18,8 @@ public class JwtTokenGenerator { // @Value("${jwt.refresh-token-expire-time}") // private long REFRESH_TOKEN_EXPIRE_TIME; - private final long accessExpiration = 24 * 60 * 60 * 100L * 14; - private final long refreshExpiration = 24 * 60 * 60 * 1000L * 14; + private final long accessExpiration = 1 * 60 * 1000L; //1분으로 테스트 + private final long refreshExpiration = 60 * 60 * 1000L; //60분으로 테스트 public String generateToken(final Long userId, boolean isAccessToken) { final Date presentDate = new Date(); diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java index 90c69ac..cad7740 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenProvider.java @@ -1,13 +1,8 @@ package org.sopt.springFirstSeminar.common.jwt; -import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtParser; import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.jwt.dto.Token; -import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; import org.springframework.stereotype.Service; @RequiredArgsConstructor diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/CustomAccessDeniedHandler.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/CustomAccessDeniedHandler.java index 3ae38c9..b4fac77 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/CustomAccessDeniedHandler.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/CustomAccessDeniedHandler.java @@ -11,6 +11,7 @@ @Component public class CustomAccessDeniedHandler implements AccessDeniedHandler { + @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { setResponse(response); diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java index f35ed0b..27c66c0 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java @@ -26,7 +26,7 @@ public class SecurityConfig { private final JwtTokenValidator jwtTokenValidator; - private static final String[] AUTH_WHITE_LIST = {"/api/v1/member", "/test"}; + private static final String[] AUTH_WHITE_LIST = {"/api/v1/member/signup", "/test", "/api/v1/member/reissue"}; @Bean SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -46,7 +46,7 @@ SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { auth.requestMatchers(AUTH_WHITE_LIST).permitAll(); auth.anyRequest().authenticated(); }) -// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, jwtTokenValidator), UsernamePasswordAuthenticationFilter.class); + .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, jwtTokenValidator), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenAndUserIdResponse.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenAndUserIdResponse.java new file mode 100644 index 0000000..dafbba9 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenAndUserIdResponse.java @@ -0,0 +1,14 @@ +package org.sopt.springFirstSeminar.common.jwt.dto; + +public record TokenAndUserIdResponse( + String accessToken, + String refreshToken, + Long userId +) { + + public static TokenAndUserIdResponse of(Token token, Long memberId) { + return new TokenAndUserIdResponse(token.accessToken(), token.refreshToken(), memberId); + } +} + + diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java deleted file mode 100644 index 08ff274..0000000 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/dto/TokenResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.sopt.springFirstSeminar.common.jwt.dto; - -public record TokenResponse( - String accessToken, - String refreshToken, - Long userId -) { - - public static TokenResponse of( - String accessToken, - String refreshToken, - Long userId - ) { - return new TokenResponse(accessToken, refreshToken, userId); - } -} - - diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java index 4395f0f..980b8d6 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java @@ -2,23 +2,23 @@ import lombok.RequiredArgsConstructor; -import org.apache.tomcat.util.http.parser.Authorization; import org.sopt.springFirstSeminar.common.ApiResponseUtil; import org.sopt.springFirstSeminar.common.BaseResponse; -import org.sopt.springFirstSeminar.common.Constant; import org.sopt.springFirstSeminar.common.dto.SuccessMessage; import org.sopt.springFirstSeminar.common.jwt.auth.MemberId; -import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; +import org.sopt.springFirstSeminar.common.jwt.dto.TokenAndUserIdResponse; import org.sopt.springFirstSeminar.service.MemberService; import org.sopt.springFirstSeminar.service.dto.MemberCreateDTO; import org.sopt.springFirstSeminar.service.dto.MemberFindDTO; import org.sopt.springFirstSeminar.service.dto.MemberDataDTO; -import org.springframework.http.HttpStatus; +import org.sopt.springFirstSeminar.service.dto.ReissueRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; +import static org.sopt.springFirstSeminar.common.Constant.AUTHORIZATION; + @RestController @RequiredArgsConstructor @RequestMapping("/api/v1/member") @@ -26,10 +26,10 @@ public class MemberController { private final MemberService memberService; - @PostMapping + @PostMapping("signup") public ResponseEntity> postMember(@RequestBody MemberCreateDTO memberCreate) { - final TokenResponse memberJoinResponse = memberService.createMember(memberCreate); + final TokenAndUserIdResponse memberJoinResponse = memberService.createMember(memberCreate); return ApiResponseUtil.success(SuccessMessage.MEMBER_CREATE_SUCCESS, memberJoinResponse); } @@ -42,6 +42,15 @@ public ResponseEntity> findMemberById(@MemberId final Long membe return ApiResponseUtil.success(SuccessMessage.MEMBER_FIND_SUCCESS, memberFindDTO); } + @PostMapping("reissue") + public ResponseEntity> reissue(@RequestHeader(AUTHORIZATION) final String refreshToken, + @RequestBody final ReissueRequest reissueRequest) { + + final TokenAndUserIdResponse reissueTokenResponse = memberService.reissue(refreshToken, reissueRequest); + + return ApiResponseUtil.success(SuccessMessage.TOKEN_REISSUE_SUCCESS, reissueTokenResponse); + } + @DeleteMapping("/{memberId}") public ResponseEntity deleteMemberById(@PathVariable final Long memberId) { memberService.deleteMemberById(memberId); diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/domain/Member.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/domain/Member.java index a4e3489..73fcc7c 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/domain/Member.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/domain/Member.java @@ -4,6 +4,7 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.sopt.springFirstSeminar.service.dto.MemberFindDTO; @Entity @Getter @@ -36,4 +37,8 @@ public static Member create(String name, Part part, int age) { .part(part) .build(); } + + public static Member of(Member member) { + return new Member(member.getName(), member.getPart(), member.getAge()); + } } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java index a96efb3..823f2d5 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java @@ -1,20 +1,22 @@ package org.sopt.springFirstSeminar.service; +import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.dto.ErrorMessage; -import org.sopt.springFirstSeminar.common.jwt.JwtTokenGenerator; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; -import org.sopt.springFirstSeminar.common.jwt.UserAuthentication; +import org.sopt.springFirstSeminar.common.jwt.JwtTokenValidator; import org.sopt.springFirstSeminar.common.jwt.auth.RefreshToken; import org.sopt.springFirstSeminar.common.jwt.auth.redis.repository.RefreshTokenRepository; import org.sopt.springFirstSeminar.common.jwt.dto.Token; -import org.sopt.springFirstSeminar.common.jwt.dto.TokenResponse; +import org.sopt.springFirstSeminar.common.jwt.dto.TokenAndUserIdResponse; import org.sopt.springFirstSeminar.domain.Member; import org.sopt.springFirstSeminar.exception.NotFoundException; +import org.sopt.springFirstSeminar.exception.UnauthorizedException; import org.sopt.springFirstSeminar.repository.MemberRepository; import org.sopt.springFirstSeminar.service.dto.MemberCreateDTO; import org.sopt.springFirstSeminar.service.dto.MemberFindDTO; import org.sopt.springFirstSeminar.service.dto.MemberDataDTO; +import org.sopt.springFirstSeminar.service.dto.ReissueRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -30,10 +32,11 @@ public class MemberService { private final JwtTokenProvider jwtTokenProvider; private final RefreshTokenRepository refreshTokenRepository; + private final JwtTokenValidator jwtTokenValidator; //멤버가입 @Transactional - public TokenResponse createMember(MemberCreateDTO memberCreate) { + public TokenAndUserIdResponse createMember(MemberCreateDTO memberCreate) { Member createdMember = memberRepository.save( Member.create(memberCreate.name(), memberCreate.part(), memberCreate.age()) @@ -42,27 +45,77 @@ public TokenResponse createMember(MemberCreateDTO memberCreate) { Token issuedToken = jwtTokenProvider.issueTokens(createdMemberId); updateRefreshToken(issuedToken.refreshToken(), createdMemberId); - return TokenResponse.of(issuedToken.accessToken(), issuedToken.refreshToken(), createdMemberId); + return TokenAndUserIdResponse.of(issuedToken, createdMemberId); } + @Transactional + public TokenAndUserIdResponse reissue(final String refreshToken, final ReissueRequest reissueRequest) { + + Long memberId = reissueRequest.memberId(); + validateRefreshToken(refreshToken,memberId); + Member member = findMemberBy(memberId); + Token issueedToken = jwtTokenProvider.issueTokens(memberId); + updateRefreshToken(issueedToken.refreshToken(), memberId); + return TokenAndUserIdResponse.of(issueedToken, memberId); + } + private void validateRefreshToken(final String refreshToken, final Long userId) { + try { + jwtTokenValidator.validateRefreshToken(refreshToken); + String storedRefreshToken = getRefreshToken(userId); + jwtTokenValidator.equalsRefreshToken(refreshToken, storedRefreshToken); + } catch (UnauthorizedException e) { + signOut(userId); + throw e; + } + } + + private String getRefreshToken(final Long memberId) { + try { + return getRefreshTokenFromRedis(memberId); + } catch (EntityNotFoundException e) { + throw new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND); + } + } + + private String getRefreshTokenFromRedis(Long userId) { + RefreshToken storedRefreshToken = refreshTokenRepository.findById(userId) + .orElseThrow(() -> new NotFoundException(ErrorMessage.REFRESH_TOKEN_NOT_FOUND)); + return storedRefreshToken.getRefreshToken(); + } + private void updateRefreshToken(String refreshToken, Long memberId) { refreshTokenRepository.save(RefreshToken.of(memberId, refreshToken)); } + public void signOut(final Long memberId) { + Member findMember = findMemberBy(memberId); + deleteRefreshToken(findMember); + } + public void findById(final Long memberId) { findMember(memberId).orElseThrow( () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND)); } + private void deleteRefreshToken(final Member member) { + refreshTokenRepository.deleteById(member.getId()); + } + public MemberFindDTO findMemberById(final Long memberId) { return MemberFindDTO.of(findMember(memberId).orElseThrow( () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND))); } + public Member findMemberBy(final Long memberId) { + return memberRepository.findById(memberId).orElseThrow( + () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND)); + + } + @Transactional public void deleteMemberById(final Long memberId) { Member member = memberRepository.findById(memberId).orElseThrow( diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/dto/ReissueRequest.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/dto/ReissueRequest.java new file mode 100644 index 0000000..f5e3e55 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/dto/ReissueRequest.java @@ -0,0 +1,7 @@ +package org.sopt.springFirstSeminar.service.dto; + +public record ReissueRequest( + Long memberId +) { + +} From 760271906da94e85e14db802c3e4ca698086e2fa Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 31 May 2024 04:11:00 +0900 Subject: [PATCH 10/17] =?UTF-8?q?[Chore]=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=8F=20@Value=ED=99=9C=EC=9A=A9=20=ED=86=A0?= =?UTF-8?q?=ED=81=B0=EB=A7=8C=EB=A3=8C=EC=8B=9C=EA=B0=84=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/jwt/JwtTokenGenerator.java | 16 ++++---- .../controller/MemberController.java | 2 +- .../service/MemberService.java | 38 +++++-------------- .../service/PostService.java | 2 +- 4 files changed, 20 insertions(+), 38 deletions(-) diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java index dc7ab46..30bdb7c 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java @@ -11,15 +11,15 @@ @Component public class JwtTokenGenerator { + @Value("${jwt.secret}") private String secretKey; -// @Value("${jwt.access-token-expire-time}") -// private long ACCESS_TOKEN_EXPIRE_TIME; -// @Value("${jwt.refresh-token-expire-time}") -// private long REFRESH_TOKEN_EXPIRE_TIME; - private final long accessExpiration = 1 * 60 * 1000L; //1분으로 테스트 - private final long refreshExpiration = 60 * 60 * 1000L; //60분으로 테스트 + @Value("${jwt.access-token-expire-time}") //1분 + private long ACCESS_TOKEN_EXPIRE_TIME; + + @Value("${jwt.refresh-token-expire-time}") //1시간 + private long REFRESH_TOKEN_EXPIRE_TIME; public String generateToken(final Long userId, boolean isAccessToken) { final Date presentDate = new Date(); @@ -47,9 +47,9 @@ private Date generateExpireDataByToken(final boolean isAccessToken, Date present //토근에 따라 만료시간 다름 private long setExpireTimeByToken(final boolean isAccessToken) { if (isAccessToken) { - return accessExpiration; + return ACCESS_TOKEN_EXPIRE_TIME; } else { - return refreshExpiration; + return REFRESH_TOKEN_EXPIRE_TIME; } } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java index 980b8d6..362cd22 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java @@ -37,7 +37,7 @@ public ResponseEntity> postMember(@RequestBody MemberCreateDTO m @GetMapping public ResponseEntity> findMemberById(@MemberId final Long memberId) { - final MemberFindDTO memberFindDTO = memberService.findMemberById(memberId); + final MemberFindDTO memberFindDTO = MemberFindDTO.of(memberService.findMemberById(memberId)); return ApiResponseUtil.success(SuccessMessage.MEMBER_FIND_SUCCESS, memberFindDTO); } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java index 823f2d5..6105518 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java @@ -53,22 +53,19 @@ public TokenAndUserIdResponse reissue(final String refreshToken, final ReissueRe Long memberId = reissueRequest.memberId(); validateRefreshToken(refreshToken,memberId); - Member member = findMemberBy(memberId); + Member member = findMemberById(memberId); Token issueedToken = jwtTokenProvider.issueTokens(memberId); updateRefreshToken(issueedToken.refreshToken(), memberId); return TokenAndUserIdResponse.of(issueedToken, memberId); - - } - - private void validateRefreshToken(final String refreshToken, final Long userId) { + private void validateRefreshToken(final String refreshToken, final Long memberId) { try { jwtTokenValidator.validateRefreshToken(refreshToken); - String storedRefreshToken = getRefreshToken(userId); + String storedRefreshToken = getRefreshToken(memberId); jwtTokenValidator.equalsRefreshToken(refreshToken, storedRefreshToken); } catch (UnauthorizedException e) { - signOut(userId); + signOut(memberId); throw e; } } @@ -87,33 +84,24 @@ private String getRefreshTokenFromRedis(Long userId) { return storedRefreshToken.getRefreshToken(); } - private void updateRefreshToken(String refreshToken, Long memberId) { + @Transactional + public void updateRefreshToken(String refreshToken, Long memberId) { refreshTokenRepository.save(RefreshToken.of(memberId, refreshToken)); } public void signOut(final Long memberId) { - Member findMember = findMemberBy(memberId); + Member findMember = findMemberById(memberId); deleteRefreshToken(findMember); } - public void findById(final Long memberId) { - findMember(memberId).orElseThrow( - () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND)); - } - - private void deleteRefreshToken(final Member member) { + @Transactional + public void deleteRefreshToken(final Member member) { refreshTokenRepository.deleteById(member.getId()); } - public MemberFindDTO findMemberById(final Long memberId) { - return MemberFindDTO.of(findMember(memberId).orElseThrow( - () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND))); - } - - public Member findMemberBy(final Long memberId) { + public Member findMemberById(final Long memberId) { return memberRepository.findById(memberId).orElseThrow( () -> new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND)); - } @Transactional @@ -123,10 +111,6 @@ public void deleteMemberById(final Long memberId) { memberRepository.delete(member); } - public Optional findMember(final Long memberId) { - return memberRepository.findById(memberId); - } - public List getAllMemberList() { // .stream : 메서드가 반환한 컬렉션을 스트림으로 변환함, 스트림을 사용하면 데이터를 순차적으로 처리함 @@ -135,6 +119,4 @@ public List getAllMemberList() { //.toList : 스트림을 리스트로 변환 return memberRepository.findAll().stream().map(member -> MemberDataDTO.of(member)).toList(); } - - } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/PostService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/PostService.java index 3a3ecd8..3cf6457 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/PostService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/PostService.java @@ -33,7 +33,7 @@ public class PostService { @Transactional public String postContent(final Long memberId, final Long blogId, final BlogContentRequestDTO blogContentRequestDTO) { - memberService.findById(memberId); + memberService.findMemberById(memberId); Blog blog = fineBlogById(blogId); From 1e3ebc34c9af1c7b8a30f8ad26229cadbe4e0bc3 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 31 May 2024 04:11:13 +0900 Subject: [PATCH 11/17] =?UTF-8?q?[Chore]=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/jwt/auth/filter/JwtAuthenticationFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java index 71e3d23..72b6b25 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java @@ -40,7 +40,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse jwtTokenValidator.validateAccessToken(accessToken); doAuthentication(request, jwtTokenProvider.getSubject(accessToken)); } catch(UnauthorizedException e){ - log.info("———"); + throw new RuntimeException(String.valueOf(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION)); } filterChain.doFilter(request, response); } From aa8f2977e53858b311e72bdcb0c7a79c07499e31 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 31 May 2024 04:11:32 +0900 Subject: [PATCH 12/17] =?UTF-8?q?[Chore]=20=EC=BD=94=EB=93=9C=20=EC=A0=95?= =?UTF-8?q?=EB=A6=AC(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java index fa6b95e..98941e5 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/RefreshToken.java @@ -27,8 +27,4 @@ public static RefreshToken of(final Long memberId, final String refreshToken) { .refreshToken(refreshToken) .build(); } - - public void updateRefreshToken(final String refreshToken) { - this.refreshToken = refreshToken; - } } From 62275876b09d443e8fced7b9213b94051e0f1e1a Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Fri, 31 May 2024 04:32:31 +0900 Subject: [PATCH 13/17] =?UTF-8?q?[Chore]=20=EC=BD=94=EB=93=9C=EC=A0=95?= =?UTF-8?q?=EB=A6=AC(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../jwt/auth/HandlerMethodArgumentResolver.java | 14 -------------- .../common/jwt/auth/UserIdArgumentResolver.java | 2 -- .../jwt/auth/filter/JwtAuthenticationFilter.java | 2 +- 3 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java deleted file mode 100644 index aea06f9..0000000 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/HandlerMethodArgumentResolver.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.sopt.springFirstSeminar.common.jwt.auth; - -import org.springframework.core.MethodParameter; -import org.springframework.lang.Nullable; -import org.springframework.web.bind.support.WebDataBinderFactory; -import org.springframework.web.context.request.NativeWebRequest; -import org.springframework.web.method.support.ModelAndViewContainer; - -public interface HandlerMethodArgumentResolver { - boolean supportsParameter(MethodParameter parameter); - - @Nullable - Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception; -} diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java index f8552f4..1997de4 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java @@ -10,7 +10,6 @@ @Component public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { - @Override public boolean supportsParameter(MethodParameter parameter) { @@ -24,7 +23,6 @@ public boolean supportsParameter(MethodParameter parameter) { return hasUserIdAnnotation && isLongType; } - //SecurityContextHolder에 있는 UserId 추출 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return SecurityContextHolder.getContext() diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java index 72b6b25..83e223c 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java @@ -40,7 +40,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse jwtTokenValidator.validateAccessToken(accessToken); doAuthentication(request, jwtTokenProvider.getSubject(accessToken)); } catch(UnauthorizedException e){ - throw new RuntimeException(String.valueOf(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION)); +// throw new RuntimeException(String.valueOf(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION)); } filterChain.doFilter(request, response); } From faa1ea46b3411304135f08d2738cfa636c1bc549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=95=EC=9C=A4?= <63058347+JungYoonShin@users.noreply.github.com> Date: Sun, 2 Jun 2024 23:38:06 +0900 Subject: [PATCH 14/17] Delete springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java --- .../common/jwt/JwtTokenGenerator.java | 60 ------------------- 1 file changed, 60 deletions(-) delete mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java deleted file mode 100644 index 30bdb7c..0000000 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.sopt.springFirstSeminar.common.jwt; - -import io.jsonwebtoken.*; -import io.jsonwebtoken.security.Keys; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import javax.crypto.SecretKey; -import java.util.Base64; -import java.util.Date; - -@Component -public class JwtTokenGenerator { - - @Value("${jwt.secret}") - private String secretKey; - - @Value("${jwt.access-token-expire-time}") //1분 - private long ACCESS_TOKEN_EXPIRE_TIME; - - @Value("${jwt.refresh-token-expire-time}") //1시간 - private long REFRESH_TOKEN_EXPIRE_TIME; - - public String generateToken(final Long userId, boolean isAccessToken) { - final Date presentDate = new Date(); - final Date expireDate = generateExpireDataByToken(isAccessToken, presentDate); - - return Jwts.builder() - .setHeaderParam(Header.TYPE, Header.JWT_TYPE) - .setSubject(String.valueOf(userId)) - .setIssuedAt(presentDate) - .setExpiration(expireDate) - .signWith(getSigningKey(), SignatureAlgorithm.HS256) //여기서 어떤 알고리즘을 사용할 지를 명시적으로 적어주는게 좋음, 안 적어주면 라이브러리 기본 설정에 의존하게됨 - .compact(); - } - - public JwtParser getJwtParser() { - return Jwts.parserBuilder() - .setSigningKey(getSigningKey()) - .build(); - } - - private Date generateExpireDataByToken(final boolean isAccessToken, Date presentDate) { - return new Date(presentDate.getTime() + setExpireTimeByToken(isAccessToken)); - } - - //토근에 따라 만료시간 다름 - private long setExpireTimeByToken(final boolean isAccessToken) { - if (isAccessToken) { - return ACCESS_TOKEN_EXPIRE_TIME; - } else { - return REFRESH_TOKEN_EXPIRE_TIME; - } - } - - public SecretKey getSigningKey() { - String encodedKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); //SecretKey 통해 서명 생성 - return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용 - } -} From b62dd35640b3f38c8443131cf306dd791adbe2e0 Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Mon, 3 Jun 2024 00:06:30 +0900 Subject: [PATCH 15/17] =?UTF-8?q?[Chore]=20=EC=9E=98=EB=AA=BB=EB=90=9C=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=88=98=EC=A0=95=20=EB=B3=80=EA=B2=BD(#1?= =?UTF-8?q?3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../common/jwt/JwtTokenGenerator.java | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java new file mode 100644 index 0000000..381b8d6 --- /dev/null +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java @@ -0,0 +1,60 @@ +package org.sopt.springFirstSeminar.common.jwt; + +import io.jsonwebtoken.*; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.util.Base64; +import java.util.Date; + +@Component +public class JwtTokenGenerator { + + @Value("${jwt.secret}") + private String secretKey; + + @Value("${jwt.access-token-expire-time}") //1분 + private long ACCESS_TOKEN_EXPIRE_TIME; + + @Value("${jwt.refresh-token-expire-time}") //1시간 + private long REFRESH_TOKEN_EXPIRE_TIME; + + public String generateToken(final Long userId, boolean isAccessToken) { + final Date presentDate = new Date(); + final Date expireDate = generateExpireDataByToken(isAccessToken, presentDate); + + return Jwts.builder() + .setHeaderParam(Header.TYPE, Header.JWT_TYPE) + .setSubject(String.valueOf(userId)) + .setIssuedAt(presentDate) + .setExpiration(expireDate) + .signWith(getSigningKey(), SignatureAlgorithm.HS256) //여기서 어떤 알고리즘을 사용할 지를 명시적으로 적어주는게 좋음, 안 적어주면 라이브러리 기본 설정에 의존하게됨 + .compact(); + } + + public JwtParser getJwtParser() { + return Jwts.parserBuilder() + .setSigningKey(getSigningKey()) + .build(); + } + + private Date generateExpireDataByToken(final boolean isAccessToken, Date presentDate) { + return new Date(presentDate.getTime() + setExpireTimeByToken(isAccessToken)); + } + + //토근에 따라 만료시간 다름 + private long setExpireTimeByToken(final boolean isAccessToken) { + if (isAccessToken) { + return ACCESS_TOKEN_EXPIRE_TIME; + } else { + return REFRESH_TOKEN_EXPIRE_TIME; + } + } + + public SecretKey getSigningKey() { + String encodedKey = Base64.getEncoder().encodeToString(secretKey.getBytes()); //SecretKey 통해 서명 생성 + return Keys.hmacShaKeyFor(encodedKey.getBytes()); //일반적으로 HMAC (Hash-based Message Authentication Code) 알고리즘 사용 + } +} From 192c4ed3ce39255b1970cb29628b1c29af0f0cab Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Wed, 5 Jun 2024 04:29:34 +0900 Subject: [PATCH 16/17] =?UTF-8?q?[Feat]=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springFirstSeminar/common/jwt/JwtTokenGenerator.java | 4 ++-- .../common/jwt/auth/UserIdArgumentResolver.java | 6 +++++- .../common/jwt/auth/{ => config}/SecurityConfig.java | 3 ++- .../common/jwt/auth/{ => config}/WebConfig.java | 3 ++- .../common/jwt/auth/filter/JwtAuthenticationFilter.java | 3 ++- 5 files changed, 13 insertions(+), 6 deletions(-) rename springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/{ => config}/SecurityConfig.java (95%) rename springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/{ => config}/WebConfig.java (81%) diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java index 381b8d6..7f8b4ba 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/JwtTokenGenerator.java @@ -23,7 +23,7 @@ public class JwtTokenGenerator { public String generateToken(final Long userId, boolean isAccessToken) { final Date presentDate = new Date(); - final Date expireDate = generateExpireDataByToken(isAccessToken, presentDate); + final Date expireDate = generateExpireDateByToken(isAccessToken, presentDate); return Jwts.builder() .setHeaderParam(Header.TYPE, Header.JWT_TYPE) @@ -40,7 +40,7 @@ public JwtParser getJwtParser() { .build(); } - private Date generateExpireDataByToken(final boolean isAccessToken, Date presentDate) { + private Date generateExpireDateByToken(final boolean isAccessToken, Date presentDate) { return new Date(presentDate.getTime() + setExpireTimeByToken(isAccessToken)); } diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java index 1997de4..edd7882 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/UserIdArgumentResolver.java @@ -8,6 +8,8 @@ import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +//Argument Resolver를 사용하면 컨트롤러 메서드의 파라미터 중 특정 조건에 맞는 파라미터가 있다면, +// 요청에 들어온 값을 이용해 원하는 객체를 만들어 바인딩해줄 수 있다. @Component public class UserIdArgumentResolver implements HandlerMethodArgumentResolver { @Override @@ -17,12 +19,14 @@ public boolean supportsParameter(MethodParameter parameter) { boolean hasUserIdAnnotation = parameter.hasParameterAnnotation(MemberId.class); //타입이 같은지 확인 - boolean isLongType = Long.class.isAssignableFrom(parameter.getParameterType()); +// boolean isLongType = Long.class.isAssignableFrom(parameter.getParameterType()); + boolean isLongType = Long.class.equals(parameter.getParameterType()); //둘 다 true면 아래 resolveArgument 메서드 실행 return hasUserIdAnnotation && isLongType; } + //바인딩할 객체로 리턴하는 메서드 @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { return SecurityContextHolder.getContext() diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/config/SecurityConfig.java similarity index 95% rename from springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java rename to springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/config/SecurityConfig.java index 27c66c0..de2356e 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/SecurityConfig.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/config/SecurityConfig.java @@ -1,8 +1,9 @@ -package org.sopt.springFirstSeminar.common.jwt.auth; +package org.sopt.springFirstSeminar.common.jwt.auth.config; import lombok.RequiredArgsConstructor; import org.sopt.springFirstSeminar.common.jwt.JwtTokenProvider; import org.sopt.springFirstSeminar.common.jwt.JwtTokenValidator; +import org.sopt.springFirstSeminar.common.jwt.auth.CustomAccessDeniedHandler; import org.sopt.springFirstSeminar.common.jwt.auth.filter.CustomJwtAuthenticationEntryPoint; import org.sopt.springFirstSeminar.common.jwt.auth.filter.JwtAuthenticationFilter; import org.springframework.context.annotation.Bean; diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/WebConfig.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/config/WebConfig.java similarity index 81% rename from springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/WebConfig.java rename to springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/config/WebConfig.java index cbc78b5..60bae8e 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/WebConfig.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/config/WebConfig.java @@ -1,7 +1,8 @@ -package org.sopt.springFirstSeminar.common.jwt.auth; +package org.sopt.springFirstSeminar.common.jwt.auth.config; import lombok.RequiredArgsConstructor; +import org.sopt.springFirstSeminar.common.jwt.auth.UserIdArgumentResolver; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java index 83e223c..7486e69 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/common/jwt/auth/filter/JwtAuthenticationFilter.java @@ -40,7 +40,8 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse jwtTokenValidator.validateAccessToken(accessToken); doAuthentication(request, jwtTokenProvider.getSubject(accessToken)); } catch(UnauthorizedException e){ -// throw new RuntimeException(String.valueOf(ErrorMessage.JWT_UNAUTHORIZED_EXCEPTION)); + //여기서 throw를 사용하면 filterChain.doFilter(request, response)가 실행되지 않고, + log.error("JwtAuthentication Authentication Exception Occurs!"); } filterChain.doFilter(request, response); } From 3d703274ee6ddfd511f65106e81404f606ff2cff Mon Sep 17 00:00:00 2001 From: Kwak Seong Joon Date: Wed, 5 Jun 2024 19:45:15 +0900 Subject: [PATCH 17/17] =?UTF-8?q?[Chore]=20=EC=BD=94=EB=93=9C=20=EB=A6=AC?= =?UTF-8?q?=EB=B7=B0=20=EB=B0=98=EC=98=81(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../springFirstSeminar/controller/MemberController.java | 4 ++-- .../org/sopt/springFirstSeminar/service/MemberService.java | 6 +----- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java index 362cd22..e5bf8a1 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/controller/MemberController.java @@ -26,7 +26,7 @@ public class MemberController { private final MemberService memberService; - @PostMapping("signup") + @PostMapping("/signup") public ResponseEntity> postMember(@RequestBody MemberCreateDTO memberCreate) { final TokenAndUserIdResponse memberJoinResponse = memberService.createMember(memberCreate); @@ -42,7 +42,7 @@ public ResponseEntity> findMemberById(@MemberId final Long membe return ApiResponseUtil.success(SuccessMessage.MEMBER_FIND_SUCCESS, memberFindDTO); } - @PostMapping("reissue") + @PostMapping("/reissue") public ResponseEntity> reissue(@RequestHeader(AUTHORIZATION) final String refreshToken, @RequestBody final ReissueRequest reissueRequest) { diff --git a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java index 6105518..48466bd 100644 --- a/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java +++ b/springFirstSeminar/src/main/java/org/sopt/springFirstSeminar/service/MemberService.java @@ -71,11 +71,7 @@ private void validateRefreshToken(final String refreshToken, final Long memberId } private String getRefreshToken(final Long memberId) { - try { - return getRefreshTokenFromRedis(memberId); - } catch (EntityNotFoundException e) { - throw new NotFoundException(ErrorMessage.MEMBER_NOT_FOUND); - } + return getRefreshTokenFromRedis(memberId); } private String getRefreshTokenFromRedis(Long userId) {