아래는 "스프링 시큐리티" 책의 6장에 포함될 "커스텀 로그인 페이지 제작", "로그아웃 기능 구현", 그리고 "세션 관리와 타임아웃 설정"에 대한 내용입니다. 실습 가능한 예제와 함께 실무에서 유용한 설정을 설명했습니다. --- ### 6장. 로그인/로그아웃 커스터마이징 #### 6.1 커스텀 로그인 페이지 제작 스프링 시큐리티의 기본 로그인 페이지는 간단한 테스트에는 유용하지만, 실무에서는 디자인과 기능을 사용자 맞춤으로 변경해야 합니다. **커스텀 로그인 페이지**를 제작하면 애플리케이션의 UI/UX를 개선하고, 추가적인 인증 로직을 통합할 수 있습니다. ##### 커스텀 로그인 페이지 설정 1. **HTML 페이지 제작**: 먼저, 로그인 폼을 포함한 커스텀 페이지를 만듭니다. 예를 들어, `src/main/resources/templates/login.html`: ```html
아이디 또는 비밀번호가 잘못되었습니다.
``` - `action="/login"`: 스프링 시큐리티의 기본 인증 엔드포인트. - `name="username"`, `name="password"`: 기본 필드 이름(변경 가능). - `${param.error}`: Thymeleaf를 사용해 로그인 실패 시 오류 메시지 표시. 2. **SecurityConfig 설정**: ```java import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.web.SecurityFilterChain; @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .requestMatchers("/login").permitAll() .anyRequest().authenticated() ) .formLogin(form -> form .loginPage("/login") // 커스텀 로그인 페이지 경로 .defaultSuccessUrl("/home") // 로그인 성공 시 리다이렉트 .permitAll() // 로그인 페이지 접근 허용 ); return http.build(); } } ``` - `loginPage("/login")`: 기본 로그인 대신 커스텀 페이지를 사용. - `defaultSuccessUrl("/home")`: 인증 성공 후 이동할 경로. - `permitAll()`: 인증되지 않은 사용자도 로그인 페이지에 접근 가능. 3. **컨트롤러 추가**: ```java import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; @Controller public class LoginController { @GetMapping("/login") public String login() { return "login"; // login.html 반환 } } ``` ##### 동작 원리 - 사용자가 보호된 리소스에 접근하면 `/login`으로 리다이렉트됩니다. - 커스텀 폼에서 입력된 `username`과 `password`는 POST 요청으로 `/login`에 전달됩니다. - `UsernamePasswordAuthenticationFilter`가 이를 처리해 인증을 수행합니다. ##### 추가 커스터마이징 - **실패 처리**: `.failureUrl("/login?error")`를 추가해 실패 시 오류 메시지를 전달. - **필드 이름 변경**: `.usernameParameter("id")`, `.passwordParameter("pass")`로 기본 이름 변경 가능. #### 6.3 로그아웃 기능 구현 로그아웃은 사용자의 세션을 종료하고 인증 상태를 해제하는 기능입니다. 스프링 시큐리티는 기본 로그아웃 설정을 제공하지만, 커스터마이징으로 사용자 경험을 개선할 수 있습니다. ##### 기본 로그아웃 설정 ```java @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .formLogin(form -> form.loginPage("/login").permitAll()) .logout(logout -> logout .logoutUrl("/logout") // 로그아웃 요청 URL .logoutSuccessUrl("/login?logout") // 로그아웃 성공 시 리다이렉트 .permitAll() // 로그아웃 접근 허용 ); return http.build(); } } ``` - `logoutUrl("/logout")`: 기본 로그아웃 엔드포인트(POST 요청 필요). - `logoutSuccessUrl("/login?logout")`: 로그아웃 후 이동할 경로. ##### 로그아웃 버튼 추가 `login.html`에 로그아웃 버튼을 추가하려면: ```html ``` - POST 요청이어야 하며, CSRF 토큰이 필요합니다(기본적으로 활성화). ##### CSRF 비활성화 시 주의 CSRF 보호를 비활성화하면 GET 요청으로도 로그아웃 가능: ```java http .csrf(csrf -> csrf.disable()) .logout(logout -> logout.logoutUrl("/logout")); ``` 이 경우 `로그아웃`로 간단히 구현 가능하지만, 보안성이 낮아지므로 주의하세요. ##### 커스터마이징 - **핸들러 추가**: `.addLogoutHandler()`로 커스텀 로그아웃 로직(예: 로그 기록) 추가. - **성공 핸들러**: `.logoutSuccessHandler()`로 리다이렉트 대신 JSON 응답 반환 가능. #### 6.4 세션 관리와 타임아웃 설정 세션 관리는 사용자의 인증 상태를 유지하고, 세션 타임아웃을 통해 보안을 강화하는 데 중요합니다. 스프링 시큐리티는 세션 설정을 세밀하게 조정할 수 있는 옵션을 제공합니다. ##### 기본 세션 관리 스프링 시큐리티는 인증 성공 시 `SecurityContext`를 세션에 저장합니다. `SecurityContextPersistenceFilter`가 이를 관리하며, 기본적으로 서블릿 컨테이너의 세션 설정을 따릅니다. ##### 세션 설정 예제 ```java @Configuration public class SecurityConfig { @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth.anyRequest().authenticated()) .formLogin(form -> form.loginPage("/login").permitAll()) .logout(logout -> logout.logoutUrl("/logout").logoutSuccessUrl("/login?logout")) .sessionManagement(session -> session .maximumSessions(1) // 최대 세션 수 제한 (1명만 로그인 가능) .maxSessionsPreventsLogin(true) // 중복 로그인 차단 .expiredUrl("/login?expired") // 세션 만료 시 리다이렉트 ); return http.build(); } } ``` - `maximumSessions(1)`: 한 사용자가 동시에 여러 세션을 가질 수 없도록 제한. - `maxSessionsPreventsLogin(true)`: 새 로그인 시 기존 세션을 무효화 대신 차단. - `expiredUrl("/login?expired")`: 세션 만료 후 이동 경로. ##### 타임아웃 설정 세션 타임아웃은 서블릿 컨테이너 수준에서 설정하거나, 애플리케이션에서 조정 가능: 1. **application.properties**: ```properties server.servlet.session.timeout=1800 # 30분 (초 단위) ``` 2. **코드로 설정**: ```java import org.springframework.context.annotation.Bean; import org.springframework.session.web.http.SessionRepositoryFilter; import org.springframework.session.data.redis.RedisIndexedSessionRepository; @Configuration public class SessionConfig { @Bean public SessionRepositoryFilter> sessionRepositoryFilter(RedisIndexedSessionRepository sessionRepository) { sessionRepository.setDefaultMaxInactiveInterval(1800); // 30분 return new SessionRepositoryFilter<>(sessionRepository); } } ``` Redis와 같은 외부 저장소를 사용하면 세션 지속성을 높일 수 있습니다. ##### 세션 관리 추가 옵션 - **세션 고정 보호**: `.sessionFixation().migrateSession()`으로 세션 고정 공격 방지. - **무효화**: `.invalidSessionUrl("/login?invalid")`로 무효 세션 처리. - **동시 세션 모니터링**: `HttpSessionEventPublisher`를 추가해 세션 생성/소멸 이벤트 감지. ##### 실습 예제 1. 커스텀 로그인 페이지를 만들고, 로그인 후 `/home`으로 이동. 2. 로그아웃 버튼을 추가해 `/login?logout`으로 리다이렉트 확인. 3. 세션 타임아웃을 1분으로 설정하고, 만료 후 동작 테스트. --- 위 내용은 커스텀 로그인/로그아웃 구현과 세션 관리 방법을 실습 가능하도록 설명했습니다. 추가적인 예제나 설정이 필요하면 말씀해 주세요!