Files
spring-boot-examples/docs/security/06_커스텀 로그인 페이지.md
2025-04-08 19:56:24 +09:00

8.9 KiB

아래는 "스프링 시큐리티" 책의 6장에 포함될 "커스텀 로그인 페이지 제작", "로그아웃 기능 구현", 그리고 "세션 관리와 타임아웃 설정"에 대한 내용입니다. 실습 가능한 예제와 함께 실무에서 유용한 설정을 설명했습니다.


6장. 로그인/로그아웃 커스터마이징

6.1 커스텀 로그인 페이지 제작

스프링 시큐리티의 기본 로그인 페이지는 간단한 테스트에는 유용하지만, 실무에서는 디자인과 기능을 사용자 맞춤으로 변경해야 합니다. 커스텀 로그인 페이지를 제작하면 애플리케이션의 UI/UX를 개선하고, 추가적인 인증 로직을 통합할 수 있습니다.

커스텀 로그인 페이지 설정
  1. HTML 페이지 제작: 먼저, 로그인 폼을 포함한 커스텀 페이지를 만듭니다. 예를 들어, src/main/resources/templates/login.html:
<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h2>로그인</h2>
    <form method="post" action="/login">
        <div>
            <label>아이디</label>
            <input type="text" name="username" required>
        </div>
        <div>
            <label>비밀번호</label>
            <input type="password" name="password" required>
        </div>
        <button type="submit">로그인</button>
    </form>
    <p th:if="${param.error}" style="color:red">아이디 또는 비밀번호가 잘못되었습니다.</p>
</body>
</html>
  • action="/login": 스프링 시큐리티의 기본 인증 엔드포인트.
  • name="username", name="password": 기본 필드 이름(변경 가능).
  • ${param.error}: Thymeleaf를 사용해 로그인 실패 시 오류 메시지 표시.
  1. SecurityConfig 설정:
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(): 인증되지 않은 사용자도 로그인 페이지에 접근 가능.
  1. 컨트롤러 추가:
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으로 리다이렉트됩니다.
  • 커스텀 폼에서 입력된 usernamepassword는 POST 요청으로 /login에 전달됩니다.
  • UsernamePasswordAuthenticationFilter가 이를 처리해 인증을 수행합니다.
추가 커스터마이징
  • 실패 처리: .failureUrl("/login?error")를 추가해 실패 시 오류 메시지를 전달.
  • 필드 이름 변경: .usernameParameter("id"), .passwordParameter("pass")로 기본 이름 변경 가능.

6.3 로그아웃 기능 구현

로그아웃은 사용자의 세션을 종료하고 인증 상태를 해제하는 기능입니다. 스프링 시큐리티는 기본 로그아웃 설정을 제공하지만, 커스터마이징으로 사용자 경험을 개선할 수 있습니다.

기본 로그아웃 설정
@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에 로그아웃 버튼을 추가하려면:

<form method="post" action="/logout">
    <button type="submit">로그아웃</button>
</form>
  • POST 요청이어야 하며, CSRF 토큰이 필요합니다(기본적으로 활성화).
CSRF 비활성화 시 주의

CSRF 보호를 비활성화하면 GET 요청으로도 로그아웃 가능:

http
    .csrf(csrf -> csrf.disable())
    .logout(logout -> logout.logoutUrl("/logout"));

이 경우 <a href="/logout">로그아웃</a>로 간단히 구현 가능하지만, 보안성이 낮아지므로 주의하세요.

커스터마이징
  • 핸들러 추가: .addLogoutHandler()로 커스텀 로그아웃 로직(예: 로그 기록) 추가.
  • 성공 핸들러: .logoutSuccessHandler()로 리다이렉트 대신 JSON 응답 반환 가능.

6.4 세션 관리와 타임아웃 설정

세션 관리는 사용자의 인증 상태를 유지하고, 세션 타임아웃을 통해 보안을 강화하는 데 중요합니다. 스프링 시큐리티는 세션 설정을 세밀하게 조정할 수 있는 옵션을 제공합니다.

기본 세션 관리

스프링 시큐리티는 인증 성공 시 SecurityContext를 세션에 저장합니다. SecurityContextPersistenceFilter가 이를 관리하며, 기본적으로 서블릿 컨테이너의 세션 설정을 따릅니다.

세션 설정 예제
@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:
server.servlet.session.timeout=1800 # 30분 (초 단위)
  1. 코드로 설정:
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분으로 설정하고, 만료 후 동작 테스트.

위 내용은 커스텀 로그인/로그아웃 구현과 세션 관리 방법을 실습 가능하도록 설명했습니다. 추가적인 예제나 설정이 필요하면 말씀해 주세요!