본문 바로가기
public void static main/Java

[Spring Security] JWT(Json Web Token)

by 햄리뮤 2025. 1. 13.
반응형

JWT를 사용하기 위해서는 참 공부할께 많구나..

https://ably.com/blog/jwt-auth-ably

JWT(Json Web Token)은 뭘까?

JWT는 인증 정보를 안정하고 효율적으로 표현하기 위해서 사용하는 JSON 기반의 토큰이다!

주로 Stateless 인증 시스템에서 사용되고, 사용자가 로그인하면 서버는 JWT를 생성하여 클라이언트에 전달한다.

클라이언트는 이후 요청마다 이 토큰을 서버로 전송하여 인증을 받는다.

JWT 구조

JWT는 Header, Payload, Signature 세 부분으로 구성되고, 각각 Base64Url로 인코딩된 뒤 "."으로 연결된다.

JWT 예시

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VySWQiLCJyb2xlIjoiUk9MRV9BRE1JTiIsImV4cCI6MTY4MTM0NjYyMn0.4j1iyxSk5KRP4OwFZqPGoP6M1WwHpFSKXchU0PAEIv8

https://supertokens.com/blog/what-is-jwt

  1. Header (헤더)
    • JWT의 메타 데이터를 포함한다.
      • alg: 서명에 사용된 알고리즘(예: HS256, RS256 등)
      • typ: 토큰 유형 (대부분 JWT)
  2. Payload (페이로드)
    • 토큰의 실제 데이터를 포함한다.
    • 이 데이터는 Base64Url로 인코딩되지만 암호화되지는 않는다. 민감 정보를 담지 않도록 주의하자!
      • sub (Subject): 토큰의 주체 (예: 사용자 ID)
      • role: 사용자의 역할
      • exp (Expiration): 토큰 만료 시간 (Unix 타임스탬프)
  3. Signature (서명)
    • JWT의 유효성을 보장하는 서명.
    • 서명은 헤더와 페이로드를 조합한 후, 비밀 키와 함께 지정된 알고리즘으로 생성된다.

JWT의 장점과 단점

장점

  1. Stateless 인증: 서버가 세션 상태를 유지하지 않아도 된다.
  2. 확장성: 분산 시스템에서도 쉽게 인증 정보를 공유 가능한다.
  3. Self-contained: 인증 관련 정보(예: 역할, 사용자 ID)를 페이로드에 포함한다.

단점

  1. 크기 문제: JWT는 클라이언트-서버 간 요청마다 전송되므로 크기가 클 경우 네트워크 비용 증가할 수 있다.
  2. 민감 정보: 암호화되지 않은 페이로드에 민감한 정보를 담으면 안 된다.
  3. 만료된 토큰: 만료된 JWT를 강제로 무효화하려면 별도의 매커니즘이 필요하다.

Spring Security와 JWT를 활용한 Stateless 인증

JWT 발행

사용자가 로그인 하면 인증 정보를 기반으로 JWT를 생성하여 반환 한다.

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class JwtTokenProvider {

    private final String secretKey = "mySecretKey";
    private final long validityInMilliseconds = 3600000; // 1시간

    // JWT 생성
    public String createToken(String username, String role) {
        Date now = new Date();
        Date expiryDate = new Date(now.getTime() + validityInMilliseconds);

        return Jwts.builder()
                .setSubject(username)
                .claim("role", role) // 클레임 추가
                .setIssuedAt(now)
                .setExpiration(expiryDate) // 만료 시간 설정
                .signWith(SignatureAlgorithm.HS256, secretKey) // 서명
                .compact();
    }
}

 

JWT 유효성 검사

클라이언트가 요청 시 JWT를 전송하면, 서버는 이 토큰의 유효성을 검증한다.

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;

public class JwtTokenProvider {

    private final String secretKey = "mySecretKey";

    // 토큰에서 사용자 이름 추출
    public String getUsername(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }

    // 토큰 유효성 검사
    public boolean validateToken(String token) {
        try {
            Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            return true;
        } catch (SignatureException e) {
            return false; // 서명 검증 실패
        }
    }
}

JWT와 Spring Security 연동

Spring Security의 필터를 확장하여 JWT를 처리한다.

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.filter.OncePerRequestFilter;

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private final JwtTokenProvider jwtTokenProvider;

    public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
        this.jwtTokenProvider = jwtTokenProvider;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String token = resolveToken(request);

        if (token != null && jwtTokenProvider.validateToken(token)) {
            String username = jwtTokenProvider.getUsername(token);
            // 인증 객체 생성 및 설정
            SecurityContextHolder.getContext().setAuthentication(
                new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList())
            );
        }

        filterChain.doFilter(request, response);
    }

    private String resolveToken(HttpServletRequest request) {
        String bearerToken = request.getHeader("Authorization");
        if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
            return bearerToken.substring(7);
        }
        return null;
    }
}

JWT 보안 취약점을 조심하자!

  1. 비밀 키 관리
    • 강력한 비밀 키 사용 (HS254 사용).
    • 비밀 키를 환경 변수 또는 Key Management System(KMS)에 저장.
  2. 민감 정보 보호
    • 페이로드에는 함호화되지 않은 민감 정보를 포함하지 않는다.
  3. 토큰 만료
    • exp(만료시간)를 설정하여 오래된 토큰 사용 방지.
    • 만료된 토큰을 무효화하려면 블랙리스트 또는 토큰 회전(Token Rotation) 방식을 사용.
  4. HTTPS
    • HTTPS를 사용하여 네트워크 중간에서 토큰이 노출되지 않도록 한다.

토큰 발행 및 유효성 검사 커스터마이징

  • 추가 클레임(Custom Claims)
    • 역할(Role), 권한, 사용자 고유 정보 등을 페이로드에 추가.
  • 토큰 서명 알고리즘 변경
    • 대칭 키 기반(HS256) -> 비대칭 키 기반(RS256)으로 변경
  • Refresh Token 사용
    • Access Token과 Refresh Token을 분리하여 토큰 재발행.

자 이제 JWT도 알아봤으니 프로젝트에 적용해봐야겠지? 할수있겠지? ㅋㅋ 으ㅏ아ㅏㅏㅏ

 

 

** 그냥 하루하루 개인 공부한 것을 끄적 거리는 공간입니다.

이곳 저곳에서 구글링한 것과 강의 들은 내용이 정리가 되었습니다.

그림들은 그림밑에 출처표시를 해놓았습니다.

문제가 될시 말씀해주시면 해당 부분은 삭제 하도록하겠습니다. **

반응형

댓글