세션 관리(Session Management)는 웹 애플리케이션에서 사용자와 서버 간의 상호작용 상태를 유지하고 관리하는 중요한 개념
웹은 기본적으로 무상태(Stateless) 프로토콜인 HTTP를 사용합니다. 즉, 각 요청은 독립적이며 이전 요청의 상태를 기억하지 못합니다. 그러나 대부분의 웹 애플리케이션은 사용자 로그인 상태, 장바구니 정보 등과 같이 상태를 유지해야 하는 기능이 필요합니다. 이를 가능하게 하는 것이 세션 관리입니다.
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";
}
}
}
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");
}
}
<!-- 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 설정 (기본값 사용 시 별도 설정 필요 없음)
}
취업 준비 중인 신입 Java 및 Spring 백엔드 개발자라면 이론적인 지식뿐만 아니라 실무에서 활용할 수 있는 실습 경험이 매우 중요합니다. 아래에 제시된 실습 과제들은 세션 관리뿐만 아니라 Spring 프레임워크의 다양한 기능을 익히는 데 도움이 될 것입니다. 각 과제는 단계별로 진행할 수 있도록 설계되었으며, 실제 프로젝트에 적용할 수 있는 기술들을 포함하고 있습니다.
목표: 사용자 로그인 및 로그아웃 기능 구현을 통해 기본적인 세션 관리 이해
주요 기능:
HttpSession을 사용하여 로그인 상태 유지실습 단계:
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);
}
컨트롤러 작성:
@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";
}
}
뷰 템플릿 작성:
register.html, login.html, home.html 등 간단한 Thymeleaf 템플릿 작성세션 타임아웃 설정:
# application.properties
server.servlet.session.timeout=15m
학습 포인트:
HttpSession을 통한 세션 관리목표: Spring Security를 사용하여 인증 및 인가를 구현하고, 세션 관리 기능을 심화 학습
주요 기능:
실습 단계:
1. 의존성 추가:
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
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");
}
}
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"))
);
}
}
추가 기능 구현:
학습 포인트:
목표: 세션을 활용하여 사용자별 쇼핑 카트 기능 구현
주요 기능:
실습 단계:
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> {}
컨트롤러 작성:
@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";
}
}
뷰 템플릿 작성:
products.html, cart.html 등추가 기능 (선택 사항):
학습 포인트:
목표: Spring Session과 Redis를 사용하여 분산 환경에서 세션을 관리하는 방법 학습
주요 기능:
실습 단계:
1. Redis 설치 및 실행:
docker run -d -p 6379:6379 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>
Spring Session 설정:
@Configuration
@EnableRedisHttpSession
public class SessionConfig {
// 기본 설정 사용, application.properties에 Redis 설정 추가 가능
}
application.properties 설정:
spring.redis.host=localhost
spring.redis.port=6379
server.servlet.session.timeout=30m
동시 테스트:
학습 포인트:
목표: 세션 기반 인증과 토큰 기반 인증(JWT)을 비교하고, 각각의 장단점을 이해
주요 기능:
실습 단계:
1. 세션 기반 인증 구현:
HttpSession을 사용하여 로그인 상태 관리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();
}
}
테스트 및 비교:
학습 포인트:
목표: 세션 데이터를 활용하여 사용자 활동을 추적하고 분석하는 기능 구현
주요 기능:
실습 단계:
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> {}
필터 또는 인터셉터 구현:
@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);
}
}
관리자 페이지 구현:
@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";
}
}
뷰 템플릿 작성:
activities.html 등학습 포인트:
Git을 활용한 버전 관리:
테스트 작성:
문서화:
배포 경험:
참고 자료:
위에서 제시한 실습 과제들은 Java와 Spring 프레임워크를 활용한 백엔드 개발 역량을 키우는 데 큰 도움이 될 것입니다. 각 과제를 수행하면서 세션 관리의 다양한 측면을 경험하고, 보안, 성능, 확장성 등의 중요한 개념을 자연스럽게 습득할 수 있습니다. 또한, 실제 프로젝트와 유사한 환경에서 작업함으로써 취업 준비에 필요한 실무 경험을 쌓을 수 있습니다. 꾸준히 실습을 진행하며 이해도를 높이고, 문제 해결 능력을 키워나가시길 바랍니다. 화이팅입니다!