diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthService.java b/src/main/java/com/example/solidconnection/auth/service/AuthService.java index 496e48724..6053e8716 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthService.java @@ -2,6 +2,7 @@ import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; +import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import lombok.RequiredArgsConstructor; @@ -17,6 +18,7 @@ public class AuthService { private final AuthTokenProvider authTokenProvider; + private final TokenBlackListService tokenBlackListService; /* * 로그아웃한다. @@ -26,7 +28,7 @@ public class AuthService { public void signOut(String token) { AccessToken accessToken = authTokenProvider.toAccessToken(token); authTokenProvider.deleteRefreshTokenByAccessToken(accessToken); - authTokenProvider.addToBlacklist(accessToken); + tokenBlackListService.addToBlacklist(accessToken); } /* diff --git a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java index fa2a064cc..2ff3e5650 100644 --- a/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/AuthTokenProvider.java @@ -1,49 +1,38 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; -import com.example.solidconnection.util.JwtUtils; +import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import java.util.Objects; @Component -public class AuthTokenProvider extends TokenProvider implements BlacklistChecker { +@RequiredArgsConstructor +public class AuthTokenProvider { - public AuthTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { - super(jwtProperties, redisTemplate); - } + private final RedisTemplate redisTemplate; + private final TokenProvider tokenProvider; public AccessToken generateAccessToken(Subject subject) { - String token = generateToken(subject.value(), TokenType.ACCESS); + String token = tokenProvider.generateToken(subject.value(), TokenType.ACCESS); return new AccessToken(subject, token); } public RefreshToken generateAndSaveRefreshToken(Subject subject) { - String token = generateToken(subject.value(), TokenType.REFRESH); - saveToken(token, TokenType.REFRESH); + String token = tokenProvider.generateToken(subject.value(), TokenType.REFRESH); + tokenProvider.saveToken(token, TokenType.REFRESH); return new RefreshToken(subject, token); } /* - * 액세스 토큰을 블랙리스트에 저장한다. - * - key = BLACKLIST:{accessToken} - * - value = "signOut" -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. - * */ - public void addToBlacklist(AccessToken accessToken) { - String blackListKey = TokenType.BLACKLIST.addPrefix(accessToken.token()); - redisTemplate.opsForValue().set(blackListKey, "signOut"); - } - - /* - * 유효한 리프레시 토큰인지 확인한다. - * - 요청된 토큰과 같은 subject 의 리프레시 토큰을 조회한다. - * - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다. - * */ + * 유효한 리프레시 토큰인지 확인한다. + * - 요청된 토큰과 같은 subject 의 리프레시 토큰을 조회한다. + * - 조회된 리프레시 토큰과 요청된 토큰이 같은지 비교한다. + * */ public boolean isValidRefreshToken(String requestedRefreshToken) { - String subject = JwtUtils.parseSubject(requestedRefreshToken, jwtProperties.secret()); + String subject = tokenProvider.parseSubject(requestedRefreshToken); String refreshTokenKey = TokenType.REFRESH.addPrefix(subject); String foundRefreshToken = redisTemplate.opsForValue().get(refreshTokenKey); return Objects.equals(requestedRefreshToken, foundRefreshToken); @@ -55,14 +44,8 @@ public void deleteRefreshTokenByAccessToken(AccessToken accessToken) { redisTemplate.delete(refreshTokenKey); } - @Override - public boolean isTokenBlacklisted(String accessToken) { - String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken); - return redisTemplate.hasKey(blackListTokenKey); - } - public Subject parseSubject(String token) { - String subject = JwtUtils.parseSubject(token, jwtProperties.secret()); + String subject = tokenProvider.parseSubject(token); return new Subject(subject); } diff --git a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java index d16cb5134..d1ebe3b52 100644 --- a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java @@ -1,9 +1,7 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; -import com.example.solidconnection.util.JwtUtils; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -14,11 +12,11 @@ @RequiredArgsConstructor public class CommonSignUpTokenProvider { - private final JwtProperties jwtProperties; + private final TokenProvider tokenProvider; public AuthType parseAuthType(String signUpToken) { try { - String authTypeStr = JwtUtils.parseClaims(signUpToken, jwtProperties.secret()).get(AUTH_TYPE_CLAIM_KEY, String.class); + String authTypeStr = tokenProvider.parseClaims(signUpToken).get(AUTH_TYPE_CLAIM_KEY, String.class); return AuthType.valueOf(authTypeStr); } catch (Exception e) { throw new CustomException(SIGN_UP_TOKEN_INVALID); diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java index c6bee80ab..fe9a176d3 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java @@ -2,12 +2,13 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Component; @@ -19,22 +20,18 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; -import static com.example.solidconnection.util.JwtUtils.parseClaims; -import static com.example.solidconnection.util.JwtUtils.parseSubject; @Component -public class EmailSignUpTokenProvider extends TokenProvider { +@RequiredArgsConstructor +public class EmailSignUpTokenProvider { static final String PASSWORD_CLAIM_KEY = "password"; static final String AUTH_TYPE_CLAIM_KEY = "authType"; private final PasswordEncoder passwordEncoder; - - public EmailSignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate, - PasswordEncoder passwordEncoder) { - super(jwtProperties, redisTemplate); - this.passwordEncoder = passwordEncoder; - } + private final JwtProperties jwtProperties; + private final RedisTemplate redisTemplate; + private final TokenProvider tokenProvider; public String generateAndSaveSignUpToken(EmailSignUpTokenRequest request) { String email = request.email(); @@ -54,7 +51,7 @@ public String generateAndSaveSignUpToken(EmailSignUpTokenRequest request) { .setExpiration(expiredDate) .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) .compact(); - return saveToken(signUpToken, TokenType.SIGN_UP); + return tokenProvider.saveToken(signUpToken, TokenType.SIGN_UP); } public void validateSignUpToken(String token) { @@ -65,7 +62,7 @@ public void validateSignUpToken(String token) { private void validateFormatAndExpiration(String token) { try { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); Objects.requireNonNull(claims.getSubject()); String encodedPassword = claims.get(PASSWORD_CLAIM_KEY, String.class); Objects.requireNonNull(encodedPassword); @@ -82,11 +79,11 @@ private void validateIssuedByServer(String email) { } public String parseEmail(String token) { - return parseSubject(token, jwtProperties.secret()); + return tokenProvider.parseSubject(token); } public String parseEncodedPassword(String token) { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); return claims.get(PASSWORD_CLAIM_KEY, String.class); } } diff --git a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java index 0f3552db2..2cd93fb8e 100644 --- a/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/TokenProvider.java @@ -1,47 +1,15 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; -import com.example.solidconnection.security.config.JwtProperties; import io.jsonwebtoken.Claims; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.springframework.data.redis.core.RedisTemplate; -import java.util.Date; -import java.util.concurrent.TimeUnit; +public interface TokenProvider { -import static com.example.solidconnection.util.JwtUtils.parseSubject; + String generateToken(String string, TokenType tokenType); -public abstract class TokenProvider { + String saveToken(String token, TokenType tokenType); - protected final JwtProperties jwtProperties; - protected final RedisTemplate redisTemplate; + String parseSubject(String token); - public TokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { - this.jwtProperties = jwtProperties; - this.redisTemplate = redisTemplate; - } - - protected final String generateToken(String string, TokenType tokenType) { - Claims claims = Jwts.claims().setSubject(string); - Date now = new Date(); - Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime()); - return Jwts.builder() - .setClaims(claims) - .setIssuedAt(now) - .setExpiration(expiredDate) - .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) - .compact(); - } - - protected final String saveToken(String token, TokenType tokenType) { - String subject = parseSubject(token, jwtProperties.secret()); - redisTemplate.opsForValue().set( - tokenType.addPrefix(subject), - token, - tokenType.getExpireTime(), - TimeUnit.MILLISECONDS - ); - return token; - } + Claims parseClaims(String token); } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java index 8fa290d30..1aae0338e 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java @@ -2,12 +2,13 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; @@ -18,17 +19,16 @@ import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.common.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; -import static com.example.solidconnection.util.JwtUtils.parseClaims; -import static com.example.solidconnection.util.JwtUtils.parseSubject; @Component -public class OAuthSignUpTokenProvider extends TokenProvider { +@RequiredArgsConstructor +public class OAuthSignUpTokenProvider { static final String AUTH_TYPE_CLAIM_KEY = "authType"; - public OAuthSignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { - super(jwtProperties, redisTemplate); - } + private final JwtProperties jwtProperties; + private final RedisTemplate redisTemplate; + private final TokenProvider tokenProvider; public String generateAndSaveSignUpToken(String email, AuthType authType) { Map authTypeClaim = new HashMap<>(Map.of(AUTH_TYPE_CLAIM_KEY, authType)); @@ -42,7 +42,7 @@ public String generateAndSaveSignUpToken(String email, AuthType authType) { .setExpiration(expiredDate) .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) .compact(); - return saveToken(signUpToken, TokenType.SIGN_UP); + return tokenProvider.saveToken(signUpToken, TokenType.SIGN_UP); } public void validateSignUpToken(String token) { @@ -53,7 +53,7 @@ public void validateSignUpToken(String token) { private void validateFormatAndExpiration(String token) { try { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); Objects.requireNonNull(claims.getSubject()); String serializedAuthType = claims.get(AUTH_TYPE_CLAIM_KEY, String.class); AuthType.valueOf(serializedAuthType); @@ -70,11 +70,11 @@ private void validateIssuedByServer(String email) { } public String parseEmail(String token) { - return parseSubject(token, jwtProperties.secret()); + return tokenProvider.parseSubject(token); } public AuthType parseAuthType(String token) { - Claims claims = parseClaims(token, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(token); String authTypeStr = claims.get(AUTH_TYPE_CLAIM_KEY, String.class); return AuthType.valueOf(authTypeStr); } diff --git a/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java new file mode 100644 index 000000000..839e58362 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/token/JwtTokenProvider.java @@ -0,0 +1,67 @@ +package com.example.solidconnection.auth.token; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.token.config.JwtProperties; +import com.example.solidconnection.common.exception.CustomException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.concurrent.TimeUnit; + +import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; + +@Component +@RequiredArgsConstructor +public class JwtTokenProvider implements TokenProvider { + + private final JwtProperties jwtProperties; + private final RedisTemplate redisTemplate; + + @Override + public final String generateToken(String string, TokenType tokenType) { + Claims claims = Jwts.claims().setSubject(string); + Date now = new Date(); + Date expiredDate = new Date(now.getTime() + tokenType.getExpireTime()); + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(expiredDate) + .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) + .compact(); + } + + @Override + public final String saveToken(String token, TokenType tokenType) { + String subject = parseSubject(token); + redisTemplate.opsForValue().set( + tokenType.addPrefix(subject), + token, + tokenType.getExpireTime(), + TimeUnit.MILLISECONDS + ); + return token; + } + + @Override + public String parseSubject(String token) { + return parseClaims(token).getSubject(); + } + + @Override + public Claims parseClaims(String token) { + try { + return Jwts.parser() + .setSigningKey(jwtProperties.secret()) + .parseClaimsJws(token) + .getBody(); + } catch (Exception e) { + throw new CustomException(INVALID_TOKEN); + } + } +} diff --git a/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java new file mode 100644 index 000000000..4175a577f --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/token/TokenBlackListService.java @@ -0,0 +1,34 @@ +package com.example.solidconnection.auth.token; + +import com.example.solidconnection.auth.service.AccessToken; +import com.example.solidconnection.security.filter.BlacklistChecker; +import lombok.RequiredArgsConstructor; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; + +@Component +@RequiredArgsConstructor +public class TokenBlackListService implements BlacklistChecker { + + private static final String SIGN_OUT_VALUE = "signOut"; + + private final RedisTemplate redisTemplate; + + /* + * 액세스 토큰을 블랙리스트에 저장한다. + * - key = BLACKLIST:{accessToken} + * - value = {SIGN_OUT_VALUE} -> key 의 존재만 확인하므로, value 에는 무슨 값이 들어가도 상관없다. + * */ + public void addToBlacklist(AccessToken accessToken) { + String blackListKey = BLACKLIST.addPrefix(accessToken.token()); + redisTemplate.opsForValue().set(blackListKey, SIGN_OUT_VALUE); + } + + @Override + public boolean isTokenBlacklisted(String accessToken) { + String blackListTokenKey = BLACKLIST.addPrefix(accessToken); + return redisTemplate.hasKey(blackListTokenKey); + } +} diff --git a/src/main/java/com/example/solidconnection/security/config/JwtProperties.java b/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java similarity index 75% rename from src/main/java/com/example/solidconnection/security/config/JwtProperties.java rename to src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java index f4afa5245..bf5180218 100644 --- a/src/main/java/com/example/solidconnection/security/config/JwtProperties.java +++ b/src/main/java/com/example/solidconnection/auth/token/config/JwtProperties.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.security.config; +package com.example.solidconnection.auth.token.config; import org.springframework.boot.context.properties.ConfigurationProperties; diff --git a/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java b/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java new file mode 100644 index 000000000..8bbbc30e4 --- /dev/null +++ b/src/main/java/com/example/solidconnection/security/filter/AuthorizationHeaderParser.java @@ -0,0 +1,29 @@ +package com.example.solidconnection.security.filter; + +import jakarta.servlet.http.HttpServletRequest; +import org.springframework.stereotype.Component; + +import java.util.Optional; + +@Component +public class AuthorizationHeaderParser { + + private static final String TOKEN_HEADER = "Authorization"; + private static final String TOKEN_PREFIX = "Bearer "; + private static final int TOKEN_PREFIX_LENGTH = TOKEN_PREFIX.length(); + + public Optional parseToken(HttpServletRequest request) { + String token = request.getHeader(TOKEN_HEADER); + if (isInvalidFormat(token)) { + return Optional.empty(); + } + return Optional.of(token.substring(TOKEN_PREFIX_LENGTH)); + } + + private boolean isInvalidFormat(String token) { + return token == null || + token.isBlank() || + !token.startsWith(TOKEN_PREFIX) || + token.substring(TOKEN_PREFIX_LENGTH).isBlank(); + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java b/src/main/java/com/example/solidconnection/security/filter/BlacklistChecker.java similarity index 61% rename from src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java rename to src/main/java/com/example/solidconnection/security/filter/BlacklistChecker.java index b4e174906..f093d8e8f 100644 --- a/src/main/java/com/example/solidconnection/auth/service/BlacklistChecker.java +++ b/src/main/java/com/example/solidconnection/security/filter/BlacklistChecker.java @@ -1,4 +1,4 @@ -package com.example.solidconnection.auth.service; +package com.example.solidconnection.security.filter; public interface BlacklistChecker { diff --git a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java index 39917d42e..8ee6a98e4 100644 --- a/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/JwtAuthenticationFilter.java @@ -15,8 +15,7 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; - -import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; +import java.util.Optional; @Component @@ -24,18 +23,19 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManager authenticationManager; + private final AuthorizationHeaderParser authorizationHeaderParser; @Override public void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - String token = parseTokenFromRequest(request); - if (token == null) { + Optional token = authorizationHeaderParser.parseToken(request); + if (token.isEmpty()) { filterChain.doFilter(request, response); return; } - JwtAuthentication authToken = createAuthentication(token); + JwtAuthentication authToken = createAuthentication(token.get()); Authentication auth = authenticationManager.authenticate(authToken); SecurityContextHolder.getContext().setAuthentication(auth); diff --git a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java index 5c51c53cd..c3926d67f 100644 --- a/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java +++ b/src/main/java/com/example/solidconnection/security/filter/SignOutCheckFilter.java @@ -1,6 +1,5 @@ package com.example.solidconnection.security.filter; -import com.example.solidconnection.auth.service.BlacklistChecker; import com.example.solidconnection.common.exception.CustomException; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; @@ -12,28 +11,29 @@ import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Optional; import static com.example.solidconnection.common.exception.ErrorCode.USER_ALREADY_SIGN_OUT; -import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; @Component @RequiredArgsConstructor public class SignOutCheckFilter extends OncePerRequestFilter { - private final BlacklistChecker tokenBlacklistChecker; + private final AuthorizationHeaderParser authorizationHeaderParser; + private final BlacklistChecker blacklistChecker; @Override protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull FilterChain filterChain) throws ServletException, IOException { - String token = parseTokenFromRequest(request); - if (token != null && hasSignedOut(token)) { + Optional token = authorizationHeaderParser.parseToken(request); + if (token.isPresent() && hasSignedOut(token.get())) { throw new CustomException(USER_ALREADY_SIGN_OUT); } filterChain.doFilter(request, response); } private boolean hasSignedOut(String accessToken) { - return tokenBlacklistChecker.isTokenBlacklisted(accessToken); + return blacklistChecker.isTokenBlacklisted(accessToken); } } diff --git a/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java b/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java index 6ce43b97c..a00b77f9a 100644 --- a/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java +++ b/src/main/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProvider.java @@ -1,8 +1,8 @@ package com.example.solidconnection.security.provider; +import com.example.solidconnection.auth.service.TokenProvider; import com.example.solidconnection.security.authentication.JwtAuthentication; import com.example.solidconnection.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import lombok.RequiredArgsConstructor; @@ -11,21 +11,19 @@ import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; -import static com.example.solidconnection.util.JwtUtils.parseSubject; - @Component @RequiredArgsConstructor public class SiteUserAuthenticationProvider implements AuthenticationProvider { - private final JwtProperties jwtProperties; private final SiteUserDetailsService siteUserDetailsService; + private final TokenProvider tokenProvider; @Override public Authentication authenticate(Authentication auth) throws AuthenticationException { JwtAuthentication jwtAuth = (JwtAuthentication) auth; String token = jwtAuth.getToken(); - String username = parseSubject(token, jwtProperties.secret()); + String username = tokenProvider.parseSubject(token); SiteUserDetails userDetails = (SiteUserDetails) siteUserDetailsService.loadUserByUsername(username); return new SiteUserAuthentication(token, userDetails); } diff --git a/src/main/java/com/example/solidconnection/util/JwtUtils.java b/src/main/java/com/example/solidconnection/util/JwtUtils.java deleted file mode 100644 index 040beb9ba..000000000 --- a/src/main/java/com/example/solidconnection/util/JwtUtils.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.example.solidconnection.util; - -import com.example.solidconnection.common.exception.CustomException; -import io.jsonwebtoken.Claims; -import io.jsonwebtoken.ExpiredJwtException; -import io.jsonwebtoken.Jwts; -import jakarta.servlet.http.HttpServletRequest; -import org.springframework.stereotype.Component; - -import static com.example.solidconnection.common.exception.ErrorCode.INVALID_TOKEN; - -@Component -public class JwtUtils { - - private static final String TOKEN_HEADER = "Authorization"; - private static final String TOKEN_PREFIX = "Bearer "; - - private JwtUtils() { - } - - public static String parseTokenFromRequest(HttpServletRequest request) { - String token = request.getHeader(TOKEN_HEADER); - if (token == null || token.isBlank() || !token.startsWith(TOKEN_PREFIX)) { - return null; - } - return token.substring(TOKEN_PREFIX.length()); - } - - public static String parseSubject(String token, String secretKey) { - try { - return parseClaims(token, secretKey).getSubject(); - } catch (Exception e) { - throw new CustomException(INVALID_TOKEN); - } - } - public static Claims parseClaims(String token, String secretKey) throws ExpiredJwtException { - return Jwts.parser() - .setSigningKey(secretKey) - .parseClaimsJws(token) - .getBody(); - } -} diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java index 8fd57eae6..c4c2a1708 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthServiceTest.java @@ -3,6 +3,7 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.ReissueRequest; import com.example.solidconnection.auth.dto.ReissueResponse; +import com.example.solidconnection.auth.token.TokenBlackListService; import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; @@ -30,6 +31,9 @@ class AuthServiceTest { @Autowired private AuthTokenProvider authTokenProvider; + @Autowired + private TokenBlackListService tokenBlackListService; + @Autowired private RedisTemplate redisTemplate; @@ -40,7 +44,7 @@ class AuthServiceTest { void 로그아웃한다() { // given Subject subject = new Subject("subject"); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // when authService.signOut(accessToken.token()); @@ -49,7 +53,7 @@ class AuthServiceTest { String refreshTokenKey = TokenType.REFRESH.addPrefix(subject.value()); assertAll( () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), - () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } @@ -58,7 +62,7 @@ class AuthServiceTest { // given SiteUser user = siteUserFixture.사용자(); Subject subject = authTokenProvider.toSubject(user); - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: #296 + AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // when authService.quit(user, accessToken.token()); @@ -69,7 +73,7 @@ class AuthServiceTest { assertAll( () -> assertThat(user.getQuitedAt()).isEqualTo(tomorrow), () -> assertThat(redisTemplate.opsForValue().get(refreshTokenKey)).isNull(), - () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue() + () -> assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue() ); } diff --git a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java index dc35ab3a7..b6c111f24 100644 --- a/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/AuthTokenProviderTest.java @@ -61,7 +61,7 @@ class 리프레시_토큰을_제공한다 { void 유효한_리프레시_토큰인지_확인한다() { // given RefreshToken refreshToken = authTokenProvider.generateAndSaveRefreshToken(subject); - AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject); // todo: issue#296 + AccessToken fakeRefreshToken = authTokenProvider.generateAccessToken(subject); // when, then assertAll( @@ -71,7 +71,7 @@ class 리프레시_토큰을_제공한다 { } @Test - void 액세서_토큰에_해당하는_리프레시_토큰을_삭제한다() { + void 액세스_토큰에_해당하는_리프레시_토큰을_삭제한다() { // given authTokenProvider.generateAndSaveRefreshToken(subject); AccessToken accessToken = authTokenProvider.generateAccessToken(subject); @@ -85,44 +85,6 @@ class 리프레시_토큰을_제공한다 { } } - @Nested - class 블랙리스트를_관리한다 { - - @Test - void 액세스_토큰을_블랙리스트에_추가한다() { - // given - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); // todo: issue#296 - - // when - authTokenProvider.addToBlacklist(accessToken); - - // then - String blackListTokenKey = TokenType.BLACKLIST.addPrefix(accessToken.token()); - String foundBlackListToken = redisTemplate.opsForValue().get(blackListTokenKey); - assertThat(foundBlackListToken).isNotNull(); - } - - /* - * todo: JwtUtils 나 TokenProvider 를 스프링 빈으로 주입받도록 변경한다. (issue#296) - * - 아래 테스트 코드에서는, 내부적으로 JwtUtils.parseSubject() 메서드가 호출될 때 발생하는 예외를 피하기 위해 jwt토큰을 생성한다. - * - 테스트 작성자는 예외 발생을 피하기 위해 "제대로된 jwt 토큰 생성이 필요하다"는 것을 몰라야한다. - * - 따라서, JwtUtils 나 TokenProvider 를 스프링 빈으로 주입받도록 변경하고, 테스트에서 mock 을 사용하여 의존성을 끊을 필요가 있다. - */ - @Test - void 블랙리스트에_있는_토큰인지_확인한다() { - // given - AccessToken accessToken = authTokenProvider.generateAccessToken(subject); - authTokenProvider.addToBlacklist(accessToken); - AccessToken notRegisteredAccessToken = authTokenProvider.generateAccessToken(new Subject("!")); - - // when, then - assertAll( - () -> assertThat(authTokenProvider.isTokenBlacklisted(accessToken.token())).isTrue(), - () -> assertThat(authTokenProvider.isTokenBlacklisted(notRegisteredAccessToken.token())).isFalse() - ); - } - } - @Test void 토큰으로부터_Subject_를_추출한다() { // given diff --git a/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java new file mode 100644 index 000000000..c36a0bb39 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/JwtTokenProviderTest.java @@ -0,0 +1,176 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.token.JwtTokenProvider; +import com.example.solidconnection.common.exception.CustomException; +import com.example.solidconnection.common.exception.ErrorCode; +import com.example.solidconnection.auth.token.config.JwtProperties; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("토큰 제공자 테스트") +@TestContainerSpringBootTest +class JwtTokenProviderTest { + + @Autowired + private JwtTokenProvider tokenProvider; + + @Autowired + private JwtProperties jwtProperties; + + @Autowired + private RedisTemplate redisTemplate; + + @Test + void 토큰을_생성한다() { + // given + String actualSubject = "subject123"; + TokenType actualTokenType = TokenType.ACCESS; + + // when + String token = tokenProvider.generateToken(actualSubject, actualTokenType); + + // then - subject와 만료 시간이 일치하는지 검증 + Claims claims = tokenProvider.parseClaims(token); + long expectedExpireTime = claims.getExpiration().getTime() - claims.getIssuedAt().getTime(); + assertAll( + () -> assertThat(claims.getSubject()).isEqualTo(actualSubject), + () -> assertThat(expectedExpireTime).isEqualTo(actualTokenType.getExpireTime()) + ); + } + + @Test + void 토큰을_저장한다() { + // given + String subject = "subject123"; + TokenType tokenType = TokenType.ACCESS; + String token = tokenProvider.generateToken(subject, tokenType); + + // when + String savedToken = tokenProvider.saveToken(token, tokenType); + + // then - key: "{TokenType.Prefix}:subject", value: {token} 로 저장되어있는지 검증, 반환하는 값이 value와 같은지 검증 + String key = tokenType.addPrefix(subject); + String value = redisTemplate.opsForValue().get(key); + assertAll( + () -> assertThat(value).isEqualTo(token), + () -> assertThat(savedToken).isEqualTo(value) + ); + } + + @Nested + class 토큰으로부터_subject_를_추출한다 { + + @Test + void 유효한_토큰의_subject_를_추출한다() { + // given + String subject = "subject000"; + String token = createValidToken(subject); + + // when + String extractedSubject = tokenProvider.parseSubject(token); + + // then + assertThat(extractedSubject).isEqualTo(subject); + } + + @Test + void 유효하지_않은_토큰의_subject_를_추출하면_예외_응답을_반환한다() { + // given + String subject = "subject123"; + String token = createExpiredToken(subject); + + // when, then + assertThatCode(() -> tokenProvider.parseSubject(token)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); + } + } + + @Nested + class 토큰으로부터_claim_을_추출한다 { + + @Test + void 유효한_토큰의_claim_을_추출한다() { + // given + String subject = "subject"; + String claimKey = "key"; + String claimValue = "value"; + Claims expectedClaims = Jwts.claims(new HashMap<>(Map.of(claimKey, claimValue))).setSubject(subject); + String token = createValidToken(expectedClaims); + + // when + Claims actualClaims = tokenProvider.parseClaims(token); + + // then + assertAll( + () -> assertThat(actualClaims.getSubject()).isEqualTo(subject), + () -> assertThat(actualClaims.get(claimKey)).isEqualTo(claimValue) + ); + } + + @Test + void 유효하지_않은_토큰의_claim_을_추출하면_예외_응답을_반환한다() { + // given + String subject = "subject"; + Claims expectedClaims = Jwts.claims().setSubject(subject); + String token = createExpiredToken(expectedClaims); + + // when + assertThatCode(() -> tokenProvider.parseClaims(token)) + .isInstanceOf(CustomException.class) + .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); + } + } + + private String createValidToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } + + private String createValidToken(Claims claims) { + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() + 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } + + private String createExpiredToken(String subject) { + return Jwts.builder() + .setSubject(subject) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } + + private String createExpiredToken(Claims claims) { + return Jwts.builder() + .setClaims(claims) + .setIssuedAt(new Date()) + .setExpiration(new Date(System.currentTimeMillis() - 1000)) + .signWith(SignatureAlgorithm.HS256, jwtProperties.secret()) + .compact(); + } +} diff --git a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java index 1656ed4e5..463bc4957 100644 --- a/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/SignInServiceTest.java @@ -2,11 +2,9 @@ import com.example.solidconnection.auth.domain.TokenType; import com.example.solidconnection.auth.dto.SignInResponse; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.util.JwtUtils; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -26,7 +24,7 @@ class SignInServiceTest { private SignInService signInService; @Autowired - private JwtProperties jwtProperties; + private TokenProvider tokenProvider; @Autowired private RedisTemplate redisTemplate; @@ -49,8 +47,8 @@ void setUp() { SignInResponse signInResponse = signInService.signIn(user); // then - String accessTokenSubject = JwtUtils.parseSubject(signInResponse.accessToken(), jwtProperties.secret()); - String refreshTokenSubject = JwtUtils.parseSubject(signInResponse.refreshToken(), jwtProperties.secret()); + String accessTokenSubject = tokenProvider.parseSubject(signInResponse.accessToken()); + String refreshTokenSubject = tokenProvider.parseSubject(signInResponse.refreshToken()); String savedRefreshToken = redisTemplate.opsForValue().get(TokenType.REFRESH.addPrefix(refreshTokenSubject)); assertAll( () -> assertThat(accessTokenSubject).isEqualTo(subject), diff --git a/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java new file mode 100644 index 000000000..e1974cc93 --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/TokenBlackListServiceTest.java @@ -0,0 +1,58 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.token.TokenBlackListService; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.redis.core.RedisTemplate; + +import static com.example.solidconnection.auth.domain.TokenType.BLACKLIST; +import static org.assertj.core.api.Assertions.assertThat; + +@TestContainerSpringBootTest +class TokenBlackListServiceTest { + + @Autowired + private TokenBlackListService tokenBlackListService; + + @Autowired + private RedisTemplate redisTemplate; + + @Test + void 액세스_토큰을_블랙리스트에_추가한다() { + // given + AccessToken accessToken = new AccessToken("subject", "token"); + + // when + tokenBlackListService.addToBlacklist(accessToken); + + // then + String blackListTokenKey = BLACKLIST.addPrefix(accessToken.token()); + String foundBlackListToken = redisTemplate.opsForValue().get(blackListTokenKey); + assertThat(foundBlackListToken).isNotNull(); + } + + @Nested + class 블랙리스트에_있는_토큰인지_확인한다 { + + @Test + void 블랙리스트에_토큰이_있는_경우() { + // given + AccessToken accessToken = new AccessToken("subject", "token"); + tokenBlackListService.addToBlacklist(accessToken); + + // when, then + assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isTrue(); + } + + @Test + void 블랙리스트에_토큰이_없는_경우() { + // given + AccessToken accessToken = new AccessToken("subject", "token"); + + // when, then + assertThat(tokenBlackListService.isTokenBlacklisted(accessToken.token())).isFalse(); + } + } +} diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java index 233317458..c748987c1 100644 --- a/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java @@ -1,11 +1,11 @@ package com.example.solidconnection.auth.service.oauth; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.service.TokenProvider; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.support.TestContainerSpringBootTest; -import com.example.solidconnection.util.JwtUtils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtBuilder; import io.jsonwebtoken.Jwts; @@ -34,6 +34,9 @@ class OAuthSignUpTokenProviderTest { @Autowired private OAuthSignUpTokenProvider OAuthSignUpTokenProvider; + @Autowired + private TokenProvider tokenProvider; + @Autowired private RedisTemplate redisTemplate; @@ -50,7 +53,7 @@ class OAuthSignUpTokenProviderTest { String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(email, authType); // then - Claims claims = JwtUtils.parseClaims(signUpToken, jwtProperties.secret()); + Claims claims = tokenProvider.parseClaims(signUpToken); String actualSubject = claims.getSubject(); AuthType actualAuthType = AuthType.valueOf(claims.get(AUTH_TYPE_CLAIM_KEY, String.class)); String signUpTokenKey = TokenType.SIGN_UP.addPrefix(email); diff --git a/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java b/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java new file mode 100644 index 000000000..1d4ba2533 --- /dev/null +++ b/src/test/java/com/example/solidconnection/security/filter/AuthorizationHeaderParserTest.java @@ -0,0 +1,54 @@ +package com.example.solidconnection.security.filter; + +import com.example.solidconnection.support.TestContainerSpringBootTest; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.mock.web.MockHttpServletRequest; + +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +@TestContainerSpringBootTest +class AuthorizationHeaderParserTest { + + @Autowired + private AuthorizationHeaderParser authorizationHeaderParser; + + @Nested + class 요청으로부터_토큰을_추출한다 { + + @Test + void 지정한_형식의_토큰이_있으면_토큰을_반환한다() { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + String token = "token"; + request.addHeader("Authorization", "Bearer " + token); + + // when + Optional extractedToken = authorizationHeaderParser.parseToken(request); + + // then + assertThat(extractedToken).get().isEqualTo(token); + } + + @Test + void 형식에_맞는_토큰이_없으면_빈_값을_반환한다() { + // given + MockHttpServletRequest noHeader = new MockHttpServletRequest(); + MockHttpServletRequest wrongPrefix = new MockHttpServletRequest(); + wrongPrefix.addHeader("Authorization", "Wrong token"); + MockHttpServletRequest emptyToken = new MockHttpServletRequest(); + emptyToken.addHeader("Authorization", "Bearer "); + + // when & then + assertAll( + () -> assertThat(authorizationHeaderParser.parseToken(noHeader)).isEmpty(), + () -> assertThat(authorizationHeaderParser.parseToken(wrongPrefix)).isEmpty(), + () -> assertThat(authorizationHeaderParser.parseToken(emptyToken)).isEmpty() + ); + } + } +} diff --git a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java index c66037c82..229fed27b 100644 --- a/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/JwtAuthenticationFilterTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.security.filter; import com.example.solidconnection.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.security.userdetails.SiteUserDetailsService; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; diff --git a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java index 5ccc1aa1b..80e927203 100644 --- a/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java +++ b/src/test/java/com/example/solidconnection/security/filter/SignOutCheckFilterTest.java @@ -1,7 +1,7 @@ package com.example.solidconnection.security.filter; import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.support.TestContainerSpringBootTest; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; diff --git a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java index 9c51de838..0bbb6677f 100644 --- a/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java +++ b/src/test/java/com/example/solidconnection/security/provider/SiteUserAuthenticationProviderTest.java @@ -2,7 +2,7 @@ import com.example.solidconnection.common.exception.CustomException; import com.example.solidconnection.security.authentication.SiteUserAuthentication; -import com.example.solidconnection.security.config.JwtProperties; +import com.example.solidconnection.auth.token.config.JwtProperties; import com.example.solidconnection.security.userdetails.SiteUserDetails; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.fixture.SiteUserFixture; diff --git a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java b/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java deleted file mode 100644 index c57e85193..000000000 --- a/src/test/java/com/example/solidconnection/util/JwtUtilsTest.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.example.solidconnection.util; - -import com.example.solidconnection.common.exception.CustomException; -import com.example.solidconnection.common.exception.ErrorCode; -import io.jsonwebtoken.Jwts; -import io.jsonwebtoken.SignatureAlgorithm; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.springframework.mock.web.MockHttpServletRequest; - -import java.util.Date; - -import static com.example.solidconnection.util.JwtUtils.parseSubject; -import static com.example.solidconnection.util.JwtUtils.parseTokenFromRequest; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.junit.jupiter.api.Assertions.assertAll; - -@DisplayName("JwtUtils 테스트") -class JwtUtilsTest { - - private final String jwtSecretKey = "jwt-secret-key"; - - @Nested - class 요청으로부터_토큰을_추출한다 { - - @Test - void 토큰이_있으면_토큰을_반환한다() { - // given - MockHttpServletRequest request = new MockHttpServletRequest(); - String token = "token"; - request.addHeader("Authorization", "Bearer " + token); - - // when - String extractedToken = parseTokenFromRequest(request); - - // then - assertThat(extractedToken).isEqualTo(token); - } - - @Test - void 토큰이_없으면_null_을_반환한다() { - // given - MockHttpServletRequest noHeader = new MockHttpServletRequest(); - MockHttpServletRequest wrongPrefix = new MockHttpServletRequest(); - wrongPrefix.addHeader("Authorization", "Wrong token"); - MockHttpServletRequest emptyToken = new MockHttpServletRequest(); - wrongPrefix.addHeader("Authorization", "Bearer "); - - // when & then - assertAll( - () -> assertThat(parseTokenFromRequest(noHeader)).isNull(), - () -> assertThat(parseTokenFromRequest(wrongPrefix)).isNull(), - () -> assertThat(parseTokenFromRequest(emptyToken)).isNull() - ); - } - } - - @Nested - class 토큰으로부터_subject_를_추출한다 { - - @Test - void 유효한_토큰의_subject_를_추출한다() { - // given - String subject = "subject000"; - String token = createValidToken(subject); - - // when - String extractedSubject = parseSubject(token, jwtSecretKey); - - // then - assertThat(extractedSubject).isEqualTo(subject); - } - - @Test - void 유효하지_않은_토큰의_subject_를_추출하면_예외_응답을_반환한다() { - // given - String subject = "subject123"; - String token = createExpiredToken(subject); - - // when - assertThatCode(() -> parseSubject(token, jwtSecretKey)) - .isInstanceOf(CustomException.class) - .hasMessage(ErrorCode.INVALID_TOKEN.getMessage()); - } - } - - private String createValidToken(String subject) { - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() + 1000)) - .signWith(SignatureAlgorithm.HS256, jwtSecretKey) - .compact(); - } - - private String createExpiredToken(String subject) { - return Jwts.builder() - .setSubject(subject) - .setIssuedAt(new Date()) - .setExpiration(new Date(System.currentTimeMillis() - 1000)) - .signWith(SignatureAlgorithm.HS256, jwtSecretKey) - .compact(); - } -}