Spring Example: ToDo List #6 스프링 인터셉터

함형주·2022년 10월 5일
0

Spring Example: ToDo

목록 보기
7/16

질문, 피드백 등 모든 댓글 환영합니다.

이번 시간에는 스프링 인터셉터를 활용하여 로그인 하지 않은 사용자가 todo 메인페이지, todo 생성, 수정, 삭제 uri에 접근을 막고 로그인페이지로 이동시키는 기능을 개발합니다. 또한 todo의 수정, 삭제 요청 시 존재하지 않는 todo를 호출하거나 다른 사용자의 todo에 접근할 수 없도록합니다.
또한 실수로 빼먹고 넘어갔던 로그아웃 기능을 개발합니다.

LoginInterceptor

LoginInterceptor

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("loginMember") == null) {
            response.sendRedirect("/login?redirectURL="+requestURI);
            return false;
        }
        return true;
    }
}

컨트롤러(정확히는 핸들러 어댑터)전에 호출되는 preHandle을 오버라이딩하여 세션쿠키 검증
현재 로그인 되어있지 않은 경우 로그인 url로 리다이렉트
로그인 후 접근을 시도한 uri로 이동할 수 있도록 uri를 쿼리 파라미터에 포함시킴

LoginController

public class LoginController {

    @PostMapping("/login")
    public String login(@Validated @ModelAttribute LoginDto loginDto, BindingResult bindingResult,
                        @RequestParam(defaultValue = "/todo") String redirectURL,
                        HttpServletRequest request) {
        if (bindingResult.hasErrors()) {
            return "/login/form";
        }

        Optional<Member> loginMember = loginService.login(loginDto.getLoginId(), loginDto.getPassword());

        if (loginMember.isEmpty()) {
            bindingResult.reject("loginFail", "id password 에러");
            return "/login/form";
        }

        HttpSession session = request.getSession();
        session.setAttribute("loginMember", loginMember.get());

        return "redirect:" + redirectURL;
    }
}

LoginInterceptor에서 추가한 리다이렉트 파라미터를 처리할 수 있도록 @RequsetParam 추가

Configurer

public class Configurer implements WebMvcConfigurer {

    private final ToDoService toDoService;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                .order(1)
                .addPathPatterns("/**")
                .excludePathPatterns("/", "/login", "/logout", "/add", "/error", "/css/**", "/js/**");
    }
}

회원가입, 로그인, 로그아웃, css, js, 에러, 첫 페이지 경로에 대한 요청을 제외한 나머지 요청에 LoginInterceptor 적용

ToDoInterceptor

ToDoInterceptor

@RequiredArgsConstructor
public class ToDoInterceptor implements HandlerInterceptor {

    private final ToDoService toDoService;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String requestURI = request.getRequestURI();
        Member loginMember = (Member) request.getSession(false).getAttribute("loginMember");

        int pos = requestURI.lastIndexOf("/");
        Long toDoId = Long.parseLong(requestURI.substring(pos + 1));
        Optional<ToDo> findById = toDoService.findById(toDoId);
        if (findById.isEmpty() || !findById.get().getMember().getId().equals(loginMember.getId())) {
            response.sendRedirect("/todo");
            return false;
        }
        return true;
    }
}

false를 반환하는 경우 :
1. todo가 존재하지 않는 경우
2. todo가 가진 memberId와 세션쿠키에 저장된 member의 id가 다를 경우(다른 사용자의 todo에 접근한 경우)

Configurer

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new ToDoInterceptor(toDoService))
                .order(2)
                .addPathPatterns("/todo/update/**", "/todo/change/**", "/todo/delete/**");
    }

todo의 수정, 삭제 요청에 적용

Logout

LoginController

    @GetMapping("/login")
    public String loginForm(@ModelAttribute("loginDto") LoginDto loginDto, HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        return session != null && session.getAttribute("loginMember") != null ? "redirect:/" : "/login/form";
    }
    
    @PostMapping("/logout")
    public String logout(HttpServletRequest request) {
        HttpSession session = request.getSession(false);
        if (session != null && session.getAttribute("loginMember") != null) session.invalidate();

        return "redirect:/";
    }

HttpSession의 invalidate()를 이용해 쿠키를 만료(삭제)시키고 첫 페이지로 이동
로그인 사용자는 /login 경로로 접근 불가능

Logout 버튼 생성

Logout.html

<body>
<div class="col offset-10" th:fragment="logout">
    <form th:action="@{/logout}" method="post">
        <button type="submit" class="mt-2 col-auto btn btn-light">로그아웃</button>
    </form>
</div>
</body>

로그아웃 버튼은 여러 페이지에서 사용되므로 th:fragment 기능을 이용
th:fragment 가 적용된 태그를 다른 html에서 호출하여 사용할 수 있음

home.html

<body>
<div th:if="${session.loginMember}">
    <div th:replace="~{header/logout :: logout}"></div>
</div>

<button class="w-100 btn btn-secondary btn-lg" th:unless="${session.loginMember}" 
        onclick="location.href='items.html'" th:onclick="|location.href='@{/login}'|" 
        type="button">
로그인
</button>
<button class="w-100 btn btn-secondary btn-lg" th:if="${session.loginMember}" 
        onclick="location.href='items.html'" th:onclick="|location.href='@{/todo}'|" 
        type="button">
할 일
</button>

th:replce로 th:fragment 호출 가능
로그인 여부에 따라 로그아웃 버튼 활성화
로그인 여부에 따라 로그인/할 일 버튼 출력

main.html

<body>
<div th:replace="~{header/logout :: logout}"></div>

다음으로

main에 출력되는 todo를 마감일과 생성일을 기준으로 정렬하여 출력하도록 수정합니다.


github , 배포 URL (첫 접속 시 로딩이 걸릴 수 있습니다.)

profile
평범한 대학생의 공부 일기?

0개의 댓글