본문 바로가기
개발&프로그래밍

Spring Security와 JWT: 개념부터 실무 코드까지 한 방에 정리하기

by 재아군 2025. 12. 6.
반응형

서버 개발을 하다 보면 가장 막막하게 느껴지는 벽이 하나 있습니다.

바로 보안이라는 거대한 산입니다.

 

특히 스프링 부트로 프로젝트를 진행할 때 Spring Security 설정을 마주하면 수많은 필터와 낯선 용어들 때문에 겁부터 나기 마련입니다.

하지만 걱정하지 마세요.

 

오늘은 이 보안이라는 벽을 아주 매끄럽게 넘을 수 있도록 도와드리겠습니다.

 

세션 기반의 인증 방식에서 벗어나, 요즘 대세라고 할 수 있는 JWT(JSON Web Token)를 활용해 확장성 있고 탄탄한 인증 시스템을 구축하는 과정을 처음부터 끝까지 상세하게 알려드릴게요.

 

복잡한 이론은 최대한 쉽게 풀고, 당장 프로젝트에 적용할 수 있는 실무 코드를 중심으로 설명해 드리겠습니다.

 

 

 

 

JWT와 스프링 시큐리티의 만남

우리가 흔히 사용하는 JWT는 유저의 정보를 암호화하여 토큰 형태로 주고받는 기술입니다.

기존의 세션 방식은 서버가 유저의 로그인 상태를 메모리에 저장해야 했기에 서버가 여러 대로 늘어나면 관리가 까다로웠습니다.

반면 JWT는 유저가 자기 신분증을 직접 들고 다니는 것과 같아서, 서버는 그저 이 신분증이 위조되었는지만 확인하면 되므로 서버 확장에 아주 유리합니다.

 

이러한 JWT를 스프링 시큐리티에 녹여내기 위해서는 스프링 시큐리티의 작동 원리인 필터 체인(Filter Chain)을 이해해야 합니다.

스프링 시큐리티는 마치 공항의 보안 검색대처럼, 요청이 들어오면 여러 개의 검문소를 차례로 통과하게 만듭니다.

우리는 이 검문소 사이에 JWT를 검사하는 우리만의 전용 검색대를 하나 끼워 넣을 것입니다.

 

 

핵심 컴포넌트 1: 토큰 공급자 (JwtTokenProvider)

가장 먼저 필요한 것은 토큰을 생성하고, 유효한지 검사하고, 토큰에서 유저 정보를 꺼내는 역할을 하는 도구 상자입니다.

이를 보통 Provider라고 부릅니다.

 

이 클래스는 JWT 라이브러리를 사용하여 암호화 키를 관리하고 비즈니스 로직과 보안 로직을 연결하는 다리 역할을 합니다.

 

 

 

여기서는 io.jsonwebtoken 라이브러리를 기준으로 설명해 드릴게요.

 

토큰을 생성할 때는 유저의 권한 정보와 만료 시간을 설정하고, 암호화 알고리즘을 사용해 서명합니다.

 

반대로 검증할 때는 서명이 올바른지, 유효 기간이 지나지 않았는지 체크하여 true 혹은 false를 반환합니다.

 

이 코드는 한 번 잘 짜두면 다른 프로젝트에서도 그대로 가져다 쓸 수 있는 아주 든든한 자산이 됩니다.

 

 

 

핵심 컴포넌트 2: 인증 필터 (JwtAuthenticationFilter)

이제 실제로 요청을 가로채서 검사할 문지기를 만들 차례입니다.

 

스프링의 OncePerRequestFilter를 상속받아 구현하는데, 이는 하나의 요청에 대해 딱 한 번만 실행됨을 보장합니다.

 

서블릿 컨테이너의 특성상 필터가 여러 번 호출될 수 있는 상황을 방지하여 리소스 낭비를 막기 위함입니다.

 

 

이 필터가 하는 일은 단순하지만 강력합니다.

 

클라이언트가 보낸 HTTP 요청의 헤더에서 Authorization이라는 키를 찾고, 그 안에 들어있는 Bearer 토큰을 꺼냅니다.

 

그리고 앞서 만든 Provider에게 이 토큰이 유효한지 물어봅니다.

 

만약 유효하다면 토큰에서 유저 정보를 꺼내 스프링 시큐리티가 관리하는 저장소인 SecurityContextHolder에 저장합니다.

 

여기에 정보가 저장되어야 비로소 스프링은 아, 이 사용자는 인증된 사용자구나 라고 인식하게 됩니다.

 

 

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)) {
            Authentication authentication = jwtTokenProvider.getAuthentication(token);
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        
        filterChain.doFilter(request, response);
    }

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

 

 

핵심 컴포넌트 3: 보안 설정 (SecurityConfig)

마지막으로 우리가 만든 필터를 스프링 시큐리티 설정에 등록해 주어야 합니다.

 

최신 스프링 부트 버전에서는 SecurityFilterChain을 빈으로 등록하는 방식을 권장합니다.

 

 

여기서 중요한 설정이 몇 가지 있습니다.

 

첫째, JWT를 사용하므로 서버가 세션을 생성하거나 유지하지 않도록 세션 정책을 STATELESS로 설정해야 합니다.

 

둘째, 폼 로그인이나 HTTP Basic 인증 같은 기본 설정은 비활성화합니다.

 

셋째, 우리가 만든 JwtAuthenticationFilter를 UsernamePasswordAuthenticationFilter 앞에 위치시킵니다.

 

이렇게 해야 아이디와 비밀번호를 검사하기 전에 토큰을 먼저 확인하여 불필요한 로직 실행을 막을 수 있습니다.

 

 

@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http
        .httpBasic(HttpBasicConfigurer::disable)
        .csrf(CsrfConfigurer::disable)
        .sessionManagement(configurer -> configurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests(authorize -> authorize
            .requestMatchers("/auth/**").permitAll()
            .anyRequest().authenticated()
        )
        .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider), UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

 

 

마무리하며

이렇게 Spring Security와 JWT를 연동하는 전체적인 흐름과 코드를 살펴보았습니다.

 

처음에는 설정할 것도 많고 개념도 낯설어 보이지만, 한 번 구조를 잡아두면 이보다 더 안전하고 편리한 인증 방식은 없습니다.

 

특히 프론트엔드와 백엔드가 분리된 현대적인 웹 애플리케이션 환경에서 JWT는 선택이 아닌 필수에 가깝습니다.

 

오늘 작성해 드린 코드를 바탕으로 여러분의 프로젝트에 맞는 유연하고 강력한 보안 시스템을 구축해 보세요.

 

보안은 완벽한 한 번의 설정보다는, 끊임없이 관심을 가지고 구멍을 메워가는 과정이라는 점을 기억해 주시면 좋겠습니다.

 

여러분의 안전한 서버 개발을 응원합니다.

 

 

SpringSecurity, JWT, 스프링부트, 백엔드개발, 로그인구현, 자바, 인증인가, 웹보안, 서버개발

반응형

댓글