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

2024. 2. 14. 08:13개발 고민

 

SSR과 JWT에 대한 고찰


여기저기 글을 찾아보면 LocalStorage, Cookie를 이용해 JWT 관리하게 되는데 Spring Boot에서 JSP, thymeleaf 같이 SSR 기반 방식의 경우 JWT를 사용하는 경우는 거의 없습니다. SSR 경우 대부분 세션-쿠키를 이용해 stateful하게 상태관리를 해줍니다. 첫 번째는 LocalStorage 방식의 경우 SSR에서는 구현이 거의 불가능하고 두 번째 Cookie를 이용하는 방법 또한 세션-쿠키를 이용한 방법에 비해 코드의 보안, 복잡도, 생산성이 모두 떨어지기에 굳이 사용하려고 하지 않습니다. 하지만 Cookie를 이용한 방법으로 처리가 가능하고 복잡하긴 하지만 왜 JWT를 사용하지 않고 세션 방식을 사용할까에 대한 고민을 하게 되었습니다.

 

세션의 경우 사용자의 상태관리를 서버에서 하기에 해킹 예방과 사용자의 상태관리를 직접할 수 있다는 장점이 있습니다. 하지만 JWT Cookie 방식의 경우 Stateless하기 때문에 사용자의 상태관리를 직접적으로 제어하지 않습니다. 때문에 사용자 확장성면에서는 효과적이지만 (하나의 토큰으로 여러 시스템에 접속 가능) 보안상으로는 CSRF, XSS 인한 토큰 탈취 보안 문제에 대한 고려도 해줘야 됩니다. 

 

웹 클라이언트의 발전 과정

 

웹 클라이언트의 발전 과정을 살펴보며, SSR(Server-Side Rendering) 환경에서 주로 세션 방식을 사용하여 사용자 로그인을 관리하는 이유에 대한 고민을 정리해 보겠습니다. 이 고민은 웹 클라이언트의 기술적 발전 경로에서 해답을 찾을 수 있습니다.

 

웹 클라이언트의 기술적 발전

  1. Servlet - JSP 단계: 초기 웹 클라이언트 개발에서는 Servlet과 JSP(JavaServer Pages)가 주요 기술로 사용되었습니다. 이 단계에서는 사용자의 활동이 주로 웹 브라우저 내에서 이루어졌으며, 서버 측에서 페이지를 동적으로 생성하여 클라이언트에게 제공하는 방식이었습니다.
  2. MVC(Model-View-Controller) 패턴 도입: 시간이 지나면서, 웹 애플리케이션의 구조는 MVC 패턴을 통해 더욱 체계적으로 발전했습니다. MVC 패턴의 도입으로 프론트엔드(UI) 개발과 백엔드 개발 영역이 명확히 분리되었습니다.
  3. 모바일 환경의 등장과 영역 확장: 스마트폰과 같은 모바일 기기의 등장으로 사용자의 활동 범위는 웹에서 앱으로 크게 확장되었습니다. 이러한 변화는 사용자 인증 방식에도 영향을 미쳤습니다. 이제는 하나의 서버로 웹과 앱을 동시에 사용해야 되는 여기서 JWT가 등장하게 됩니다.

SSR 환경에서 세션 방식 사용의 이유

  • 기술적 호환성: SSR 기반 웹 애플리케이션은 주로 서버에서 페이지를 렌더링하고, 사용자 인증 정보를 서버의 세션에 저장하여 관리하는 방식을 사용했습니다. 이는 웹 브라우저와 밀접한 기술적 호환성을 가지고 있었습니다.
  • JWT의 등장 배경: 반면, 모바일 애플리케이션과 SPA(Single Page Application)의 등장은 사용자 인증 방식에도 변화를 요구했습니다. 세션 기반 인증은 상태를 서버에서 유지해야 하기 때문에, 분산 시스템이나 다양한 클라이언트 환경에서의 확장성과 유연성 측면에서 제한을 가질 수 있습니다. 이러한 문제를 해결하기 위해, JWT와 같은 토큰 기반 인증 방식이 등장하게 되었습니다.
  • SSR과 JWT의 사용 어려움: SSR 환경은 원래부터 JWT를 고려하여 설계된 것이 아니었기 때문에, 세션 방식이 자연스럽게 선호되었습니다. 또한, SSR 환경에서 JWT를 사용할 경우, 쿠키를 통한 전송 방식 외에는 클라이언트 측에서 JWT를 안전하게 관리하고 전송하는 데 제한이 있습니다.

 

결론적으로, 웹 클라이언트의 기술적 발전 과정과 사용자 활동의 환경 변화는 인증/인가 방식의 변화를 가져왔습니다. 초기에는 세션-쿠키 방식이 효과적이었으나, 모바일 환경과 다양한 클라이언트 애플리케이션의 등장으로 토큰 기반 인증 방식의 필요성이 증가했습니다. 그러나 SSR 환경에서는 여전히 세션 방식이 널리 사용되며, 이는 기술적 호환성과 보안, 관리의 용이성 때문입니다. 프로젝트의 프로그램이 단순히 웹에 사용되는 서비스라면 SSR - 세션 기반으로 프로젝트를 진행해도 무방하지만 모바일까지 확장을 고려한다면 CSR - API 기반의 방식으로 프로젝트를 구현하는 것이 좋습니다. 특정 기술을 사용할 때 이것을 왜 사용해야 되는지에 대한 생각을 해보는 것이 좋을 것 같습니다.

 

JWT 토큰을 SSR 방식에서 사용하기 어려운 이유

 

SSR 방식의 경우 Spring Boot 내에서 만들어진 Controller를 이용해 직접 direction을 걸어줘 연결해 서버에서 페이지 완성을 해 사용자에게 렌더링 하는 방식입니다. 굳이 사용해야된다면 페이지의 이동과 API 코드를 분리 구현을 해야 됩니다. 이 과정의 경우 코드의 복잡도가 늘어나게 됩니다. 또한 Spring Boot를 이용해 인증/인가 방식을 사용한다면 Spring Security를 이용하는데 분리 구현 방식을 이용하는 경우에는 Security 태그 기능을 이용한 권한(Role : ADMIN, USER)을 확인해 페이지 이동하는 부가적인 기능들을 이용할 수 없게 됩니다. 

 

정리 

SSR 방식에서 JWT 사용하려면 사용할 수 있습니다. 하지만 코드의 복잡성과 생산성의 문제로 JWT를 이용한 방식보다는 세션 방식이 더 선호됩니다.

 

아래의 코드는 SSR로 구현한 페이지에서 요청하는 데이터 정보와 페이지 이동을 동시해 진행하는 SSR 방식의 코드입니다. 상품의 정보를 호출하는 메서드로 페이지 요청과 데이터 요청 코드가 결합되어 있는 것을 확인 할 수 있습니다. JWT를 이용하기 위해서는 쿠키를 통해 인증 토큰을 전달하고 API 코드를 별도로 작성해 Header JWT 데이터를 받아오는 작업을 수행하여야 합니다.

@Controller
@RequiredArgsConstructor
@Slf4j
public class ProductController {
    @GetMapping("/product")
        public String getProductList(Model model, @PageableDefault(size = 9) Pageable pageable) {
            Page<ProductDto> productPage = productService.getProductList(pageable);
            model.addAttribute("products", productPage.getContent());
            model.addAttribute("page", productPage);
            return "productPages/product";
        }
}

 

https://zks145.tistory.com/104

 

[Spring] SSR, CSR HTTP API 이용한 렌더링 방식의 차이점

서비스를 제공하기 위한 방식의 고민 과정을 정리 SSR 방식의 리소스 제공 방식 CSR 방식의 리소스 제공 방식 어쩌면 CSR과 비슷한 HTTP API 제공 방식 렌더링은 서버에 요청한 데이터를 브라우저 화

zks145.tistory.com

 

쿠키를 이용한 자동 인증 구현

 

SSR 방식에서도 API를 별도 작성하지 않고도 JWT를 이용해 인증/인가를 처리하는 방법이 있습니다. 쿠키의 자동 전달 특성을 이용해 JWT를 저장해 자동으로 서버에 요청을 보낼 때 마다 JWT 데이터를 전달해 주는 방법입니다. 프로젝트를 진행하면서 JWT를 사용하기 위한 두 번째 방법으로 생각한 방법이지만 위에서 생각을 통해 효율적인 방식이 아니라 생각해 포기한 방법입니다.

 

모바일과의 확장성을 고려한다면 결국에는 API 코드를 별도로 작성해야됩니다.

 

로그인을 실시할 때 토큰을 쿠키를 통해 전달해 클라이언트에서 서버에 요청을 할 때마다 자동으로 인증을 받는 작업을 진행할 수 있습니다.

@PostMapping("/login")
    public ResponseEntity<?> login(@Valid @RequestBody UserLoginDto userLoginDto, BindingResult bindingResult, HttpServletResponse response) {

        if (bindingResult.hasErrors()) {
            Map<String, String> errors = new HashMap<>();
            for (FieldError error : bindingResult.getFieldErrors()) {
                errors.put(error.getField(), error.getDefaultMessage());
            }

            return ResponseEntity.badRequest().body(new ErrorResponse("로그인 실패", errors));
        }

        String token = userService.login(userLoginDto);

        // 쿠키 생성 및 응답에 추가
        Cookie jwtCookie = new Cookie("Authorization", token);

        jwtCookie.setHttpOnly(true);
        jwtCookie.setPath("/");
        jwtCookie.setMaxAge(1000 * 60 * 60);
        response.addCookie(jwtCookie);

        return ResponseEntity.ok(new ResponseDto("로그인 성공"));
    }

 

 

총정리

 

SSR 기반의 프로젝트도 원한다면 JWT를 이용한 인증 방식을 구현할 수 있다. 하지만 프로젝트 영역을 확장 하게 된다면  영역 별도의 엔드코드를 작성해 줘야 돼 불필요한 생산성 증가와 코드의 높은 복잡도를 유발하므로 사용을 권장하지 않는다. 때문에 프로젝트 환경 확장을 고려하고 있다면 서버에서 통합된 API를 이용해 처리하는 방식을 찾는게 옳다고 생각이 든다.