[Spring] 스프링 시큐리티 - 어노테이션 기반으로 개선하기

Jin·2023년 9월 5일
0

Spring

목록 보기
8/9
post-thumbnail

1. 개요

일반적으로 프로그래밍에서 같은 코드가 반복되는 것은 개선이 필요한 나쁜 코드에 속한다. 같은 코드를 계속해서 복사 및 붙여넣기로 반복한다면, 이후에 수정이 필요할 때 모든 부분을 하나씩 찾아가며 수정해야 한다. 이러한 경우는 유지 보수성이 떨어질 수밖에 없으며, 혹시나 수정이 반영되지 않은 코드가 있다면 문제가 발생할 수 있다.

그러면 앞서 만든 코드에서 개선이 필요할 만한 부분을 찾아보자. 아마 HomeController에서 세션 값을 가져오는 부분일 것이다.

SessionUser user = (SessionUser) httpSession.getAttribute("user");

해당 코드가 작성된 메서드 외에 다른 컨트롤러와 메서드에서 세션 값이 필요하면, 그때마다 직접 세션에서 값을 가져와야 한다. 이는 결국 같은 코드가 반복되는 것이므로 불필요하다. 따라서 이 부분을 메서드 인자로 세션 값을 바로 받을 수 있도록 변경할 것이다.

2. @LoginUser 어노테이션 생성하기

config.auth 패키지에 @LoginUser 어노테이션을 생성하자.

package toy.project.bulletin_board.config.auth;

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

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface LoginUser {
}
  • @Target(ElementType.PARAMETER)
    - 해당 어노테이션이 생성될 수 있는 위치 지정
    - PARAMETER로 지정 ⇒ 메서드의 파라미터로 선언된 객체에서만 사용 가능
  • @interface
    - 해당 파일을 어노테이션 클래스로 지정
    - 즉, LoginUser라는 이름을 가진 어노테이션이 생성되었다고 보면 됨

3. LoginUserArgumentResolver 생성하기

같은 위치에 LoginUserArgumentResolver를 생성하자. 이 클래스는 HandlerMethodArgumentResolver 인터페이스를 구현한 클래스이다.

참고: HandlerMethodArgumentResolver 인터페이스가 지원하는 기능
조건에 맞는 경우, 메서드가 있다면 HandlerMethodArgumentResolver의 구현체가 지정한 값으로 해당 메서드의 파라미터로 넘길 수 있다. 자세한 사용법은 직접 만들면서 배워보자.

package toy.project.bulletin_board.config.auth;

import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
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;
import toy.project.bulletin_board.config.auth.dto.SessionUser;

@RequiredArgsConstructor
@Component
public class LoginUserArgumentResolver implements HandlerMethodArgumentResolver {

    private final HttpSession httpSession;

    // 컨트롤러 메서드의 특정 파라미터를 지원하는지 확인
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        // 파라미터에 @LoginUser 어노테이션이 붙어있으면 true
        boolean isLoginUserAnnotation = parameter.getParameterAnnotation(LoginUser.class) != null;

        // 파라미터 클래스 타입이 SessionUser.class면 true
        boolean isUserClass = SessionUser.class.equals(parameter.getParameterType());

        return isLoginUserAnnotation && isUserClass;
    }

    // 파라미터에 전달할 객체를 세션에서 가져오기
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        return httpSession.getAttribute("user");
    }
}
  • supportsParameter
    - 컨트롤러 메서드의 특정 파라미터를 지원하는지 확인
    - 파라미터에 @LoginUser 어노테이션이 붙어있고, 파라미터 클래스 타입이 SessionUser.class인 경우 true 반환
  • resolveArgument
    - 파라미터에 전달할 객체를 세션에서 가져와서 반환

@LoginUser를 사용하기 위한 환경은 구성되었다. 이제 이렇게 생성된 LoginUserArgumentResolver스프링에서 인식될 수 있도록 WebMvcConfigurer에 추가하면 된다.

4. WebMvcConfigurer 추가하기

config 패키지에 WebMvcConfig 클래스를 생성하여 아래와 같이 설정을 추가하자.

package toy.project.bulletin_board.config;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import toy.project.bulletin_board.config.auth.LoginUserArgumentResolver;

import java.util.List;

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {
    private final LoginUserArgumentResolver loginUserArgumentResolver;

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(loginUserArgumentResolver); // HandlerMethodArgumentResolver 추가
    }
}

HandlerMethodArgumentResolver는 항상 WebMvcConfigureraddArgumentResolvers()를 통해 추가해야 한다. 만약 다른 HandlerMethodArgumentResolver가 필요하다면 같은 방식으로 추가하면 된다.

최종적으로 스프링 시큐리티와 관련된 패키지 구조는 아래와 같다.

5. HomeController 개선하기

모든 설정이 끝났으니 처음 언급한 대로 반복되는 부분들을 모두 @LoginUser로 개선하자.

@Controller
@RequiredArgsConstructor
public class HomeController {

    private final PostService postService;

    // 메인 화면 - 게시판 목록
    @GetMapping("/")
    public String postList(Pageable pageable, Model model, @LoginUser SessionUser user) {
        Page<Post> posts = postService.findAllPosts(pageable);
        model.addAttribute("posts", posts);
        if (user != null) {
            model.addAttribute("userName", user.getName());
        }
        return "posts/list";
    }
}

이제 어느 컨트롤러든지 @LoginUser만 사용하면 세션 정보를 가져올 수 있다.


참고 도서
스프링 부트와 AWS로 혼자 구현하는 웹 서비스

profile
https://guswls28.tistory.com 💨💨

0개의 댓글