개발 지식, 슀크립트/Spring

μŠ€ν”„λ§ λΆ€νŠΈ ν•„ν„°μ˜ λ™μž‘ ꡬ쑰

μ‹œνλ¦¬ν‹°μ§€ν˜Έ 2024. 10. 21. 04:05

μ „λ°˜μ μΈ 흐름

 

1. ν΄λΌμ΄μ–ΈνŠΈ μš”μ²­μ΄ λ°œμƒν•˜λ©΄, ν•΄λ‹Ή μš”μ²­μ€ μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆ(Tomcat, Jetty λ“±)에 μ „λ‹¬λœλ‹€.

2. μ„œλΈ”λ¦Ώ μ»¨ν…Œμ΄λ„ˆλŠ” μš”μ²­μ„ μ²˜λ¦¬ν•˜κΈ° 전에 λ“±λ‘λœ <<ν•„ν„° 체인(filter chain)>> 을 톡해 μš”μ²­μ„ ν•„ν„°λ§ν•œλ‹€.

3. ν•„ν„° 체인에 λ“±λ‘λœ 필터듀은 λ“±λ‘λœ μˆœμ„œλŒ€λ‘œ μš”μ²­μ„ κ°€λ‘œμ±„κ³ , 각 ν•„ν„°λŠ” ν•„μš”μ— 따라 μš”μ²­μ„ μˆ˜μ •ν•˜κ±°λ‚˜, νŠΉμ • μ‘°κ±΄μ—μ„œ μš”μ²­μ„ 차단할 μˆ˜λ„ μžˆλ‹€.

4. λͺ¨λ“  ν•„ν„°λ₯Ό ν†΅κ³Όν•œ 후에야, μ„œλΈ”λ¦Ώ λ˜λŠ” μŠ€ν”„λ§ MVC에 μš”μ²­μ΄ λ„λ‹¬ν•œλ‹€.

 

 

 

μŠ€ν”„λ§μ—μ„œ ν•„ν„°λ₯Ό κ΅¬ν˜„ν•˜λŠ” 방식

μŠ€ν”„λ§μ—μ„œ ν•„ν„°λŠ” 두 κ°€μ§€ λ°©μ‹μœΌλ‘œ μ‚¬μš©ν•  수 μžˆλ‹€.

 

1. μ„œλΈ”λ¦Ώ ν•„ν„° (Servlet Filter)

  • javax.servlet.Filter μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” ν•„ν„°
  • μŠ€ν”„λ§ λΆ€νŠΈμ—μ„œ <<FilterRegistrationBean>> 을 μ‚¬μš©ν•˜μ—¬ ν•„ν„°λ₯Ό 등둝할 수 μžˆλ‹€.

2. μŠ€ν”„λ§ μ‹œνλ¦¬ν‹° ν•„ν„° (Spring Security Filter)

  • 인증 및 인가λ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•œ μŠ€ν”„λ§ μ‹œνλ¦¬ν‹°μ˜ ν•„ν„° 체인
  • μ—¬λŸ¬ 개의 μ‹œνλ¦¬ν‹° 필터듀이 μš”μ²­μ„ κ²€μ¦ν•œ ν›„ μš”μ²­μ΄ μ²˜λ¦¬λœλ‹€.

 

 

ν•„ν„° μ‚¬μš© μ˜ˆμ‹œ (μ„œλΈ”λ¦Ώ ν•„ν„°)

1. κΈ°λ³Έ ν•„ν„° κ΅¬ν˜„

 

1.1 LoggingFilter.java

package com.example.demo.filter;

import javax.servlet.*;
import java.io.IOException;

public class LoggingFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("LoggingFilter initialized");
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        System.out.println("LoggingFilter: Request is being filtered");
        chain.doFilter(request, response);
        System.out.println("LoggingFilter: Response is being filtered");
    }

    @Override
    public void destroy() {
        System.out.println("LoggingFilter destroyed");
    }
}

 

 

1.2 AuthenticationFilter.java

package com.test.test1.filter;

import com.test.test1.wrapper.CharResponseWrapper;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.io.PrintWriter;

public class AuthenticationFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("AuthnticationFilter initialized");
    }


    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        // μš”μ²­ 데이터λ₯Ό 읽음
        String requestData = request.getParameter("body");
        System.out.println("Request data: " + requestData); // μš”μ²­ 데이터 좜λ ₯ (예: ν΄λΌμ΄μ–ΈνŠΈκ°€ 보낸 'body' νŒŒλΌλ―Έν„°)

        // 응닡을 μˆ˜μ •ν•˜κΈ° μœ„ν•΄ responseλ₯Ό λž˜ν•‘, μ΄λ ‡κ²Œ ν•˜λ©΄ 버퍼에 μ €μž₯이 되고 μ‹€μ œ 데이터λ₯Ό μˆ˜μ •ν•  수 μžˆλ‹€.
        CharResponseWrapper wrappedResponse = new CharResponseWrapper((HttpServletResponse) response);

        // λ‹€μŒ ν•„ν„° λ˜λŠ” μ„œλΈ”λ¦Ώ(컨트둀러) 호좜 -> μ΅œμ’… ν•„ν„°κΉŒμ§€ κ°€μ„œ μ΅œμ’… ν•„ν„°μ˜ 응닡값을 λ³€ν˜•ν•˜λ©΄μ„œ 졜초 μ—¬κΈ° κΉŒμ§€ 옴
        chain.doFilter(request, wrappedResponse);

        // μ›λž˜ 응닡 데이터λ₯Ό κ°€μ Έμ˜΄
        String originalResponseContent = wrappedResponse.getCapturedResponse();
        System.out.println("Original Response: " + originalResponseContent);

        // 응닡 데이터λ₯Ό μˆ˜μ • (예: 응닡 본문에 μš”μ²­ 데이터λ₯Ό μΆ”κ°€)
        String modifiedResponseContent = originalResponseContent + "\nModified response with request data: " + requestData;

        byte[] responseBytes = modifiedResponseContent.getBytes("UTF-8");
        // content-lengthλŠ” λ°”μ΄νŠΈ 수λ₯Ό μš”κ΅¬ν•œλ‹€. μ˜μ–΄, λ„μ–΄μ“°κΈ°λŠ” μ „λΆ€ 1λ°”μ΄νŠΈμ§€λ§Œ, ν•œκΈ€μ€ κ°œλ‹Ή 3λ°”μ΄νŠΈμ΄κΈ° λ•Œλ¬Έμ— λ‹¨μˆœ κΈ€μž 길이둜 ν–ˆμ„ 경우 μ „λΆ€ λͺ» 읽어 λ‚Έλ‹€.
        // UTF-8은 μ°Έκ³ ν•˜μžλ©΄ νŠΉμ • μ–Έμ–΄λ₯Ό 컴퓨터 μ–Έμ–΄ λΉ„νŠΈμΈ μ €κΈ‰μ–Έμ–΄λ‘œ λ³€ν˜•ν•˜λŠ” 즉, 인코딩 방식이닀.
        response.setContentLength(responseBytes.length);
        System.out.println(responseBytes.length); // -> ν•œκΈ€λ‘œ μΈν•΄μ„œ μ˜μ–΄λ‘œλ§Œ λ˜μ–΄ μžˆλ‹€λ©΄ 52λ°”μ΄νŠΈκ°€ 56λ°”μ΄νŠΈκ°€ λœλ‹€.


        // μˆ˜μ •λœ 응닡을 ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 전솑

        //λ¬Έμžμ—΄μ„ 직접 λ‹΄μ•„μ„œ 보내고, 이 λ¬Έμžμ—΄μ€ λ‚΄λΆ€μ μœΌλ‘œ λ°”μ΄νŠΈ λ°°μ—΄λ‘œ λ³€ν™˜λ˜μ–΄ μ „μ†‘ν•œλ‹€. 그러면 ν΄λΌμ΄μ–ΈνŠΈλŠ” 이 λ°”μ΄νŠΈ 배열을 λ‹€μ‹œ λ¬Έμžμ—΄λ‘œ λ””μ½”λ”©ν•˜μ—¬ μ‚¬μš©μžκ°€
        //λ³Ό 수 μžˆλŠ” ν˜•νƒœλ‘œ ν‘œμ‹œ

        // μ΅œμ’… ν•„ν„°κΉŒμ§€ κ°„ λ’€, μ—­μˆœμœΌλ‘œ λŒμ•„μ˜€λ©΄μ„œ response 데이터가 λ³€ν˜•μ΄ 일어날텐데, μ΄λ•Œ 버퍼에 μ €μž₯ν•΄λ‘λ©΄μ„œ λ³€ν˜•ν•˜κ³  μ΅œμ’… λ³€ν˜•ν•œ λ’€, λ²„νΌμ—μ„œ ν΄λΌμ΄μ–ΈνŠΈλ‘œ 응닡
        response.getWriter().write(modifiedResponseContent);

//        response.getOutputStream().write(responseBytes); -> μ΄κ±°λŠ” λ°”λ‘œ λ°”μ΄νŠΈ λ‹¨μœ„λ‘œ 전솑

    }

    @Override
    public void destroy() {
        System.out.println("AuthenticationFilter destroyed");
    }


}

 

 

μœ„ μ½”λ“œ 처럼 응닡 데이터λ₯Ό κ°€μ Έμ™€μ„œ 탐지 등을 ν•˜κ±°λ‚˜ λ³€μ‘°λ₯Ό ν•œ λ’€ μ‘λ‹΅κ°’μœΌλ‘œ 보낼 수 μžˆλ‹€.

 

 

2. ν•„ν„° 등둝 

 

μ—¬λŸ¬ ν•„ν„°λ₯Ό λ“±λ‘ν•˜λ €λ©΄ <<FilterRegistrationBean>> μ„ μ‚¬μš©ν•˜μ—¬ 각각의 ν•„ν„°λ₯Ό μ„€μ •ν•΄μ•Ό ν•œλ‹€.

 

package com.example.demo.config;

import com.example.demo.filter.LoggingFilter;
import com.example.demo.filter.AuthenticationFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FilterConfig {

    @Bean
    public FilterRegistrationBean<LoggingFilter> loggingFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());
        registrationBean.addUrlPatterns("/api/*");  // ν•„ν„°κ°€ 적용될 URL νŒ¨ν„΄
        registrationBean.setOrder(1);  // ν•„ν„° μ‹€ν–‰ μˆœμ„œ μ„€μ •
        return registrationBean;
    }

    @Bean
    public FilterRegistrationBean<AuthenticationFilter> authenticationFilter() {
        FilterRegistrationBean<AuthenticationFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new AuthenticationFilter());
        registrationBean.addUrlPatterns("/api/*");  // ν•„ν„°κ°€ 적용될 URL νŒ¨ν„΄
        registrationBean.setOrder(2);  // ν•„ν„° μ‹€ν–‰ μˆœμ„œ μ„€μ •
        return registrationBean;
    }
}

 

 

3. ν•„ν„° μ‹€ν–‰ μˆœμ„œ

setOrder(int order) :  ν•„ν„°μ˜ μ‹€ν–‰ μˆœμ„œλ₯Ό μ„€μ •ν•œλ‹€. μˆ«μžκ°€ μž‘μ„μˆ˜λ‘ λ¨Όμ € μ‹€ν–‰λœλ‹€. μœ„μ˜ μ˜ˆμ—μ„œλŠ” LoggingFilterκ°€ λ¨Όμ € μ‹€ν–‰λ˜κ³ , κ·Έ λ‹€μŒμ— AuthenticationFilter κ°€ μ‹€ν–‰λœλ‹€.

 

 

3.5 Controller μ„€μ •

package com.test.test1.controller;


import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    
    @GetMapping("/api/hello")
    public String hello() {
        return "Hello, World";
    }
}

 

4. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μ‹€ν–‰

이제 μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ‹€ν–‰ν•˜κ³  /api/ 둜 μ‹œμž‘ν•˜λŠ” μš”μ²­μ„ 보내면, λ“±λ‘λœ ν•„ν„°κ°€ μ°¨λ‘€λ‘œ μ‹€ν–‰λœλ‹€.

 

μœ„μ˜ Controllerλ₯Ό μ‚¬μš©ν•΄μ„œ Postman에 url인 http://localhost:8080/api/hello λ₯Ό μš”μ²­ν•˜λ©΄ ν•΄λ‹Ή ν•„ν„°λ₯Ό μ •ν•΄μ€€ μˆœμ„œλŒ€λ‘œ κ±°μΉœλ‹€.

 

LoggingFilter initialized
AuthenticationFilter initialized
LoggingFilter: Request is being filtered
AuthenticationFilter: Checking authentication
AuthenticationFilter: Response is being filtered
LoggingFilter: Response is being filtered

 

 

5. μ „λ°˜μ μΈ 흐름

 

ν΄λΌμ΄μ–ΈνŠΈ -> ν•„ν„° -> 컨트둀러 -> ν•„ν„° -> ν΄λΌμ΄μ–ΈνŠΈ


 

μš”μ¦˜ 많이 μ ‘ν•˜κ³  μžˆλŠ” ν•„ν„° λΆ€λΆ„ ν•œ 번 정리해 λ³΄μ•˜λ‹€. μ΄λ²ˆμ—λŠ” Spring Bootλ₯Ό μ‚¬μš©ν•  λ•Œλ‹€ λ³΄λ‹ˆ Gradleλ₯Ό ν™œμš©ν•œ ν•„ν„° κ΅¬μ‘°μ˜€λ‹€.

 

λ‹€μŒ λ²ˆμ—λŠ” 전톡적인 ν•„ν„° 적용 방식인 web.xmlλ₯Ό ν™œμš©ν•΄μ„œ 해보렀고 ν•œλ‹€.