Everyday Dev System

Spring 에서 Filter란? 본문

내배캠 주요 학습/Spring 숙련

Spring 에서 Filter란?

chaeyoung- 2023. 6. 20. 00:43
Spring Boot에서 Server는 Client로 부터 요청이 들어오면 Filter를 거치게 되어 있다.

요청이 들어오고 FIlter를 통해 인증을 한 후
인증이 완료된 경우  Securitycontext에 authority를 넣어준다. 
그리고 권한 확인을 하고 , Controller로 이동한다.

 

 

아래와 같이 Filter interface를 상속받아 doFilter()를 재정의하여 커스튬한 Filter를

SpringBoot의 기본적으로 구현되어 있는 필터의 순서에 끼워 넣어서 Filter로서 기능을 수행시킬 수 있다.

 

.addFilterBefore(A,B) -> B Filter 이전에 A Filter를 먼저 거친다.

 

 

Filter를 커스튬하기 위해 상속받을 수 있는 Filter의 종류는 많은데 그 중 현업에서 주로 활용하는 것이

한 요청마다 거치는 FIlter -> OncePerRequestFilter 이다.

 

Filter를 거쳐서 권한을 부여받고 그 권한을 Http.authorizeRequest() 코드에서

해당 url에 따른 권한을 매칭시켜 확인 하는 것이다.

 

 

예를 들어,

public class LshFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        SecurityContextHolder.setContext(new SecurityContextImpl(new UsernamePasswordAuthenticationToken("lsh", "lsh")));
    }
    
}

OncePerRequestFilter extend한 Filter Class를 생성한 후 , doFiltertInternal를 재구현한다.

SecutiryContextHolder.setContext(new SecurityContext( new UsernamePasswordAuthenticationToken("dslkj","sd"));

해당 필터에서는 아무것도 안하고 권한 부여하는 코드로 작성되어 있다.

권한 부여는 SecurityContextHolder를 통해 SecurityContext에 UsernamePasswordAuthenticationToken을 저장한다. 인증처리가 완료가 됨을 뜻함.

 

 

 

그 후, Config 클래스에서

http.addFilterBefore(new LshFilter() , UsernamePasswordAuthenticationFilter.class);

.addFilterBefore(A,B) -> B Filter 이전에 A Filter를 먼저 거친다.

httip.authorizeRequest
       .antMatchers("/lsh").hasRole("lsh");

위 코드를 통해 해당 url에 따른 권한을 매칭시켜 확인 하고 인가하는 것이다.

인가가 허락되지 않으면 예외처리를 작성하여 처리할 수 있다.

 

위와 같은 순서로 필터를 통해 인증 절차 -> 권한 부여 -> url에 따른 권한 확인 -> 인가 절차

 


 

references : 

Spring Security의 권한 부여 처리 흐름 (velog.io)

https://gngsn.tistory.com/160

 


예제 코드:

 

1. 요청했을 때 들어오는 URL을 Logging 처리 하는 Filter

package com.sparta.springauth.filter;

import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Slf4j(topic = "LoggingFilter")
@Component
@Order(1) // filter 순서 지정
public class LoggingFilter implements Filter {
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // 전처리
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();
        log.info(url);

        chain.doFilter(request, response); // 다음 Filter 로 이동

        // 후처리
        log.info("비즈니스 로직 완료");
    }
}

 

@Order(1)

filter 순서 지정

 

url을 가져오기 위해 HttpServletRequest 타입으로 캐스팅하고 가져옴.

        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();

 

 

 

2. 인증 및 인가를 처리하는 Filter

package com.sparta.springauth.filter;

import com.sparta.springauth.entity.User;
import com.sparta.springauth.jwt.JwtUtil;
import com.sparta.springauth.repository.UserRepository;
import io.jsonwebtoken.Claims;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.IOException;

@Slf4j(topic = "AuthFilter")
@Component
@Order(2)
public class AuthFilter implements Filter {

    private final UserRepository userRepository;
    private final JwtUtil jwtUtil;

    public AuthFilter(UserRepository userRepository, JwtUtil jwtUtil) {
        this.userRepository = userRepository;
        this.jwtUtil = jwtUtil;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String url = httpServletRequest.getRequestURI();

        if (StringUtils.hasText(url) &&
                (url.startsWith("/api/user") || url.startsWith("/css") || url.startsWith("/js"))
        ) {
            // 회원가입, 로그인 관련 API 는 인증 필요없이 요청 진행
            chain.doFilter(request, response); // 다음 Filter 로 이동
        } else {
            // 나머지 API 요청은 인증 처리 진행
            // 토큰 확인
            String tokenValue = jwtUtil.getTokenFromRequest(httpServletRequest);

            if (StringUtils.hasText(tokenValue)) { // 토큰이 존재하면 검증 시작
                // JWT 토큰 substring
                String token = jwtUtil.substringToken(tokenValue);

                // 토큰 검증
                if (!jwtUtil.validateToken(token)) {
                    throw new IllegalArgumentException("Token Error");
                }

                // 토큰에서 사용자 정보 가져오기
                Claims info = jwtUtil.getUserInfoFromToken(token);

                User user = userRepository.findByUsername(info.getSubject()).orElseThrow(() ->
                        new NullPointerException("Not Found User")
                );

                request.setAttribute("user", user);
                chain.doFilter(request, response); // 다음 Filter 로 이동
            } else {
                throw new IllegalArgumentException("Not Found Token");
            }
        }
    }

}

'내배캠 주요 학습 > Spring 숙련' 카테고리의 다른 글

중요한 Validation, 예외처리  (0) 2023.06.20
Spring Secutiry  (0) 2023.06.20
로그인 구현하기  (0) 2023.06.19
회원가입 구현하기  (0) 2023.06.19
JWT version 0.11.5  (0) 2023.06.19