Network

SSR에 JWT 적용이 부적합한 이유

dev-rootable 2024. 6. 8. 17:57

세션 방식으로 구현했던 SSR 서버에 JWT를 적용하면서 느낀 점을 정리하고자 한다.

 

💥 문제점

 

🚨 Stateless 하지 않다.

 

SSR 서버는 매 요청마다 비즈니스 로직을 수행하고 컨트롤러를 통해 렌더링 한다. 이것은 매 요청마다 인증 토큰을 생성한다고 해석할 수 있다. Stateless 특징을 가지는 JWT를 사용할 때 서버의 발급은 1회고, 그 이후에는 검증만 수행한다. 결과적으로 Stateless 하지 않다고 볼 수 있다.

 

JWT 방식은 클라이언트 스토리지에 저장되기 때문에 쿠키를 통해 구현할 수 있다. 아래 코드는 세션 방식과 쿠키를 통해 JWT를 구현한 코드이다.

 

    @PostMapping("/login")
    public String login(@Valid @ModelAttribute("loginForm") LoginDto dto, BindingResult bindingResult,
                        @RequestParam(defaultValue = "/") String redirectURL) {

        log.info("로그인 검증");

        if (bindingResult.hasErrors()) {
            log.info("검증 에러 errors={}", bindingResult);
            return "login/signIn";
        }

        //로그인 유효성 검사
        Member loginMember = loginService.validationLogin(dto.getLoginId(), dto.getPassword());
        log.info("Login Member = {}", loginMember);

        if (loginMember == null) {
            bindingResult.reject("loginFail");
            return "login/signIn";
        }

        //로그인 성공 처리
        log.info("정상 입력으로 로그인 성공");

        httpSession.setAttribute("loginMember", new SessionMember(loginMember)); //세션에 회원 정보 저장


        return "redirect:" + redirectURL; //로그인 후 최초 위치로 돌아가도록

    }

 

위 코드는 세션 방식으로 로그인을 처리하는 코드다.

 

인증된 사용자라면 세션에 정보를 저장하고, 그렇지 않다면 검증 에러를 객체에 담아 적절한 뷰를 내려주는 식으로 동작한다.

 

@Slf4j
@Controller
@RequiredArgsConstructor
public class LoginController {
    
    private final TokenProvider tokenProvider;

    ...

    @PostMapping("/login")
    public String login(@Valid @ModelAttribute("loginForm") LoginDto dto, BindingResult bindingResult,
                        @RequestParam(defaultValue = "/") String redirectURL, HttpServletResponse res) {

        log.info("로그인 검증");

        if (bindingResult.hasErrors()) {
            log.info("검증 에러 errors={}", bindingResult);
            return "login/signIn";
        }

        //로그인 유효성 검사
        Member loginMember = loginService.validationLogin(dto.getLoginId(), dto.getPassword());
        log.info("Login Member = {}", loginMember);

        if (loginMember == null) {
            bindingResult.reject("loginFail");
            return "login/signIn";
        }

        //로그인 성공 처리
        log.info("정상 입력으로 로그인 성공");

        Map<String, Object> member = new HashMap<>();
        member.put("loginId", dto.loginId);
        member.put("password", dto.password);

        //10분 후 만료되는 jwt 생성하여 쿠키에 저장
        Cookie cookie = new Cookie(JwtLoginInterceptor.COOKIE_NAME, 
                             tokenProvider.createToken(member, Optional.of(LocalDateTime.now().plusMinutes(10)))
                        );

        cookie.setPath("/");
        cookie.setMaxAge(Integer.MAX_VALUE);
        
        res.addCookie(cookie);

        return "redirect:" + redirectURL; //로그인 후 최초 위치로 돌아가도록

    }
    
}

 

 

위 코드를 서비스에서 처리하거나 Spring Security 설정 클래스에서 해당 로직을 처리하는 커스텀 Filter를 등록하여 처리할 수도 있다. 어떤 방식을 사용하든 Client의 요청마다 해당 로직이 동작한다. 이것은 추가적인 연산 오버헤드를 발생시켜 성능 저하를 초래할 수 있다.

 

🚨 JWT의 보안 취약점

 

JWT 방식은 다음과 같은 이유로 보안 취약점이 뚜렷하다.

 

  • 저장소가 Client 측에 있어 XSS(Cross Site Scripting) 공격에 취약하다.
  • Payload가 Base64로 인코딩되었을 뿐, 암호화된 것이 아니다.
  • 암호화 알고리즘에 취약점이 있을 수 있다.
  • 발급한 JWT는 서버에서 통제할 방법이 없다.

 

🚨 SSR 특성과의 부적합

 

JWT는 Client 측에서 관리하기 때문에 Javascript로 처리해야 한다. SSR 환경에서는 Javascript를 JQuery를 통해 HTML에 삽입하거나 Ajax를 통해 비동기 통신으로 처리해야 하므로 Javascript를 사용하기 불편하다는 단점이 있다.

 

References:

 

https://zks145.tistory.com/106

 

[Spring] SSR에서 JWT를 이용한 인증/인가 처리 고민

SSR과 JWT에 대한 고찰여기저기 글을 찾아보면 LocalStorage, Cookie를 이용해 JWT 관리하게 되는데 Spring Boot에서 JSP, thymeleaf 같이 SSR 기반 방식의 경우 JWT를 사용하는 경우는 거의 없습니다. SSR 경우 대

zks145.tistory.com

 

https://okky.kr/questions/1488896

 

OKKY - Spring boot SSR에서는 JWT를 지양하는 이유?

SSR 기반으로 클라이언트를 구성하던 도중에 JWT이용해 인증/인가 처리를 하려고 했습니다. 그런데 해당 방식으로 구현한 소스를 찾아보는데 SSR에서는 JWT를 이용하는 방식보다는 세션을 이용해

okky.kr

 

https://stove99.github.io/java/2019/08/02/springboot-with-jwt/

 

JAVA : SpringBoot2 & Thymeleaf 에서 JWT 이용해서 로그인 하기

SpringBoot2 & Thymeleaf 로 구성된 전통적인(?) 환경에서 JWT 를 이용한 로그인 처리 샘플

stove99.github.io