JWT + SpringSecurity를 이용한 웹페이지 보안 파이프라인 구성

승환·2025년 2월 25일

SpringSecurity PipeLine

SpringSecurity에서 제공하는 보안 기능은 기본적으로 세션의 형태로 작동하게 된다. 또한 기본 로그인 폼과 기초 인증, 인가를 제공하기 때문에 보안적으로 사용자가 신경써야할 점을 많이 대신해주게 된다.

하지만 JWT토큰 인증 방식을 사용하기 위해선 Stateless상태로 구성해야하기 때문에 파이프라인을 조금 손봐야 한다.

이번에는 파이프라인에 JWT인증을 추가하는 방법과 구성시 세팅을 알아보도록한다.


기본적인 SpringSecurity pipeline

위 사진의 형태로 파이프라인이 제공되게 된다.

잘 보면 UsernamePasswordAuthentiactionFilter이라는 객체가 있는데, 이 객체가 인증, 인가요청을 담당하게 된다.
인증을 하고 그 정보를 SecurityContextHolder라는 요청을 저장하는 공간에 담아 인가요청을 처리하게 된다.

코드로 보면 아래와 같다.

UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                            new UsernamePasswordAuthenticationToken(
                                    userDetails,
                                    null,
                                    userDetails.getAuthorities()
                            );
                    log.info("■ 접근 토큰 생성 완료");
                    //접근 토큰 활성화
                    SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
                    log.info("■ 접근 토큰 활성화: "+usernamePasswordAuthenticationToken.getAuthorities());

사용하는 코드의 일부를 가져왔는데, SecurityContextHoler에 setAuthentication메서드를 통해 등록해야하는 UsernamePasswordAuthenticationToken을 등록해 유저가 파이프 라인을 지나갈 수 있는 통행증을 만든다.

이후 session을 통해 관리하게 되고 세션이 유지되면 인증, 인가 요청이 들어올 때 마다 파이프라인을 통과시키게 된다.


JWT 토큰을 이용한 파이프라인 설정

JWT토큰을 이용하게 되면 아래와 같은 기능이 필요가 없어진다.

  • ContextHolder을 이용한 세션유지 처리
  • 기본적인 로그인 폼

세션유지는 stateless 형태로 작용하기 때문에 그런 것이고 로그인 폼은 기본적인 로그인 폼을 사용하면 파이프라인에 들어올때 Serucrityfilter가 먼저 들어오게 되는데 아래에 나올 내용이지만 이렇게하면 JWT인증 요청이 두번 발생할 수 있다.

JWT인증 요청 구조는 아래와같다.

이런 형식으로 필터를 구성하게 되면 Spring Security에 들어오기 전에 jwt인증 요청을 해서 원하는 로직으로 JWT토큰을 발급할 수 있다.


오류 : JWT인증 요청이 두번 발생함.

파이프라인을 구성할 때 아래와 같이 JWT필터를 넣어주었다.

http.addFilterBefore(new jwtRequestFilter(jwtBuilder,userDetailsService), UsernamePasswordAuthenticationFilter.class);

jwtRequsetFilter라는 jwt인증요청을 처리하는 필터를 만들어서 UsernamePasswordAuthentication필터 앞에 넣도록 하였다. 저게 바로 Springboot Security에서 진입점에 해당하는 필터이다.

근데 이렇게 하면 문제가 발생한다. 인증이 두번 요청이 되서 처리는 제대로 되는데 로그를 보면 JWT토큰을 두번 인증하게 된다.

문제점은 new로 새로운 의존성을 주입해 객체를만들게 되는데
JWT인증 구조를 보면 아래와 같은 클래스를 상속하고 있는 것을 볼 수 있다.

@Component
@RequiredArgsConstructor
@Slf4j
public class JwtRequestFilter extends OncePerRequestFilter

OncePerRequestFilter 클래스는 해당 필터가 반드시 한번만 동작하도록 보장하는 필터이다.

그럼 두번 동작을 하면 안되는데, 왜 두번 인증을 요청할까?
그건 SpringBoot Bean등록 방식을 봐야한다.

SpringBoot에서 빈을 등록할 때 우리는 어노테이션을 이용해서 Service,Contorller등의 빈을 등록한다.
이 과정은 컴파일 시간에서 이뤄지고 같은 빈은 등록되면 오류가 나기 때문에 반드시 하나의 빈만 등록된다. 하지만 security를 사용하게 되면 Security내부적으로 빈을 등록하는 절차를 거치게 된다.

여기서 문제가 발생한다.
Spring에서 등록한 JWT필터 빈을 Seucrity에서 new로 새로운 필터를 넣었기 때문에
Spring에서 빈을 등록하고 Security에서 빈을 또 등록해서 이중 인증이 되는 것이다.
원래는 같은 빈은 등록이 되지 않지만 Security에서 등록하기 때문에 컴파일 단계에서 걸러지지 않는것 같다.

해결 방법은 간단하다 코드를

@RequiredArgsConstructor
@Slf4j
public class JwtRequestFilter extends OncePerRequestFilter

이렇게 수정하면 Spring에서 빈등록을 하지 않게 된다.
JwtRequest필터는 Security에서만 사용하기 때문에 등록되지 않은 빈을 사용하는 오류가 날 위험도 없다.

따라서 중복문제를 해결할 수 있다.
그리고 새로운 객체를 new를 이용해서 만들고 필터에 넣으면 코드를 보기 힘들 수 있기 때문에

http.addFilterBefore(jwtRequestFilter(jwtBuilder,userDetailsService), UsernamePasswordAuthenticationFilter.class);

이런식으로 생성자를 주입한 클래스를 위에서 적용하고 필터 순서를 정할 때 주입된 클래스를 활용하는 것이 안전할 듯 하다.

profile
왕초보 학부생

0개의 댓글