세션 관리(Session Management)는 어떻게 이루어지나요?

김상욱·2024년 12월 22일

세션 관리(Session Management)는 어떻게 이루어지나요?

세션 관리(Session Management)는 웹 애플리케이션에서 사용자와 서버 간의 상호작용 상태를 유지하고 관리하는 중요한 개념

What?

웹은 기본적으로 무상태(Stateless) 프로토콜인 HTTP를 사용합니다. 즉, 각 요청은 독립적이며 이전 요청의 상태를 기억하지 못합니다. 그러나 대부분의 웹 애플리케이션은 사용자 로그인 상태, 장바구니 정보 등과 같이 상태를 유지해야 하는 기능이 필요합니다. 이를 가능하게 하는 것이 세션 관리입니다.

  • 세션(Session) : 사용자가 웹 애플리케이션에 접속하여 로그인을 하고 특정 작업을 수행하는 동안의 상태 정보를 의미합니다.
  • 세션 식별자(Session ID) : 각 세션을 고유하게 식별하기 위한 고유한 값으로, 주로 쿠키를 통해 클라이언트에 저장됩니다.
  • 쿠키(Cookie) : 클라이언트(브라우저)에 데이터를 저장할 수 있는 작은 데이터 조각으로, 세션 ID를 저장하는 데 주로 사용됩니다.

Session Management in Java/spring

a. Spring MVC에서의 세션 관리

Spring MVC에서는 HttpSession 객체를 사용하여 세션을 관리합니다. 예를 들어, 사용자가 로그인할 때 세션에 사용자 정보를 저장할 수 있습니다.

@Controller
public class LoginController {

    @PostMapping("/login")
    public String login(@RequestParam String username, @RequestParam String password, HttpSession session) {
        // 사용자 인증 로직 (예: 데이터베이스 조회)
        User user = userService.authenticate(username, password);
        if (user != null) {
            session.setAttribute("USER", user);
            return "redirect:/home";
        } else {
            return "login";
        }
    }

    @GetMapping("/home")
    public String home(HttpSession session, Model model) {
        User user = (User) session.getAttribute("USER");
        if (user != null) {
            model.addAttribute("username", user.getUsername());
            return "home";
        } else {
            return "redirect:/login";
        }
    }
}
b. Spring Security에서의 세션 관리

Spring Security는 보다 강력하고 세부적인 세션 관리 기능을 제공합니다. 기본적인 인증과 인가 외에도 세션 고정 보호, 동시 세션 제어, 세션 만료 등의 기능을 지원합니다.

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 인증과 인가 설정
            .authorizeRequests()
                .antMatchers("/login", "/resources/**").permitAll()
                .anyRequest().authenticated()
                .and()
            // 로그인 설정
            .formLogin()
                .loginPage("/login")
                .defaultSuccessURL("/home")
                .permitAll()
                .and()
            // 로그아웃 설정
            .logout()
                .permitAll()
                .and()
            // 세션 관리 설정
            .sessionManagement()
                .maximumSessions(1) // 동시 세션 수 제한
                .expiredUrl("/login?expired");
    }
}

세션의 저장소

  • In-Memory : 간단하지만 서버가 재시작되면 세션 데이터가 사라집니다. 단일 서버 환경에서 주로 사용됩니다.
  • 데이터베이스(Database) : 영구적으로 세션 데이터를 저장할 수 있어 분산 환경에 적합합니다.
  • Redis와 같은 캐시 서버 : 빠른 접근 속도를 제공하며, 분산 환경에서도 유용합니다.
Spring Session과 Redis 설정 예제
<!-- pom.xml에 의존성 추가 -->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
    // Redis 설정 (기본값 사용 시 별도 설정 필요 없음)
}

Caution

  • 보안(Security) : 세션 하이재킹, 세션 고정 공격 등을 방지하기 위해 HTTPS 사용, 세션 타임아웃 설정, 세션 ID의 복잡성 증가 등이 필요.
  • 스케일링(Sacaling) : 분산 환경에서 세션을 공유할 수 있도록 외부 저장소를 사용하는 것이 좋음.
  • 성능(Performance) : 세션 저장소의 선택은 애플리케이션의 성능에 영향을 미칠 수 있으므로 적절한 선택이 필요합니다.

취업 준비 중인 신입 Java 및 Spring 백엔드 개발자라면 이론적인 지식뿐만 아니라 실무에서 활용할 수 있는 실습 경험이 매우 중요합니다. 아래에 제시된 실습 과제들은 세션 관리뿐만 아니라 Spring 프레임워크의 다양한 기능을 익히는 데 도움이 될 것입니다. 각 과제는 단계별로 진행할 수 있도록 설계되었으며, 실제 프로젝트에 적용할 수 있는 기술들을 포함하고 있습니다.

1. 간단한 사용자 인증 시스템 구축

목표: 사용자 로그인 및 로그아웃 기능 구현을 통해 기본적인 세션 관리 이해

주요 기능:

  • 사용자 등록 및 로그인
  • HttpSession을 사용하여 로그인 상태 유지
  • 로그아웃 기능 구현
  • 세션 타임아웃 설정

실습 단계:
1. 프로젝트 설정:

  • Spring Boot 프로젝트 생성 (Spring Initializr 사용 권장)
  • 필요한 의존성 추가: Spring Web, Spring Data JPA, Thymeleaf, H2 Database
  1. 엔티티 및 레포지토리 생성:

    @Entity
    public class User {
        @Id @GeneratedValue
        private Long id;
        private String username;
        private String password;
        // getters and setters
    }
    
    public interface UserRepository extends JpaRepository<User, Long> {
        User findByUsername(String username);
    }
  2. 컨트롤러 작성:

    @Controller
    public class AuthController {
        @Autowired
        private UserRepository userRepository;
    
        @GetMapping("/register")
        public String showRegistrationForm() {
            return "register";
        }
    
        @PostMapping("/register")
        public String register(User user) {
            user.setPassword(new BCryptPasswordEncoder().encode(user.getPassword()));
            userRepository.save(user);
            return "redirect:/login";
        }
    
        @GetMapping("/login")
        public String showLoginForm() {
            return "login";
        }
    
        @PostMapping("/login")
        public String login(@RequestParam String username, @RequestParam String password, HttpSession session) {
            User user = userRepository.findByUsername(username);
            if (user != null && new BCryptPasswordEncoder().matches(password, user.getPassword())) {
                session.setAttribute("USER", user);
                return "redirect:/home";
            }
            return "login";
        }
    
        @GetMapping("/logout")
        public String logout(HttpSession session) {
            session.invalidate();
            return "redirect:/login";
        }
    
        @GetMapping("/home")
        public String home(HttpSession session, Model model) {
            User user = (User) session.getAttribute("USER");
            if (user != null) {
                model.addAttribute("username", user.getUsername());
                return "home";
            }
            return "redirect:/login";
        }
    }
  3. 뷰 템플릿 작성:

    • register.html, login.html, home.html 등 간단한 Thymeleaf 템플릿 작성
  4. 세션 타임아웃 설정:

    # application.properties
    server.servlet.session.timeout=15m

학습 포인트:

  • HttpSession을 통한 세션 관리
  • Spring MVC의 요청 처리 흐름 이해
  • 비밀번호 암호화 및 보안 기초

2. Spring Security를 활용한 보안 강화

목표: Spring Security를 사용하여 인증 및 인가를 구현하고, 세션 관리 기능을 심화 학습

주요 기능:

  • Spring Security 설정
  • 사용자 역할(Role) 기반 접근 제어
  • 세션 고정 보호, 동시 세션 제어 설정

실습 단계:
1. 의존성 추가:

<!-- pom.xml -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
  1. Spring Security 설정 클래스 작성:

    @Configuration
    @EnableWebSecurity
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private UserDetailsService userDetailsService;
    
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                .authorizeRequests()
                    .antMatchers("/register", "/login").permitAll()
                    .antMatchers("/admin/**").hasRole("ADMIN")
                    .anyRequest().authenticated()
                    .and()
                .formLogin()
                    .loginPage("/login")
                    .defaultSuccessUrl("/home")
                    .permitAll()
                    .and()
                .logout()
                    .permitAll()
                    .and()
                .sessionManagement()
                    .maximumSessions(1)
                    .expiredUrl("/login?expired");
        }
    }
  2. UserDetailsService 구현:

    @Service
    public class CustomUserDetailsService implements UserDetailsService {
        @Autowired
        private UserRepository userRepository;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userRepository.findByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException("User not found");
            }
            return new org.springframework.security.core.userdetails.User(
                user.getUsername(),
                user.getPassword(),
                Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"))
            );
        }
    }
  3. 추가 기능 구현:

    • 역할(Role) 추가: User 엔티티에 역할 필드 추가 및 관리자 역할 구현
    • 동시 세션 제어: 한 사용자가 동시에 여러 세션을 가질 수 없도록 설정
    • 세션 고정 보호: 세션 ID 변경을 통해 세션 고정 공격 방지

학습 포인트:

  • Spring Security의 인증 및 인가 메커니즘
  • 사용자 역할 기반 접근 제어
  • 고급 세션 관리 설정

3. 쇼핑 카트 애플리케이션 구축

목표: 세션을 활용하여 사용자별 쇼핑 카트 기능 구현

주요 기능:

  • 상품 목록 조회
  • 장바구니에 상품 추가/삭제
  • 세션을 통한 장바구니 상태 유지

실습 단계:
1. 프로젝트 설정:

  • Spring Boot 프로젝트 생성
  • 의존성 추가: Spring Web, Thymeleaf, Spring Data JPA, H2 Database
  1. 엔티티 및 레포지토리 생성:

    @Entity
    public class Product {
        @Id @GeneratedValue
        private Long id;
        private String name;
        private Double price;
        // getters and setters
    }
    
    public interface ProductRepository extends JpaRepository<Product, Long> {}
  2. 컨트롤러 작성:

    @Controller
    public class CartController {
        @Autowired
        private ProductRepository productRepository;
    
        @GetMapping("/products")
        public String listProducts(Model model) {
            model.addAttribute("products", productRepository.findAll());
            return "products";
        }
    
        @PostMapping("/cart/add")
        public String addToCart(@RequestParam Long productId, HttpSession session) {
            List<Product> cart = (List<Product>) session.getAttribute("CART");
            if (cart == null) {
                cart = new ArrayList<>();
            }
            Product product = productRepository.findById(productId).orElse(null);
            if (product != null) {
                cart.add(product);
                session.setAttribute("CART", cart);
            }
            return "redirect:/products";
        }
    
        @GetMapping("/cart")
        public String viewCart(HttpSession session, Model model) {
            List<Product> cart = (List<Product>) session.getAttribute("CART");
            if (cart == null) {
                cart = new ArrayList<>();
            }
            model.addAttribute("cart", cart);
            return "cart";
        }
    
        @PostMapping("/cart/remove")
        public String removeFromCart(@RequestParam Long productId, HttpSession session) {
            List<Product> cart = (List<Product>) session.getAttribute("CART");
            if (cart != null) {
                cart.removeIf(p -> p.getId().equals(productId));
                session.setAttribute("CART", cart);
            }
            return "redirect:/cart";
        }
    }
  3. 뷰 템플릿 작성:

    • products.html, cart.html
  4. 추가 기능 (선택 사항):

    • 세션 만료 시 장바구니 초기화
    • 장바구니 아이템 수량 관리
    • 주문 기능 구현

학습 포인트:

  • 세션을 활용한 상태 관리
  • 데이터 모델링 및 관계 설정
  • 사용자 경험 향상을 위한 세션 활용

4. Spring Session과 Redis를 활용한 분산 세션 관리

목표: Spring Session과 Redis를 사용하여 분산 환경에서 세션을 관리하는 방법 학습

주요 기능:

  • Redis를 세션 저장소로 설정
  • 다중 서버 환경에서 세션 공유
  • 세션 지속성 및 확장성 확보

실습 단계:
1. Redis 설치 및 실행:

  • 로컬에 Redis 서버 설치 또는 Docker 사용
    docker run -d -p 6379:6379 redis
  1. 의존성 추가:

    <!-- pom.xml -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>redis.clients</groupId>
        <artifactId>jedis</artifactId>
    </dependency>
  2. Spring Session 설정:

    @Configuration
    @EnableRedisHttpSession
    public class SessionConfig {
        // 기본 설정 사용, application.properties에 Redis 설정 추가 가능
    }
  3. application.properties 설정:

    spring.redis.host=localhost
    spring.redis.port=6379
    server.servlet.session.timeout=30m
  4. 동시 테스트:

    • 두 개 이상의 애플리케이션 인스턴스를 실행하여 동일한 Redis 서버를 세션 저장소로 사용
    • 한 인스턴스에서 로그인하면 다른 인스턴스에서도 세션이 공유되는지 확인

학습 포인트:

  • 분산 시스템에서의 세션 관리
  • 외부 저장소(Redis)를 활용한 세션 유지
  • Spring Session의 설정 및 활용 방법

5. 토큰 기반 인증 (JWT)과 세션 비교 실습

목표: 세션 기반 인증과 토큰 기반 인증(JWT)을 비교하고, 각각의 장단점을 이해

주요 기능:

  • 세션 기반 인증 구현
  • JWT 기반 인증 구현
  • 두 방식의 보안 및 성능 비교

실습 단계:
1. 세션 기반 인증 구현:

  • 이전 실습 1번과 유사하게 HttpSession을 사용하여 로그인 상태 관리
  1. JWT 기반 인증 구현:

    • 의존성 추가:

      <dependency>
          <groupId>io.jsonwebtoken</groupId>
          <artifactId>jjwt</artifactId>
          <version>0.9.1</version>
      </dependency>
    • JWT 생성 및 검증 서비스 작성:

      @Service
      public class JwtService {
          private String secretKey = "yourSecretKey";
      
          public String generateToken(User user) {
              return Jwts.builder()
                  .setSubject(user.getUsername())
                  .setExpiration(new Date(System.currentTimeMillis() + 86400000)) // 1일
                  .signWith(SignatureAlgorithm.HS512, secretKey)
                  .compact();
          }
      
          public String validateToken(String token) {
              try {
                  return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();
              } catch (Exception e) {
                  return null;
              }
          }
      }
    • 컨트롤러 수정:

      @RestController
      public class AuthController {
          @Autowired
          private JwtService jwtService;
          @Autowired
          private UserRepository userRepository;
      
          @PostMapping("/api/login")
          public ResponseEntity<?> login(@RequestBody LoginRequest request) {
              User user = userRepository.findByUsername(request.getUsername());
              if (user != null && new BCryptPasswordEncoder().matches(request.getPassword(), user.getPassword())) {
                  String token = jwtService.generateToken(user);
                  return ResponseEntity.ok(new JwtResponse(token));
              }
              return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
          }
      
          @GetMapping("/api/protected")
          public ResponseEntity<?> protectedEndpoint(@RequestHeader("Authorization") String token) {
              String username = jwtService.validateToken(token.substring(7));
              if (username != null) {
                  return ResponseEntity.ok("Hello, " + username);
              }
              return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
          }
      }
  2. 테스트 및 비교:

    • Postman 등을 사용하여 세션 기반과 JWT 기반 인증 방식 테스트
    • 각 방식의 장단점 분석

학습 포인트:

  • 세션 관리 vs. 토큰 기반 인증의 차이점
  • JWT의 구조와 활용 방법
  • 보안 고려사항 및 선택 기준

6. 사용자 활동 추적 및 세션 데이터 분석

목표: 세션 데이터를 활용하여 사용자 활동을 추적하고 분석하는 기능 구현

주요 기능:

  • 사용자 활동 로그 저장
  • 세션 데이터를 기반으로 통계 정보 제공
  • 관리자 페이지에서 활동 분석

실습 단계:
1. 엔티티 및 레포지토리 생성:

@Entity
public class UserActivity {
    @Id @GeneratedValue
    private Long id;
    private String username;
    private String activity;
    private LocalDateTime timestamp;
    // getters and setters
}

public interface UserActivityRepository extends JpaRepository<UserActivity, Long> {}
  1. 필터 또는 인터셉터 구현:

    @Component
    public class UserActivityInterceptor implements HandlerInterceptor {
        @Autowired
        private UserActivityRepository activityRepository;
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            HttpSession session = request.getSession(false);
            if (session != null) {
                User user = (User) session.getAttribute("USER");
                if (user != null) {
                    UserActivity activity = new UserActivity();
                    activity.setUsername(user.getUsername());
                    activity.setActivity(request.getRequestURI());
                    activity.setTimestamp(LocalDateTime.now());
                    activityRepository.save(activity);
                }
            }
            return true;
        }
    }
    
    @Configuration
    public class WebConfig implements WebMvcConfigurer {
        @Autowired
        private UserActivityInterceptor userActivityInterceptor;
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(userActivityInterceptor);
        }
    }
  2. 관리자 페이지 구현:

    @Controller
    public class AdminController {
        @Autowired
        private UserActivityRepository activityRepository;
    
        @GetMapping("/admin/activities")
        public String viewActivities(Model model) {
            List<UserActivity> activities = activityRepository.findAll();
            model.addAttribute("activities", activities);
            return "activities";
        }
    }
  3. 뷰 템플릿 작성:

    • activities.html

학습 포인트:

  • 세션 데이터를 활용한 사용자 활동 추적
  • Spring 인터셉터를 통한 공통 기능 구현
  • 데이터 분석 및 시각화 기초

추가 팁 및 리소스

  1. Git을 활용한 버전 관리:

    • 각 실습 과제를 Git 레포지토리로 관리하여 버전 관리 및 변경 사항 추적 연습
  2. 테스트 작성:

    • JUnit과 Mockito를 사용하여 유닛 테스트 및 통합 테스트 작성
    • 테스트 주도 개발(TDD) 연습
  3. 문서화:

    • 프로젝트마다 README 파일 작성
    • API 문서화 도구 (Swagger) 사용
  4. 배포 경험:

    • 로컬 환경 외에도 Heroku, AWS, DigitalOcean 등을 활용하여 애플리케이션 배포 경험 쌓기
  5. 참고 자료:

결론

위에서 제시한 실습 과제들은 Java와 Spring 프레임워크를 활용한 백엔드 개발 역량을 키우는 데 큰 도움이 될 것입니다. 각 과제를 수행하면서 세션 관리의 다양한 측면을 경험하고, 보안, 성능, 확장성 등의 중요한 개념을 자연스럽게 습득할 수 있습니다. 또한, 실제 프로젝트와 유사한 환경에서 작업함으로써 취업 준비에 필요한 실무 경험을 쌓을 수 있습니다. 꾸준히 실습을 진행하며 이해도를 높이고, 문제 해결 능력을 키워나가시길 바랍니다. 화이팅입니다!

0개의 댓글