공부/그룹 스터디

[5회차 05] Filter - 페이지 접근 처리

junani0v0 2024. 5. 7. 23:24

페이지 접근 관리

IndexController

현재 로그인을 해야 main을 갈 수 있게 하도록 하겠습니다

@RequestMapping("/mainPage.do")
public String mainPage() {
    return "main";
}

IndexController에 간단하네 mainPage를 가는 메서드를 만들어 줍니다

현재는 mainPage.do를 하면 바로 main페이지가 보이는데요 이걸 로그인을 안하면 접속을 못하게 바꾸어 보겠습니다

로그인 여부는 어떻게 알까요?
로그인을 하면 session이 생기기에 session을 활용해 로그인 여부를 체그해줍니다

@RequestMapping("/mainPage.do")
public String mainPage(HttpServletRequest request) {
    HttpSession session = request.getSession();
    if(!ObjectUtils.isEmpty(session.getAttribute("memberId"))) {
        return "main";
    }
    return "login";
}


실행해보면 이렇게 로그인을 안하면 로그인 페이지로 보내줍니다 하지만 주소창을 보면 mainPage.do로 페이지랑 따로 놀게됩니다

@RequestMapping("/mainPage.do")
public String mainPage(
        HttpServletRequest request,
        HttpServletResponse response) throws IOException{
    HttpSession session = request.getSession();
    if(ObjectUtils.isEmpty(session.getAttribute("memberId"))) {
        response.sendRedirect(request.getContextPath() + "loginPage.do");
    }
    return "main";
}

이걸 해결하기 위해 HttpServletResponse를 파라미터에 추가하고 sendRedirect를 이용해 loginPage.do로 보내줍니다
여기서 단점은 request가 아닌 response를 사용하면 값을 못뽑아와서 알림 메시지를 띄울 수 없습니다
그렇기에 response.sendRedirect바로 처리할때(리다이렉트 할때)는 이렇게 쓰지만 무조건 Get방식

  • request는 페이지를 다룰때 주로 사용 (레거시에서는 request 많이 사용)()
  • response는 페이지 자체를 Redirect하거나 request에 대한 응답으로만 사용 (text를 실어 보낼 수 있음 - Rest APIresponse)
  • 처음 코드에서 request를 사용할 경우 mainPage.do가 안바뀐이유는 request가 갔지만 다시 response가 온게 아니어서 그렇습니다 반대로 response의 경우에는 request의 응답 자체를 redirect 시킨 것 입니다 (request가 끝나고(끊어지고) response로 보내는 것 입니다)

Tip.

  • response.sendRedirect() : ()안에 있는 해당 경로로 이동하라는 의미
    서블릿에서 처리한 데이터를 별도로 넘기지 않고 페이지만 이동 (session 객체와는 별개, 무조건 Get방식)

  • request.getContextPath() : 프로젝트 Path만 가져옵니다
    (ex.http://localhost:8080/05/mainPage.do -> /05만 가져옵니다)

  • request.getRequestURI() : 프로젝트 + 파일경로 가져옵니다
    (http://localhost:8080/05/mainPage.do -> /05/mainPage.do까지 가져옵니다)

  • request.getRequestURL() : 전체 경로를 가져옵니다
    (ex.http://localhost:8080/05/mainPage.do -> http://localhost:8080/05/mainPage.do전체 가져옵니다)

  • request.ServletPath() : 파일명만 가져옵니다
    (ex.http://localhost:8080/05/mainPage.do -> /mainPage.do만 가져옵니다)

  • request.getRealPath() : 서버 or 로컬 웹 애플리케이션 절대경로 가져옵니다
    (ex.http://localhost:8080/05/mainPage.do -> C:\dev\workspace\05_springJdbc_Self\ 가져옵니다)

Filter

filter 생성 - LoginFilter

하지만 하나바꾸는데 이렇게했는데 여러개일 경우에는 어떻게 해결할까요?
바로 filter를 사용해 해결해 볼 수 있습니다
먼저 com.portfolio.www.filter 패키지를 만들고 그안에 filter만들기를 선택후
이름을 LoginFilter하고 next를 누르면 아래 Filter Mappings을 눌러 URL pattern -> Servlet으로 변경 -> dispatcher request 선택 -> finish

@WebFilter(dispatcherTypes = {DispatcherType.REQUEST }
                    , servletNames = { "pf" })
public class LoginFilter extends HttpFilter implements Filter {

    //생성자
    public LoginFilter() {
        super();
    }

    //죽을 때
    public void destroy() {

    }

    //Filter 작동할 때
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

        chain.doFilter(request, response);
    }

    public void init(FilterConfig fConfig) throws ServletException {

    }

}

이렇게 만든 LoginFilter는 Filter를 구현하고 HttpFilter를 확장합니다

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    String uri = req.getRequestURI();
    System.out.println("----------------uri : " + uri);
    chain.doFilter(request, response);
}

uri값을 찍어보겠습니다 그전에 web.xml으로 가줍니다

web.xml

<!-- 필터 등록 -->
<filter>
    <filter-name>loginFilter</filter-name>
    <filter-class>com.portfolio.www.filter.LoginFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>loginFilter</filter-name>
    <servlet-name>pf</servlet-name>
    <url-pattern>*.do</url-pattern>
</filter-mapping>

filter를 등록해줍니다

LoginFilter.java

@WebFilter(dispatcherTypes = {DispatcherType.REQUEST }
                    , servletNames = { "pf" })
public class LoginFilter extends HttpFilter implements Filter {

    //생성자
    public LoginFilter() {
        super();
        System.out.println("--------LoginFilter constructor--------");
    }

    //필터 죽으면
    public void destroy() {

    }

    //필터 동작하면
    public void doFilter(HttpServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        String uri = request.getRequestURI();
        System.out.println("----------------" + uri);
        chain.doFilter(request, response);
    }

    //필터 등록되면
    public void init(FilterConfig fConfig) throws ServletException {
        System.out.println("--------LoginFilter init--------");
    }

}

console창을 확인하여 작동을 확인합니다

그러고 .do 페이지에 들어가보면

이렇게 uri가 찍히는 것을 확인할 수 있습니다

그럼 이 uri값을 활용하여 페이지를 처리할 수 있겠지요?

먼저 방법은 아래 3가지 있습니다

if(StringUtils.pathEquals(uri, "/05/mainPage.do")) {

}
if(uri.contains("/05/mainPage.do")) {

}
if(uri.indexOf("/05/mainPage.do") > 1) {

}

여기서 첫번째껀 context-root가 붙어있으니 사용이 불편할꺼 같으니 패스
세번째껀 숫자비교이기 때문에 패스
그렇게 이번에는 두번째 방법을 사용하겠습니다

Tip. String 값 확인

- `StringUtils` : 자바의 문자열에 관련된 작업에 유용한 라이브러리(null값을 주더라고 NullPointException이 발생X - 기본 결과값 반환) - `StringUtils.pathEquals( , )` : 두 경로를 비교하여 같은지 확인 - `uri.contains` : 문자열이 지정된 부분 문자열을 포함했는지 확인 - `uri.indexOf` : 주어진 부분 문자열이 처음으로 발견된 인덱스를 반환, 없으면 -1 반환
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse resp = (HttpServletResponse) response;
    String uri = req.getRequestURI();
    System.out.println("----------------uri : " + uri);

    if(uri.contains("/mainPage.do")) {
        HttpSession session = req.getSession();
        if(ObjectUtils.isEmpty(session.getAttribute("memberId"))) {
            resp.sendRedirect(req.getContextPath() + "/loginPage.do");
            return ;
        }
    }
    chain.doFilter(request, response);
}

getAttribute를 활용해 "memberId"값을 가져옵니다 값이 있는지 확인하고 값이 비어있으면 loginPage.do로 redirect시킵니다

IndexController

IndexController가서 위에서 redirect한 코드를 삭제합니다

@RequestMapping("/mainPage.do")
public String mainPage(
        HttpServletRequest request,
        HttpServletResponse response) throws IOException{

    return "main";
}

그리고 서버를 재시작해 mainPage.do를 실행해보면 로그인이 안되있으니 loginPage.do로 가는 것을 확인 할 수 있습니다

doFilter 마지막에 있는

chain.doFilter(request, response);

를 지우면 어떻게 될까요?

바로 filter에 chain이 넘어와야하는데 넘어오지 않아 더이상의 filter를 실행시키지 않아 하얀 화면만 나오게됩니다

추가로 sendRedirect시킬때 html도 가능하고, 이미지만 보여주기 등이 가능합니다(response는 get방식이라 이렇게해서 사용)

여러 페이지 필터링 - LoginFilter

/mainPage.do
/develop.do
/support.do
/sales.do
/fresh.do
/stage.do
/prod.do
/qa.do
전부 로그인 테스트하려면 어떻게 할까요?

정답은 배열을 활용하면 됩니다
실습을 해보면

private final String[] LOGIN_REQUIRED_URI = {
        "/mainPage.do", //보통 무슨 페이지인지 주석 달아줌
        "/develop.do",
        "/support.do",
        "/sales.do",
        "/fresh.do",
        "/stage.do",
        "/prod.do",
        "/qa.do"
};

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
    HttpServletRequest req = (HttpServletRequest) request;
    HttpServletResponse resp = (HttpServletResponse) response;
    String uri = req.getRequestURI();
    System.out.println("----------------uri : " + uri);

    if(Arrays.asList(LOGIN_REQUIRED_URI).contains(uri.replace("/05", ""))) {
        HttpSession session = req.getSession();
        if(ObjectUtils.isEmpty(session.getAttribute("memberId"))) {
            resp.sendRedirect(req.getContextPath() + "/loginPage.do");
            return ;
        }
    }
    chain.doFilter(request, response);
}

위처럼 LOGIN_REQUIRED_URI배열에 값을 담고 Arrays.asList(LOGIN_REQUIRED_URI)해서 배열 값을 List로 변환 contains(uri.replace("/05", ""))로 앞에 있는 값인 "/05"를 제거하고 문자열을 포함하는지 확인합니다
(List에도 contains가 있다는걸 기억하세요)
(처음부터 List객체르 만들려면 손이 많이가니 배열로 만들어 List로 변환해 사용합니다)

Tip.

  • Arrays.asList() : ()안의 값을 List로 변환
  • contains(uri.replace( , )) : 괄호 앞에 있는걸 삭제후 뒤에 조건과 같은지 확인

한글 인코딩 필터 추가

web.xml

항상 기본으로 달아주는 필터를 추가해 보겠습니다
바로 인코딩 필터인데요
기본적으로 한글이 깨지니 꼭 추가해주세요

<!-- 한글 처리를 위한 필터 설정 -->
<filter>
    <filter-name>encodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
        <param-name>encoding</param-name>
        <param-value>UTF-8</param-value>
    </init-param>
    <init-param>
        <param-name>forceEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>encodingFilter</filter-name>
    <url-pattern>/*</url-pattern>    <!-- 모든 url에 적용 -->
</filter-mapping>

코드를 보시면 param이 2개 필요합니다
forceEncoding도 매서드 이름입니다