JWT & Filter
2026. 3. 6. 21:25

필터란 무엇인가?

우리가 알고 있던 필터와 같다!

필터에 들어오는 것 중 필요한 것만 통과시키고 나머지는 걸러주는 것이다.

 

스프링에서의 필터 또한 마찬가지이다.

요청이 들어오면 특정조건을 검사하고 통과시킬지 말지 결정하는 문지기 역할을 해준다.

다만, 반드시 걸러내는 역할만 하는 게 아니라 요청과 응답을 중간에서 처리하는 역할도 한다.

그래서 로그 기록을 남기거나 인증 검사를 하는 등 여러 방법으로 사용할 수 있다.

 

Spring에서 사용할 수 있는 Filter의 종류에는 여러 가지가 있다. 그중에서 OncePerRequestFilter를 제일 많이 사용한다!

  • OncePerRequestFilter : 요청 당 한 번만 실행되도록 보장한다. 인증/인가, 트랜잭션 관리 등에서 자주 사용한다.
  • CharacterEncodingFilter : 클라이언트가 보내는 요청과 서버가 보내는 응답을 특정 문자 인코딩으로 통일해 주는 필터이다.
  • HiddenHttpMethodFilter : form에서 _method라는 값을 읽어서 HTTP Method를 바꿔주는 역할을 한다.
    ( GET / POST만 가능) ( form = 사용자가 입력한 데이터를 서버로 보내는 HTML 기능)
  • CorsFilter : 브라우저가 CORS 정책 때문에 막은 요청을 서버에서 허용해 주기 위해 설정하는 필터이다.
    ( CORS policy blocked : 브라우저가 보안 정책(CORS) 때문에 다른 출처(origin)에서 온 요청을 차단한 것.)

필터는 어떻게 구현해야 할까?

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로 나간다 ");
 
    }
}

위에 있는 필터는 OncePerRequestFilter를 상속하여 요청당 한 번만 실행되도록 구현한 필터이다!

이처럼 사용할 필터의 목적에 맞는 필터 클래스를 상속하여 구현하면 된다.

 

FilterChain란 무엇인가?

필터 체인은 Filter 묶음이라고 생각하면 된다.

정확히는 여러 개의 필터를 순서대로 실행하도록 연결해 놓은 구조를 말한다.

필터 순서는 어떻게 정할 수 있을까?

@Component
@Order(1)
public class NbcamFilter extends OncePerRequestFilter { }
@Component
@Order(2)
public class NbcamFilter extends OncePerRequestFilter { }

@Order 어노테이션을 이용하여 정해줄 수 있다. @Order(숫자)를 작성해주면 숫자가 작은 순서에 따라 실행된다.

 

@ Order 외에도
SecurityFilterChain (Spring Security에서 사용하는 필터 체인)
FilterRegistrationBean (Spring Bean으로 필터를 등록하면서 URL, 순서 등을 설정)
등이 있다.

 

JWT란 무엇인가?

JWT는 출입증이랑 동일하다.

매번 출입증을 발급 받는 것이 아니라 최초에만 신분증을 통해 출입증 발급을 받고,

그 뒤에는 출입증만으로도 입장이 가능한 한 것처럼 JWT도 똑같다.

 

JWT는 크게 3개로 구성되어있다.

HEADER : 서명에 사용할 알고리즘 정보

PAYLOAD : 토큰에 담는 데이터 (사용자 정보 등)

SIGNATURE : 토큰 위조 여부를 확인하기 위한 서명 값 (header + payload + secretKey)

 

위 사진과 같이 JWT를 테스트하거나 구조를 확인해볼 수 있는 사이트이다.

https://token.dev/ 

 

JWT Debugger

Create and Debug JWT Tokens

token.dev

 

JWT를 사용할 때 꼭 필요한 것들

  • 어떤 암호화 알고리즘을 사용할 것인지 ex) HS256
  • 어떤 정보를 넣을 것인지 ex) Tutor, KIM DONG HYUN , male
  • 어떤 암호키로 암호화 할 것인지 ex) Secret Key
  • 만료 시간

만료 시간이 필요한 이유는?

JWT는 최초의 로그인 이후에는 아무런 검증을 하지 않고 무조건 프리패스다.
이런 JWT을 누군가 탈취하게 된다면 개발자도 아무런 조치를 취할수가 없다.

그래서 최소한의 안전 장치로 만료 시간을 저장한다. 만료 시간 이후에는 새로운 토큰을 발급해서 사용해야 된다.

여기서 잠깐! 그러면 토큰은 보통 얼마의 만료시간을 줄까? 보통 10~30분정도를 준다.

그렇다면 10~30분마다 계속 로그인을 해야할까? 
아니다!
Access Token과 Refresh Token을 같이 사용하면 된다!

Access Token은 사용자가 API 요청을 할 때 인증을 위해 사용하는 토큰이다.
Refresh Token은 Access Token이 만료되었을 때 새로운 Access Token을 발급받기 위해 사용하는 토큰이다.

로그인 할 때 Access Token과 Refresh Token을 발급한다.
이후 Access Token 사용하고 만료가 되면 Refresh Token을 통해 새로운 Access Token을 발급한다.

그러면 이 과정동안 사용자는 자동으로 로그인이 유지 되는 것처럼 느낀다.

그렇다면 여기서 또 궁금한점이 생긴다!

Access Token보다 Refresh Token 탈취가 더 어려운가?
맞다!
보통 Refresh Token은 자주 전송되지 않고 안전한 저장소에 보관되기 때문에 Access Token보다 탈취되기 어렵다.

 

이제 JWT를 생성하고 검증해보자!

더보기
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);
    }


}

JWT를 생성하고 검증하는 유틸 클래스이다. (JwtUtil)

 

더보기
package org.example.nbcam_addvanced_1.user.controller;

import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.example.nbcam_addvanced_1.common.utils.JwtUtil;
import org.example.nbcam_addvanced_1.user.model.response.LoginResponseDto;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
@Slf4j
public class UserController {

    private final JwtUtil jwtUtil;

    @GetMapping("/get")
    public String getUserInfo() {
        log.info("호출");
        return "호출 되었습니다.";
    }

    // 토큰 생성 테스트
    @PostMapping("/login")
    public ResponseEntity<LoginResponseDto> login() {

        String token  = jwtUtil.generateToken("kim-dong-hyun");

        return ResponseEntity.ok(new LoginResponseDto(token));
    }

    // 토큰 검증 테스트
    @GetMapping("/validate")
    public ResponseEntity<Boolean> checkValidate(HttpServletRequest request) {

        String authorizationHeader = request.getHeader("Authorization");

        String jwt = authorizationHeader.substring(7);

        Boolean validate = jwtUtil.validateToken(jwt);

        return ResponseEntity.ok(validate);
    }

    // 토큰 복호화 테스트
    @GetMapping("/username")
    public ResponseEntity<String> getUsername(HttpServletRequest request) {

        String authorizationHeader = request.getHeader("Authorization");

        String jwt = authorizationHeader.substring(7);

        String username = jwtUtil.extractUsername(jwt);

        return ResponseEntity.ok(username);
    }

}

JWT를 생성하고 검증하는 컨트롤러이다. (UserController)

'💻 Backend > 이론 및 실습' 카테고리의 다른 글

QueryDSL 검색 기능 구현  (0) 2026.03.30
QueryDSL 개념 및 적용  (0) 2026.03.30
VPC EC2 생성  (0) 2026.03.10
클라우드 컴퓨팅 개념 정리 (On-Premise, IaaS, PaaS, SaaS)  (0) 2026.03.09
인증 & 인가  (0) 2026.03.07