Spring Boot 개념 정리(JWT,Filter)

제이 용·2025년 12월 2일

앞으로 과제 하기 전 숙지해둬야할 개념들을 복기 차원에서 정리할 예정이다.

필터(Fillter)

  • 필터는 본격적인 스프링 로직이 실행되기 전에 사용자의 요청을 한번 걸러주는 역할을 한다.

구조

  • 직접 구현할 필요 없이 많이 사용되는 목적에 따라 이미 구현이 되어 있고 우리는 이중에 하나를 선택해서 사용하면 되는데 OncePerRequestFilter 를 제외하고는 거의 사용할 일이 없다.

  • OncePerRequestFilter

    • 요청 당 한 번만 실행되도록 보장.
    • 인증/인가, 로깅, 트랜잭션 관리 같은 곳에서 자주 사용.

구현하기

@Component
public class NbcamFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
        throws ServletException, IOException {

        // 요청이 들어갈 때 실행되는 부분
        System.out.println("✅ NbcamFilter로 들어간다 ");

        // 필터 계속 진행
        filterChain.doFilter(request, response);

        // 요청이 나갈 때 실행되는 부분
        System.out.println("✅ NbcamFilter로 나간다 ");
    }
}
  • filterChain.doFilter(request, response); 를 기준으로 필터를 통과해서 들어갈 때와 필터를 통과해서 나갈 때를 구분 짓는다.

FilterChain

  • FilterChain이라는 Filter 묶음이 있고 필요하면 여기 FilterChain에 만든 Filter를 하나씩 추가하는 개념이다.

  • @Order

    • 어노테이션을 통해서 순서를 지정 할 수 있다.

JWT

  • JWT는 출입증이랑 동일하다.
  • 최초 발급받을 때만 검사하고 이후에는 소유하고 있을 시 출입이 가능하다는 점이 매우 흡사하다.

구성요소

  • HEADER
    • 암호화에서 사용된 알고리즘
  • PAYLOAD
    • 암호화 된 값
  • SIGNATURE
    • 암호화 할 때 사용한 KEY 값

필수요소

  • 어떤 암호화 알고리즘을 사용할 것인지 ex) HS256

  • 어떤 정보를 넣을 것인지 ex) Tutor, KIM DONG HYUN , male

  • 어떤 암호키로 암호화 할 것인지 ex) Secret Key

  • 만료 시간(리프레쉬 토큰)

    • JWT 는 최초의 로그인 이후에는 아무런 검증을 하지 않고 무조건 프리패스이다.

    • 편리하지만 그만큼 보안에 약하다.

    • 최소한의 안전 장치로 만료 시간을 저장한다.


구현하기

  • build.gradle 추가
implementation "io.jsonwebtoken:jjwt-api:0.12.5"
runtimeOnly  "io.jsonwebtoken:jjwt-impl:0.12.5"
runtimeOnly  "io.jsonwebtoken:jjwt-jackson:0.12.5" // JSON 직렬화
  • yml파일 키값 추가
## 암호화에 사용할 비밀키 
jwt:
  secret:
    key: IfthisgetsstolenitsabigproblemIfthelengthistooshortthesecurityisnotsufficientsoanerroroccurs
  • jwtutill 클래스 구성
package org.example.nbcam_addvanced_1.common.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtException;
import io.jsonwebtoken.JwtParser;
import io.jsonwebtoken.Jwts;

import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;

import java.util.Date;
import javax.crypto.SecretKey;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;


@Component
public class JwtUtil {

    public static final String BEARER_PREFIX = "Bearer ";
    private static final long TOKEN_TIME = 60 * 60 * 1000L; // 60분

    @Value("${jwt.secret.key}")           // application.yml 에 있는 key 가져옴
    private String secretKeyString;

    private SecretKey key;
    private JwtParser parser;

    /**
     * 빈 초기화 메서드
     * @PostConstruct 어플리케이션 실행 될 때 가장 먼저 실행 되게 하는 어노테이션
     */

    @PostConstruct
    public void init() {
        byte[] bytes = Decoders.BASE64.decode(secretKeyString);
        this.key = Keys.hmacShaKeyFor(bytes);
        this.parser = Jwts.parser()
            .verifyWith(this.key)
            .build();
    }


    // 토큰 생성
    public String generateToken(String username) {
        Date now = new Date();
        return BEARER_PREFIX + Jwts.builder()
            .claim("username", username)
            .issuedAt(now)
            .expiration(new Date(now.getTime() + TOKEN_TIME))
            .signWith(key, Jwts.SIG.HS256)
            .compact();
    }

    // 토큰 검증
    public boolean validateToken(String token) {
        if (token == null || token.isBlank()) return false;
        try {
            parser.parseSignedClaims(token);
            return true;
        } catch (JwtException | IllegalArgumentException e) {
            // 개별 예외 분리 없음: 서명/형식/만료 등 모든 실패를 한 번에 처리
            log.debug("Invalid JWT: {}", e.toString());
            return false;
        }
    }



    // 토큰 복호화
    private Claims extractAllClaims(String token) {
        return parser.parseSignedClaims(token).getPayload();
    }

    public String extractUsername(String token) {
        return extractAllClaims(token).get("username", String.class);
    }


}
  • @Value("${jwt.secret.key}")

    • 야믈파일에 고유 키값 불러오기
  • @PostConstruct

    • 어플리케이션 실행 될 때 가장 먼저 실행 되게 하는 어노테이션
  • claim("username", username)

    • 토큰 내 들어갈 정보

JWT와 Filter의 조합

  • JWT를 통해 인증 / 인가를 처리하였고 이를 매 API에 코드를 추가할 수 없으니 공통적으로 맨 앞단에서 필터를 통해 적용하는 것이 가장 효율 적이다.

HttpServletRequest

  • 요청을 보내면 Postman 기준 윗쪽 화면과 아랫쪽화면을 각각 request, response라는 공간에 나눠 담고 servlet 이라는 객체에 담아서 spring으로 전달한다.

  • 그중에서 reqeust 부분에는 위와 같은 그림의 구조로 되어 있다.

  • Postman 기준 윗쪽 request에 있는 모든 정보를 담고 있고 그것 이외로 Map 타입의 attribute 라는 저장공간이 있어서 attribute 에 JWT 에서 복호화한 정보를 담아서 Controller로 넘겨주는 것이다.


강의를 통해 여러 개념들을 얻게 되었지만, 복기하면서 정리하기에는 방대한 양이라, 하루 하루 과제도 풀어가면서 정리해나갈 예정이다.

2개의 댓글

comment-user-thumbnail
2025년 12월 3일

용준님~ 용준님의 성장을 항상 응원하며 바라보고 있습니다 😊
짧은 가을이 가고 어느덧 추운 겨울이 다가왔네요~ 이 겨울이 지나고 봄이 올때쯤 용준님의 무한한 성장에 취업길에도 봄이 올거라 믿어 의심치 않습니다~

1개의 답글