프로젝트[종료]

16. ArgumentResolver사용하기, 로그아웃 기능 추가

알렉스 페레이라 2023. 5. 11. 19:29

ArgumentResolver란?

  • 특정 요청이 Controller로 들어왔을때, 넘어온 값을 미리 가공하여 넘겨주는 작업을 해주는것.
  • Controller단에서 중복되는 작업을 방지할 수 있다. ex)@Sign Annotation으로 User객체가 넘어온다면 세션에서 읽어서 가져오는 작업.

나는 ArgumentResolver를 사용해서, Home화면에 갔을때 로그인한 사용자라면 로그인전용 Home화면을, 로그인하지 않은 사용자라면 기본Home화면으로 보낼 생각이다.

 

순서는 다음과 같다.

 

1.@Sign Annotation 생성

경로(\src\main\java\com\project\domain\sign\Sign.java)

package com.project.domain.sign;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER) //파라미터에서 사용되는 Annotation임을 명시.
@Retention(RetentionPolicy.RUNTIME) //어노테이션을 런타임시에까지 사용할 수 있음. JVM이 자바 바이트코드가 담긴 class 파일에서 런타임환경을 구성하고 런타임을 종료할 때까지 메모리는 살아있음을 명시.
public @interface Sign {
}

 

 

2.SignInArgumentResolver.java 파일 생성

경로(\src\main\java\com\project\web\session\SignInArgumentResolver.java)

 

package com.project.web.session;

import com.project.domain.sign.Sign;
import com.project.domain.sign.SignInForm;
import com.project.domain.user.User;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.hibernate.Session;
import org.springframework.core.MethodParameter;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

public class SignInArgumentResolver implements HandlerMethodArgumentResolver {

    //해당 Resolver가 동작해야하는지 여부를 return한다.
    @Override
    public boolean supportsParameter(MethodParameter parameter) {

        //@Sign이라는 Annotation이 넘어왔는지 여부
        boolean hasSignInAnnotation = parameter.hasParameterAnnotation(Sign.class);

        //넘어온 파라미터가 User.class인지 여부
        boolean hasSignInType = User.class.isAssignableFrom(parameter.getParameterType());

        return hasSignInAnnotation && hasSignInType;
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        //세션에서 로그인정보를 읽어 return해준다.
        HttpServletRequest httpRequest = (HttpServletRequest) webRequest.getNativeRequest();
        HttpSession session = httpRequest.getSession(false);

        if (session == null) {
            return null;
        }

        return session.getAttribute(SessionConst.SESSION_NAME);
    }
}

 

3.WebConfig.java에 SignInArgumentResolver을 등록해준다.

package com.project.config;

import com.project.web.session.SessionFilter;
import com.project.web.session.SessionInterceptor;
import com.project.web.session.SignInArgumentResolver;
import jakarta.servlet.Filter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new SignInArgumentResolver());
    }

    @Override
    public void addInterceptors(InterceptorRegistry registry) {

        registry.addInterceptor(new SessionInterceptor())
                .order(0) //interceptor의 순서를 정한다.
                .addPathPatterns("/**") // [/**]는 전부 Interceptor의 범위에 있다는 뜻이다.
                .excludePathPatterns("/", "/sign/**", "/assets/**", "/images/**"); //다음과 같은 url은 interceptor에서 제외한다.
    }

//    @Bean
    public FilterRegistrationBean sessionFilter() {
        FilterRegistrationBean<Filter> sessionFilterRegistrationBean = new FilterRegistrationBean<Filter>();
        sessionFilterRegistrationBean.setFilter(new SessionFilter()); //SessionFilter 객체를 등록한다.
        sessionFilterRegistrationBean.setOrder(0); //Filter의 순서를 정한다. 순서가 작을수록 chain에 먼저 걸림
        sessionFilterRegistrationBean.addUrlPatterns("/*"); //해당 필터가 적용될 URL 패턴을 지정한다.

        return sessionFilterRegistrationBean;
    }
}

 

4.홈화면으로 이동하는 RequestMapping에서, ArgumentResolver가 반환한 Session여부에 따라 분기한다.

package com.project.web;

import com.project.domain.sign.Sign;
import com.project.domain.sign.SignInForm;
import com.project.domain.user.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class IndexController {

    @GetMapping("/")
    public String index(@Sign User user, Model model) {
        //세션이 없다면 그냥 Home화면으로 이동
        if (user == null) {
            return "/index";
        }

        //세션이 있다면 model에 담아 로그인사용자 전용 Home화면으로 이동
        model.addAttribute("user", user);
        return "/login";
    }

    @GetMapping("/elements")
    public String element(){
        return "/elements";
    }

    @GetMapping("/generic")
    public String generic(){
        return "/generic";
    }

}

 

5. login.html을 만들고,  로그인한 사용자가 넘어왔을시에는 로그아웃버튼을 출력한다.

이전에 사용했던 fragment를 이용해 로그인사용자 전용 버튼들의 영역을 추가.

경로(\src\main\resources\templates\fragment\loginUser-fragment.html)

<header id="header" th:fragment="loginUser">
    <div class="inner">
        <a href="/" class="logo">
            <span class="symbol"><img src="/images/logo.svg" alt="" /></span><span class="title" th:text="#{index.title}">금융권 데이터 서비스</span>
        </a>
        <nav>
            <ul>
                <li>
                    <a href="#" class="button" th:onclick="|location.href='@{/sign/signUp}'|" th:text="#{signUp}" data-toggle="modal" data-target="#signUp">Sign Up</a>
                </li>
                <li><a href="#" class="button" th:onclick="|location.href='@{/sign/signOut}'|" th:text="#{signOut}">Sign Out</a></li>
                <li><a href="#menu">Menu</a></li>
            </ul>
        </nav>
    </div>
</header>

 

 

로그인한 사용자는 loginUser fragment가 노출된다

6. SignController에 로그아웃 RequestMapping을 추가한다.

    //로그아웃 start
    @GetMapping("/signOut")
    public String getSignOut(@Sign User user, HttpServletRequest request) {
        HttpSession session = request.getSession();
        User loginUser = (User)session.getAttribute(SessionConst.SESSION_NAME);

        if (loginUser != null) {
            session.invalidate();
            return "redirect:/";
        }
        return "/";
    }
    //로그아웃 end

 

 

 

7.서버를 재기동한다.

 

로그인하지 않은 사용자의 홈화면

 

 

로그인한 사용자의 홈 화면. 로그아웃 버튼을 클릭한다.

 

 

 

성공!

 

근데 문제가 있다. 홈화면은 로그아웃, 로그인버튼이 잘 나오는데

 

/generic이라던지, /element같은 게시글 조회화면은 로그인한 사용자임에도 불구하고 아래와 같이 나온다.

 

 

모든 페이지에서 특정영역을 Session여부에 맞게 동적으로 변경하려면 어떻게 해야할까? 고민을 좀더 해봐야겠다..