From d67a3af8b7a2f259c2cd7e73a92bbf9d0b4ed474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EC=84=B8=EC=A4=80?= <74056843+sejoon00@users.noreply.github.com> Date: Thu, 23 Jan 2025 15:21:53 +0900 Subject: [PATCH] =?UTF-8?q?[feat/#11]=20jwt=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=ED=95=84=ED=84=B0=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 1 + .../domain/member/service/MemberService.java | 5 + .../config/security/SecurityConfig.java | 60 +++++++--- .../global/error/exception/ErrorCode.java | 11 ++ .../exception/JwtInvalidException.java | 14 +++ .../filter/JwtAuthenticationFilter.java | 45 +++++++ .../handler/EmailPasswordSuccessHandler.java | 2 +- .../security/provider/JwtTokenProvider.java | 69 +++++++++++ .../token/JwtAuthenticationToken.java | 38 ++++++ .../global/security/{ => utils}/JwtUtil.java | 15 ++- .../global/security/utils/JwtUtilTest.java | 113 ++++++++++++++++++ 11 files changed, 352 insertions(+), 21 deletions(-) create mode 100644 src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java create mode 100644 src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java rename src/main/java/com/moplus/moplus_server/global/security/{ => utils}/JwtUtil.java (76%) create mode 100644 src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java diff --git a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java index 9a3957e..48d6cfd 100644 --- a/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java +++ b/src/main/java/com/moplus/moplus_server/domain/auth/controller/AuthController.java @@ -36,4 +36,5 @@ public void adminLogin( ) { // 실제 처리는 Security 필터에서 이루어지며, 이 메서드는 Swagger 명세용입니다. } + } diff --git a/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java index a16f50b..0940f76 100644 --- a/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java +++ b/src/main/java/com/moplus/moplus_server/domain/member/service/MemberService.java @@ -16,4 +16,9 @@ public class MemberService { public Member getMemberByEmail(String email) { return memberRepository.findByEmailOrThrow(email); } + + @Transactional(readOnly = true) + public Member getMemberById(Long id) { + return memberRepository.findById(id).orElseThrow(); + } } diff --git a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java index 64d3142..2ee9340 100644 --- a/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java +++ b/src/main/java/com/moplus/moplus_server/global/config/security/SecurityConfig.java @@ -2,8 +2,11 @@ import com.moplus.moplus_server.domain.member.service.MemberService; import com.moplus.moplus_server.global.security.filter.EmailPasswordAuthenticationFilter; +import com.moplus.moplus_server.global.security.filter.JwtAuthenticationFilter; import com.moplus.moplus_server.global.security.handler.EmailPasswordSuccessHandler; import com.moplus.moplus_server.global.security.provider.EmailPasswordAuthenticationProvider; +import com.moplus.moplus_server.global.security.provider.JwtTokenProvider; +import com.moplus.moplus_server.global.security.utils.JwtUtil; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Value; @@ -11,8 +14,7 @@ import org.springframework.context.annotation.Configuration; import org.springframework.http.HttpStatus; import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.ProviderManager; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; @@ -32,7 +34,7 @@ public class SecurityConfig { private final MemberService memberService; private final EmailPasswordSuccessHandler emailPasswordSuccessHandler; - private final AuthenticationConfiguration authenticationConfiguration; + private final JwtUtil jwtUtil; private String[] allowUrls = {"/", "/favicon.ico", "/swagger-ui/**", "/v3/**"}; @@ -45,6 +47,17 @@ public WebSecurityCustomizer configure() { return (web) -> web.ignoring().requestMatchers(allowUrls); } + @Bean + public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception { + AuthenticationManagerBuilder authenticationManagerBuilder = + http.getSharedObject(AuthenticationManagerBuilder.class); + authenticationManagerBuilder + .authenticationProvider(emailPasswordAuthenticationProvider()) + .authenticationProvider(jwtTokenProvider()); + authenticationManagerBuilder.parentAuthenticationManager(null); + return authenticationManagerBuilder.build(); + } + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { @@ -62,34 +75,43 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { exception.authenticationEntryPoint((request, response, authException) -> response.setStatus(HttpStatus.UNAUTHORIZED.value()))); // 인증,인가가 되지 않은 요청 시 발생시 + http.authenticationManager(authenticationManager(http)); + http - .addFilterAt(emailPasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); -// .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class) -// .addFilterBefore(new JwtExceptionFilter(), JwtAuthenticationFilter.class); + .addFilterAt(emailPasswordAuthenticationFilter(authenticationManager(http)), + UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtAuthenticationFilter(authenticationManager(http)), + UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean - public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter() throws Exception { - EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter = new EmailPasswordAuthenticationFilter( - authenticationManager(authenticationConfiguration)); - emailPasswordAuthenticationFilter.setFilterProcessesUrl("/api/v1/auth/admin/login"); - emailPasswordAuthenticationFilter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler); - emailPasswordAuthenticationFilter.afterPropertiesSet(); - return emailPasswordAuthenticationFilter; + public EmailPasswordAuthenticationFilter emailPasswordAuthenticationFilter( + AuthenticationManager authenticationManager) throws Exception { + EmailPasswordAuthenticationFilter filter = new EmailPasswordAuthenticationFilter(authenticationManager); + filter.setFilterProcessesUrl("/api/v1/auth/admin/login"); + filter.setAuthenticationSuccessHandler(emailPasswordSuccessHandler); + filter.afterPropertiesSet(); + return filter; } @Bean - public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception { - ProviderManager providerManager = (ProviderManager) authenticationConfiguration.getAuthenticationManager(); - providerManager.getProviders().add(emailPasswordAuthenticationProvider()); - return configuration.getAuthenticationManager(); + public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() { + return new EmailPasswordAuthenticationProvider(memberService); } @Bean - public EmailPasswordAuthenticationProvider emailPasswordAuthenticationProvider() { - return new EmailPasswordAuthenticationProvider(memberService); + public JwtAuthenticationFilter jwtAuthenticationFilter(AuthenticationManager authenticationManager) + throws Exception { + JwtAuthenticationFilter filter = new JwtAuthenticationFilter(authenticationManager); + filter.afterPropertiesSet(); + return filter; + } + + @Bean + public JwtTokenProvider jwtTokenProvider() { + return new JwtTokenProvider(jwtUtil, memberService); } @Bean diff --git a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java index bc30576..31735e8 100644 --- a/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java +++ b/src/main/java/com/moplus/moplus_server/global/error/exception/ErrorCode.java @@ -11,6 +11,17 @@ public enum ErrorCode { INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST, "잘못된 입력 값입니다"), BAD_CREDENTIALS(HttpStatus.UNAUTHORIZED, "잘못된 인증 정보입니다"), + //Auth + AUTH_NOT_FOUND(HttpStatus.UNAUTHORIZED, "시큐리티 인증 정보를 찾을 수 없습니다."), + UNKNOWN_ERROR(HttpStatus.UNAUTHORIZED, "알 수 없는 에러"), + EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "만료된 Token입니다"), + UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "토큰 길이 및 형식이 다른 Token입니다"), + WRONG_TYPE_TOKEN(HttpStatus.UNAUTHORIZED, "서명이 잘못된 토큰입니다."), + ACCESS_DENIED(HttpStatus.UNAUTHORIZED, "토큰이 없습니다"), + TOKEN_SUBJECT_FORMAT_ERROR(HttpStatus.UNAUTHORIZED, "Subject 값에 Long 타입이 아닌 다른 타입이 들어있습니다."), + AT_EXPIRED_AND_RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AT는 만료되었고 RT는 비어있습니다."), + RT_NOT_FOUND(HttpStatus.UNAUTHORIZED, "RT가 비어있습니다"), + //모의고사 PRACTICE_TEST_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 모의고사를 찾을 수 없습니다"), diff --git a/src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java b/src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java new file mode 100644 index 0000000..e95b846 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/exception/JwtInvalidException.java @@ -0,0 +1,14 @@ +package com.moplus.moplus_server.global.security.exception; + +import org.springframework.security.core.AuthenticationException; + +public class JwtInvalidException extends AuthenticationException { + + public JwtInvalidException(String msg) { + super(msg); + } + + public JwtInvalidException(String msg, Throwable cause) { + super(msg, cause); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java b/src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java new file mode 100644 index 0000000..1f37c3a --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/filter/JwtAuthenticationFilter.java @@ -0,0 +1,45 @@ +package com.moplus.moplus_server.global.security.filter; + +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; + +@RequiredArgsConstructor +public class JwtAuthenticationFilter extends OncePerRequestFilter { + + public static final String TOKEN_PREFIX = "Bearer "; + + private final AuthenticationManager authenticationManager; + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) + throws ServletException, IOException { + + String accessToken = extractAccessTokenFromHeader(request); + + if (StringUtils.hasText(accessToken)) { + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(accessToken); + Authentication authentication = authenticationManager.authenticate(jwtAuthenticationToken); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + + filterChain.doFilter(request, response); + } + + private String extractAccessTokenFromHeader(HttpServletRequest request) { + return Optional.ofNullable(request.getHeader("Authorization")) + .filter(header -> header.startsWith(TOKEN_PREFIX)) + .map(header -> header.replace(TOKEN_PREFIX, "")) + .orElse(null); + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java index a7cdaba..9fe78fc 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java +++ b/src/main/java/com/moplus/moplus_server/global/security/handler/EmailPasswordSuccessHandler.java @@ -2,7 +2,7 @@ import com.moplus.moplus_server.domain.member.domain.Member; import com.moplus.moplus_server.global.security.AuthConstants; -import com.moplus.moplus_server.global.security.JwtUtil; +import com.moplus.moplus_server.global.security.utils.JwtUtil; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; diff --git a/src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java b/src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java new file mode 100644 index 0000000..7d8a608 --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/provider/JwtTokenProvider.java @@ -0,0 +1,69 @@ +package com.moplus.moplus_server.global.security.provider; + +import com.moplus.moplus_server.domain.member.domain.Member; +import com.moplus.moplus_server.domain.member.service.MemberService; +import com.moplus.moplus_server.global.error.exception.ErrorCode; +import com.moplus.moplus_server.global.security.exception.JwtInvalidException; +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import com.moplus.moplus_server.global.security.utils.JwtUtil; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.SignatureException; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class JwtTokenProvider implements AuthenticationProvider { + + private final JwtUtil jwtUtil; + private final MemberService memberService; + + @Override + public Authentication authenticate(Authentication authentication) throws AuthenticationException { + Claims claims = getClaims(authentication); + final Member member = getMemberById(claims.getSubject()); + + return new JwtAuthenticationToken( + member, + "", + List.of(new SimpleGrantedAuthority(member.getRole().getValue()) + )); + } + + private Claims getClaims(Authentication authentication) { + Claims claims; + try { + claims = jwtUtil.getAccessTokenClaims(authentication); + } catch (ExpiredJwtException expiredJwtException) { + throw new JwtInvalidException(ErrorCode.EXPIRED_TOKEN.getMessage()); + } catch (SignatureException signatureException) { + throw new JwtInvalidException(ErrorCode.WRONG_TYPE_TOKEN.getMessage()); + } catch (MalformedJwtException malformedJwtException) { + throw new JwtInvalidException(ErrorCode.UNSUPPORTED_TOKEN.getMessage()); + } catch (IllegalArgumentException illegalArgumentException) { + throw new JwtInvalidException(ErrorCode.UNKNOWN_ERROR.getMessage()); + } + return claims; + } + + private Member getMemberById(String id) { + try { + return memberService.getMemberById(Long.parseLong(id)); + } catch (Exception e) { + throw new BadCredentialsException(ErrorCode.BAD_CREDENTIALS.getMessage()); + } + } + + @Override + public boolean supports(Class authentication) { + return JwtAuthenticationToken.class.isAssignableFrom(authentication); + } +} diff --git a/src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java b/src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java new file mode 100644 index 0000000..c9f6d5f --- /dev/null +++ b/src/main/java/com/moplus/moplus_server/global/security/token/JwtAuthenticationToken.java @@ -0,0 +1,38 @@ +package com.moplus.moplus_server.global.security.token; + +import java.util.Collection; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +public class JwtAuthenticationToken extends AbstractAuthenticationToken { + + private String jsonWebToken; + private Object principal; + private Object credentials; + + public JwtAuthenticationToken(String jsonWebToken) { + super(null); + this.jsonWebToken = jsonWebToken; + this.setAuthenticated(false); + } + + public JwtAuthenticationToken(Object principal, Object credentials, + Collection authorities) { + super(authorities); + this.principal = principal; + this.credentials = credentials; + super.setAuthenticated(true); + } + + public Object getCredentials() { + return credentials; + } + + public Object getPrincipal() { + return this.principal; + } + + public String getJsonWebToken() { + return this.jsonWebToken; + } +} \ No newline at end of file diff --git a/src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java b/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java similarity index 76% rename from src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java rename to src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java index fdaf629..d282029 100644 --- a/src/main/java/com/moplus/moplus_server/global/security/JwtUtil.java +++ b/src/main/java/com/moplus/moplus_server/global/security/utils/JwtUtil.java @@ -1,13 +1,16 @@ -package com.moplus.moplus_server.global.security; +package com.moplus.moplus_server.global.security.utils; import com.moplus.moplus_server.domain.member.domain.Member; import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import java.security.Key; import java.util.Date; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.security.core.Authentication; import org.springframework.stereotype.Component; @Slf4j @@ -45,6 +48,16 @@ public String generateRefreshToken(Member member) { .compact(); } + public Claims getAccessTokenClaims(Authentication authentication) { + + return Jwts.parserBuilder() + .requireIssuer(jwtProperties.issuer()) + .setSigningKey(getAccessTokenKey()) + .build() + .parseClaimsJws(((JwtAuthenticationToken) authentication).getJsonWebToken()) + .getBody(); + } + private Key getAccessTokenKey() { return Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); } diff --git a/src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java b/src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java new file mode 100644 index 0000000..e96648c --- /dev/null +++ b/src/test/java/com/moplus/moplus_server/global/security/utils/JwtUtilTest.java @@ -0,0 +1,113 @@ +package com.moplus.moplus_server.global.security.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +import com.moplus.moplus_server.global.properties.jwt.JwtProperties; +import com.moplus.moplus_server.global.security.token.JwtAuthenticationToken; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.MalformedJwtException; +import io.jsonwebtoken.security.Keys; +import io.jsonwebtoken.security.SignatureException; +import java.security.Key; +import java.util.Date; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.security.core.Authentication; + +@ExtendWith(MockitoExtension.class) +public class JwtUtilTest { + + @Mock + private JwtProperties jwtProperties; + + @InjectMocks + private JwtUtil jwtUtil; + + private String validToken; + private Key key; + + @BeforeEach + public void setup() { + // Mock JwtProperties + when(jwtProperties.issuer()).thenReturn("testIssuer"); + when(jwtProperties.accessTokenSecret()).thenReturn( + "mySecretKeymySecretKeymySecretKeymySecretKey"); // 256-bit key + when(jwtProperties.accessTokenExpirationMilliTime()).thenReturn(7200000L); // 1 hour + + // Generate a test token + key = Keys.hmacShaKeyFor(jwtProperties.accessTokenSecret().getBytes()); + Date issuedAt = new Date(); // 3 hour ago + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + validToken = Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject("1") + .claim("role", "ROLE_USER") + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) + .signWith(key) + .compact(); + } + + @Test + public void 유효한_토큰_통과() { + + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(validToken); + + // Act + Claims claimsJws = jwtUtil.getAccessTokenClaims(jwtAuthenticationToken); + + // Assert + assertNotNull(claimsJws); + assertEquals("testIssuer", claimsJws.getIssuer()); + assertEquals("1", claimsJws.getSubject()); + assertEquals("ROLE_USER", claimsJws.get("role", String.class)); + } + + @Test + public void 만료된_토큰_예외() { + Date issuedAt = new Date(System.currentTimeMillis() - 10800000L); // 3 hour ago + Date expiredAt = new Date(issuedAt.getTime() + jwtProperties.accessTokenExpirationMilliTime()); + String expiredToken = Jwts.builder() + .setIssuer(jwtProperties.issuer()) + .setSubject("12345") + .claim("role", "ROLE_USER") + .setIssuedAt(issuedAt) + .setExpiration(expiredAt) // 1 hour ago + .signWith(key) + .compact(); + + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(expiredToken); + + assertThrows(ExpiredJwtException.class, + () -> jwtUtil.getAccessTokenClaims(jwtAuthenticationToken)); + } + + @Test + public void 조작된_토큰_예외() { + //토큰 변형 + String invalidSignatureToken = validToken.substring(0, validToken.length() - 1) + "@"; + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(invalidSignatureToken); + assertThrows(SignatureException.class, () -> { + jwtUtil.getAccessTokenClaims(jwtAuthenticationToken); + }); + } + + @Test + public void jwt_토큰_형식이_아닌_토큰_예외() { + //형식이 jwt 토큰 형식 조차 아닌 토큰 + String malformedToken = "malformed.token.here"; + Authentication jwtAuthenticationToken = new JwtAuthenticationToken(malformedToken); + assertThrows(MalformedJwtException.class, () -> { + jwtUtil.getAccessTokenClaims(jwtAuthenticationToken); + }); + } +}