🙏내용에 대한 피드백은 언제나 환영입니다!!🙏
개발 환경 : jdk17, Spring Boot, jjwt 0.12.3
JWT를 사용하여 Access Token으로 권한을 인증하는 과정에서 문제가 생겼다.
PostMan을 통해 테스트를 진행할다면, 헤더에 Access Token을 넣고 인증이 필요한 Restful API에 접근하는 것이 일반적이다.
하지만, 여기서 문제가 발생했다. 토큰의 맨 끝에. 즉, '서명' 부분에 특수문자가 들어가도 정상적으로 작동하는 것이었다.
예를 들어, 원래 토큰이 aaaa.bbbb.cccc였다면, aaaa.bbbb.cccc!@이러한 특수문자가 들어가도 정상적인 토큰으로 인지하고 권한은 인증되었다.
(jwt 토큰에 사용되는 '.', '-', '_'를 제외한 다른 특수문자들에 관한 내용이다.)
API 플랫폼 PostMan에서만의 문제인지 확인하기 위해 네트워크에 입력하여 넣어보니 같은 결과가 나왔다.
간단한 동작 사진이다.
1. 정상적인 토큰에 대한 흐름
2. accessToken 뒤에 특수문자를 넣는 경우. (정상적으로 작동)
2번에 대한 accessToken의 로그
INFO 2994 --- [nio-8080-exec-5] c.p.t.j.filter.JwtAuthenticationFilter : accessToken: eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZG1pbiIsInJvbGUiOiJBRE1JTiIsImNhdGVnb3J5IjoiYWNjZXNzIiwidXNlcklkIjoxLCJpYXQiOjE3MjU3OTAzNTUsImV4cCI6MTcyNTc5MjE1NX0._-Nv8LjYfhSopWrwfHSTZW1xFknPBdVNe-29pzU85O8@@
구글링을 통해 다양한 글을 찾아보았다. 나의 검색력이 부족한 것인지, 이것에 대한 문제를 다루는 내용을 찾지 못하였다. 또한, jwt.io사이트에 들어가도 찾을 수 없었고, jwt.io에서 제공하는 인코딩, 디코딩 변환 결과에서는 특수문자를 넣게되면 오류가 발생했다.
프레임워크마다 인지하는 것이 다른 것인지(상단에 개발 환경에 대해 간략히 적은 이유이다.), 내가 jwt에 대해서 잘못 다루는 것인지 정확한 이유를 찾는 것은 힘들었다. 그래서, 내가 고민해보고 생각한 것은 아래와 같다.
JWT은 보통 영문자, 숫자, '_', '-', '.' 등의 내용으로 이루어진다. 그래서, 디코딩 하는 과정에서 맨 뒤에 붙은 !@#이러한 특수문자는 제외시키는 일이 일어난다는 생각을 하였다. (물론, 맨 뒤를 제외하고 사이사이에 특수문자가 들어가면 잘못된 토큰임을 인지한다.)
JWT는 헤더, 페이로드, 서명으로 이루어 지고, 서명은 헤더와 페이로드 + secretKey를 통해서 만들어진다.
그래서, 서버는 수신된 헤더와 페이로드, secretKey를 사용하여 서명을 조합해 서명이 올바른지 확인하며 '무결성'을 확인한다.
이러한 과정에서 서명이 올바르다면 뒤에 특수문자와 상관없이 그대로 인증을 통과시킨다고 생각을 하였다. (1번과 이유가 비슷한가..?)
어디까지나 나의 생각이고, 이것에 대해 그냥 재밌게 읽어줬으면 좋겠다.
추가적으로 정보를 찾고, Stackoverflow 커뮤니티에 물어본 결과 아래와 같은 깃허브 레포지토리의 README.md 와 이슈를 발견하게 되었다.
jjwt GitHub 중 Adding Invalid Characters 부분
간단히 요약하자면, 아래와 같다.
<< jjwt GitHub 중 Adding Invalid Characters의 내용 >>
<< 특수문자 허용에 대한 의문점 제기 이슈 내용 >>
강건성의 원칙이란, "너그러이 수용하고, 엄격하게 보낸다."는 뜻.
이 원칙의 목표는 시스템의 견고함을 높이고, 다양한 입력을 잘 처리하면서도 정확하고 신뢰성 있는 출력을 유지하는 것이다.
내가 생각했던 이유가 정확하진 않지만, 너그럽게 수용한다는 점에서 같은 의미인 것 같다. 나는, 사용자가 로그아웃을 한다면, 그 사용자의 AccessToken으로의 접근은 허용하지 않도록 BlackList
메모리에 담아 놓을 것이기 때문에, 아래와 같은 해결방법을 적용해보았다.
public boolean isBase64URL(String token) {
return token.matches("^[0-9A-Za-z-_.]+$");
}
String accessToken = accessTokenGetHeader.substring(TOKEN_PREFIX.length()).trim();
if (!isBase64URL(accessToken)) {
// 예외 처리.
}
public class CustomBase64UrlEncoder implements Encoder<OutputStream, OutputStream> {
@Override
public OutputStream encode(OutputStream outputStream) {
// OutputStream을 Base64 URL-safe로 인코딩
Base64.Encoder encoder = Base64.getUrlEncoder().withoutPadding();
return encoder.wrap(outputStream); // Base64로 인코딩한 결과를 OutputStream으로 반환
}
}
public class CustomBase64UrlDecoder implements Decoder<InputStream, InputStream> {
@Override
public InputStream decode(InputStream inputStream) {
try {
byte[] bytes = inputStream.readAllBytes(); // InputStream을 바이트 배열로 변환
byte[] decodedBytes = Base64.getUrlDecoder().decode(bytes); // Base64 URL-safe 디코딩
return new ByteArrayInputStream(decodedBytes); // 디코딩된 결과를 새로운 InputStream으로 반환
} catch (Exception e) {
throw new RuntimeException("Decoding failed", e);
}
}
}
private final Encoder<OutputStream, OutputStream> base64UrlEncoder = new CustomBase64UrlEncoder();
/* 토큰 생성 */
private String createJwt(Map<String, Object> claims, String subject, Long expirationTime) {
return Jwts.builder()
.subject(subject)
.claims(claims)
.b64Url(base64UrlEncoder) // 커스텀 인코더 설정
.issuer(issuer)
.issuedAt(new Date(System.currentTimeMillis()))
.expiration(new Date(System.currentTimeMillis() + expirationTime))
.signWith(secretKey)
.compact();
}
private final Decoder<InputStream, InputStream> base64UrlDecoder = new CustomBase64UrlDecoder();
/* 토큰 정보 불러오기 */
private Claims parseClaims(String token){
return Jwts.parser()
.verifyWith(secretKey)
.b64Url(base64UrlDecoder) // 커스텀 디코더 설정
.build()
.parseSignedClaims(token)
.getPayload();
}
내가 잘못된 코드를 짠 것인지 알 수 없었다. 하지만, 깃허브를 통해 다양한 사람들이 짠 코드를 보았고, 그 코드들을 적용시켜도 똑같은 결과가 나와 특수문자 처리에 대한 의심을 해보게 된 것이다.
또한, 특수문자 처리가 중요한가? 라는 생각을 가질 수 있다.
나는 개인의 정보가 사용되는 로그인 부분은 보안이 아주 중요하다고 생각한다. 그래서, 로그아웃을 하면 Access Token을 만료기간 까지 BlackList에 담아 사용하지 못하게 만드는 과정을 만들었다. 이러한 기능을 넣으면, 위처럼 특수문자를 사용할 시 그대로 접근이 되는 문제가 발생한다.
이와 같이, 보안은 중요하다고 생각하고, 좀 더 알아볼 것이며, 이러한 문제에 대한 정확한 이유를 파악하고 싶다. (위에 이유에 내용을 추가함.)