Spring Security의 SessionManagementConfigurer에서 사용하는 createPolicy는 세션 생성 정책을 정의합니다. 이는 애플리케이션이 인증 및 세션 관리를 어떻게 처리할지 설정할 때 유용합니다.
createPolicy는 기본적으로 다음과 같은 옵션을 제공합니다.
사용자가 요청을 보낼 때마다 항상 세션을 생성합니다.
새로운 인증이 발생하지 않더라도 세션이 존재하지 않으면 새 세션을 만듭니다.
예: 상태 유지를 위한 세션이 반드시 필요한 애플리케이션.
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
)
Spring Security가 세션을 절대 생성하지 않습니다.
기존 세션이 존재한다면 사용하지만, 새로운 세션은 생성하지 않습니다.
예: 세션 사용을 최소화하고 싶은 애플리케이션.
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.NEVER)
)
세션이 필요한 경우에만 생성합니다.
예: 인증 또는 권한 검사를 위해 세션이 필요할 때 생성.
대부분의 애플리케이션에서 기본 설정으로 적합합니다.
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
)
애플리케이션이 세션을 전혀 사용하지 않습니다.
인증 정보를 완전히 토큰 기반(예: JWT)으로 처리합니다.
예: RESTful API와 같은 상태 비저장(stateless) 애플리케이션.
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
세션을 전혀 생성하지 않으므로 서버의 리소스 소비가 줄어듭니다.
RESTful 서비스에서 권장됩니다.
무상태 인증 방식(JWT, OAuth2 등)을 구현해야 합니다.
기본값으로, 대부분의 일반 웹 애플리케이션에서 사용됩니다.
상태 기반 인증 방식을 사용할 때 적합합니다.
항상 세션을 생성하기 때문에 리소스 사용량이 증가할 수 있습니다.
특정 비즈니스 로직에서 상태를 지속적으로 유지해야 하는 경우에만 사용하세요.
기존 세션이 존재하지 않는 경우 오류를 방지하려면 신중하게 사용해야 합니다.
STATELESS를, 일반 웹 애플리케이션에서는 기본값인 IF_REQUIRED를 사용하는 것이 일반적입니다.상태 기반 인증은 서버가 클라이언트의 인증 상태를 세션으로 관리하는 방식입니다. 서버가 사용자의 인증 정보를 서버 측에 저장하고, 이후 요청 시 이를 참조하여 사용자를 인증합니다. 이는 전통적인 웹 애플리케이션에서 많이 사용되는 인증 방식입니다.
1. 로그인 요청:
클라이언트가 사용자 자격 증명(예: 사용자 ID와 비밀번호)을 서버에 전송합니다.
2. 인증 성공:
서버는 자격 증명이 유효하면, 사용자 정보를 기반으로 세션을 생성하고 고유한 세션 ID를 생성합니다.
이 세션 ID는 클라이언트의 쿠키에 저장되어 서버로 전송됩니다.
3. 요청 처리:
이후 클라이언트는 요청마다 쿠키에 포함된 세션 ID를 서버에 전송합니다.
서버는 세션 저장소에서 해당 세션 ID를 조회하여 사용자를 인증합니다.
4. 로그아웃:
사용자가 로그아웃하면 서버는 세션을 삭제하고 클라이언트의 쿠키에서 세션 ID를 제거합니다.
서버는 클라이언트의 인증 상태를 유지하기 위해 세션 저장소(메모리, 데이터베이스 등)를 사용합니다.
세션 데이터는 사용자 정보를 포함할 수 있으며, 이를 기반으로 사용자 권한 등을 관리합니다.
로그인 처리 (Spring Security 기반)
@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";
}
return "redirect:/login?error";
}
인증 처리
@GetMapping("/profile")
public String getProfile(HttpSession session) {
User user = (User) session.getAttribute("user"); // 세션에서 사용자 정보 가져오기
if (user != null) {
return "profile";
}
return "redirect:/login";
}
상태 기반 인증은 서버가 사용자 인증 상태를 관리하는 방식으로, 전통적인 웹 애플리케이션에서 많이 사용됩니다. 그러나 확장성과 RESTful 설계 원칙을 고려할 때, 무상태 인증(JWT 기반 인증 등)이 더 적합한 환경도 있습니다.
예제: JWT 기반 무상태 인증
1. JWT 생성 (로그인 시)
클라이언트가 서버에 로그인 요청을 보내면, 서버는 사용자 정보를 검증한 뒤 JWT를 생성해 반환합니다.
@RestController @RequestMapping("/auth") public class AuthController { @PostMapping("/login") public ResponseEntity<?> login(@RequestBody LoginRequest request) { // 사용자 인증 로직 (DB에서 사용자 확인) User user = userService.authenticate(request.getUsername(), request.getPassword()); if (user == null) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials"); } // JWT 생성 String token = jwtProvider.generateToken(user); // JWT 반환 return ResponseEntity.ok(new LoginResponse(token)); } }
2. JWT 생성 도구
JWT를 생성하고 검증하는 클래스를 작성합니다.
@Component
public class JwtProvider {
private final String secretKey = "your-secret-key";
private final long expirationMs = 3600000; // 1시간
public String generateToken(User user) {
return Jwts.builder()
.setSubject(user.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expirationMs))
.signWith(SignatureAlgorithm.HS256, secretKey)
.compact();
}
public boolean validateToken(String token) {
try {
Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
public String getUsernameFromToken(String token) {
return Jwts.parser()
.setSigningKey(secretKey)
.parseClaimsJws(token)
.getBody()
.getSubject();
}
}
3. 요청 검증 필터
클라이언트 요청에 포함된 JWT를 검증하고, 인증 정보를 SecurityContext에 저장합니다
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtProvider jwtProvider;
@Autowired
private UserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = getTokenFromRequest(request);
if (token != null && jwtProvider.validateToken(token)) {
String username = jwtProvider.getUsernameFromToken(token);
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
UsernamePasswordAuthenticationToken authentication =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
filterChain.doFilter(request, response);
}
private String getTokenFromRequest(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
4. Spring Security 설정
JwtAuthenticationFilter를 필터 체인에 추가합니다.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/**").permitAll() // 로그인, 회원가입 등 공개 API
.anyRequest().authenticated() // 그 외 요청은 인증 필요
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
}
5. 사용자 요청 처리
클라이언트는 로그인 후 서버로부터 받은 JWT를 Authorization 헤더에 포함시켜 요청을 보냅니다.
GET /api/private/data HTTP/1.1
Authorization: Bearer
6. API 컨트롤러
요청에서 JWT가 유효하면 SecurityContext에 저장된 사용자 정보에 접근할 수 있습니다.
@RestController
@RequestMapping("/api/private")
public class PrivateController {
<br>
@GetMapping("/data")
public ResponseEntity<String> getData(Authentication authentication) {
String username = authentication.getName();
return ResponseEntity.ok("Hello, " + username + "! This is a private API.");
}
}
클라이언트가 로그인하면 서버는 JWT를 발급합니다.
JWT는 클라이언트가 보관하며, 요청마다 Authorization 헤더에 포함시켜 서버로 전달됩니다.
서버는 JWT를 검증하여 인증 상태를 확인하고 요청을 처리합니다.
세션이나 상태를 서버에 저장하지 않으므로 확장성이 뛰어나며 RESTful 설계에 적합합니다.
장점