Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/main/java/org/websoso/WSSServer/auth/jwt/JWTUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,27 @@ public Long getUserIdFromToken(String token) {
public JwtValidationType validateJWT(String token) {
try {
final Claims claims = getClaim(token);
if (TokenType.from(claims.getSubject()) == TokenType.ACCESS) {
final TokenType tokenType = TokenType.from(claims.getSubject());
if (tokenType == TokenType.ACCESS) {
return JwtValidationType.VALID_ACCESS;
}
return JwtValidationType.VALID_REFRESH;
if (tokenType == TokenType.REFRESH) {
return JwtValidationType.VALID_REFRESH;
}
return JwtValidationType.UNSUPPORTED_SUBJECT;
} catch (SignatureException ex) {
return JwtValidationType.INVALID_SIGNATURE;
} catch (MalformedJwtException ex) {
return JwtValidationType.INVALID_TOKEN;
} catch (ExpiredJwtException ex) {
if (TokenType.from(ex.getClaims().getSubject()) == TokenType.ACCESS) {
final TokenType tokenType = TokenType.from(ex.getClaims().getSubject());
if (tokenType == TokenType.ACCESS) {
return JwtValidationType.EXPIRED_ACCESS;
}
return JwtValidationType.EXPIRED_REFRESH;
if (tokenType == TokenType.REFRESH) {
return JwtValidationType.EXPIRED_REFRESH;
}
return JwtValidationType.UNSUPPORTED_SUBJECT;
} catch (UnsupportedJwtException ex) {
return JwtValidationType.UNSUPPORTED_TOKEN;
} catch (IllegalArgumentException ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ protected void doFilterInternal(@NonNull HttpServletRequest request,
writeError(response, CustomAuthError.WRONG_TOKEN_TYPE);
return;
}
case INVALID_TOKEN, INVALID_SIGNATURE, UNSUPPORTED_TOKEN, EMPTY_TOKEN -> {
case INVALID_TOKEN, INVALID_SIGNATURE, UNSUPPORTED_TOKEN, UNSUPPORTED_SUBJECT, EMPTY_TOKEN -> {
writeError(response, CustomAuthError.INVALID_TOKEN);
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@ public enum JwtValidationType {
EXPIRED_ACCESS,
EXPIRED_REFRESH,
UNSUPPORTED_TOKEN,
EMPTY_TOKEN
EMPTY_TOKEN,
UNSUPPORTED_SUBJECT
}
16 changes: 16 additions & 0 deletions src/test/java/org/websoso/WSSServer/auth/jwt/JWTUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,22 @@ void validateJWT_expiredRefreshToken_returnsExpiredRefresh() {
assertThat(jwtUtil.validateJWT(token)).isEqualTo(JwtValidationType.EXPIRED_REFRESH);
}

@DisplayName("서명이 유효해도 subject가 access/refresh가 아니면 UNSUPPORTED_SUBJECT를 반환한다")
@Test
void validateJWT_unknownSubjectToken_returnsUnsupportedSubject() {
String token = testTokenFactory.createTokenWithSubject(USER_ID, "unknown");

assertThat(jwtUtil.validateJWT(token)).isEqualTo(JwtValidationType.UNSUPPORTED_SUBJECT);
}

@DisplayName("만료된 토큰이어도 subject가 access/refresh가 아니면 UNSUPPORTED_SUBJECT를 반환한다")
@Test
void validateJWT_expiredUnknownSubjectToken_returnsUnsupportedSubject() {
String token = testTokenFactory.createExpiredTokenWithSubject(USER_ID, "unknown");

assertThat(jwtUtil.validateJWT(token)).isEqualTo(JwtValidationType.UNSUPPORTED_SUBJECT);
}

@DisplayName("다른 시크릿으로 서명된 Access Token은 INVALID_SIGNATURE를 반환한다")
@Test
void validateJWT_wrongSignatureAccessToken_returnsInvalidSignature() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,18 @@ void tamperedOrUnsupportedToken_returnsInvalidToken() throws Exception {
}
}

@DisplayName("지원하지 않는 subject 토큰이면 401과 AUTH-001을 응답하고 체인을 중단한다")
@Test
void unsupportedSubjectToken_returnsInvalidTokenAndStopsChain() throws Exception {
given(jwtUtil.validateJWT(TOKEN)).willReturn(JwtValidationType.UNSUPPORTED_SUBJECT);

MockHttpServletResponse response = doFilter(bearerRequest(TOKEN));

assertThat(response.getStatus()).isEqualTo(401);
assertThat(response.getContentAsString()).contains("AUTH-001");
verify(filterChain, never()).doFilter(any(), any());
}

@DisplayName("Authorization 헤더가 없으면 익명 인증으로 설정하고 필터 체인을 계속 진행한다")
@Test
void noToken_setsAnonymousAuthenticationAndContinues() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,27 @@
package org.websoso.WSSServer.auth.jwt;

import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;

public class TestTokenFactory {

public static final String TEST_SECRET = "test-only-jwt-secret-never-used-in-production-0123456789";
private static final String OTHER_SECRET = "a-completely-different-test-secret-not-matching-the-real-one-987654";
public static final long ACCESS_TOKEN_EXPIRATION = 3_600_000L;
public static final long REFRESH_TOKEN_EXPIRATION = 1_209_600_000L;

private final JwtKeyProvider jwtKeyProvider;
private final JwtProvider jwtProvider;

public TestTokenFactory() {
this(TEST_SECRET);
}

public TestTokenFactory(String secret) {
this.jwtProvider = new JwtProvider(new JwtKeyProvider(secret), ACCESS_TOKEN_EXPIRATION, REFRESH_TOKEN_EXPIRATION);
this.jwtKeyProvider = new JwtKeyProvider(secret);
this.jwtProvider = new JwtProvider(jwtKeyProvider, ACCESS_TOKEN_EXPIRATION, REFRESH_TOKEN_EXPIRATION);
}

public String createAccessToken(Long userId) {
Expand All @@ -40,4 +47,24 @@ public String createAccessTokenWithInvalidSignature(Long userId) {
public String createRefreshTokenWithInvalidSignature(Long userId) {
return new TestTokenFactory(OTHER_SECRET).createRefreshToken(userId);
}

public String createTokenWithSubject(Long userId, String subject) {
return signTokenWithSubject(userId, subject, ACCESS_TOKEN_EXPIRATION);
}

public String createExpiredTokenWithSubject(Long userId, String subject) {
return signTokenWithSubject(userId, subject, -1_000L);
}

private String signTokenWithSubject(Long userId, String subject, long expirationTime) {
long now = System.currentTimeMillis();
return Jwts.builder()
.setHeaderParam(Header.TYPE, Header.JWT_TYPE)
.setSubject(subject)
.setIssuedAt(new Date(now))
.setExpiration(new Date(now + expirationTime))
.claim(JwtProvider.CLAIM_USER_ID, userId)
.signWith(jwtKeyProvider.getSigningKey(), SignatureAlgorithm.HS256)
.compact();
}
}
Loading