2025-04-08T19:56:24

This commit is contained in:
2025-04-08 19:56:24 +09:00
parent a75a1dbd0f
commit eef061c1c9
100 changed files with 18639 additions and 0 deletions

View File

@@ -0,0 +1,209 @@
아래는 "스프링 시큐리티" 책의 6장에 포함될 "커스텀 로그인 페이지 제작", "로그아웃 기능 구현", 그리고 "세션 관리와 타임아웃 설정"에 대한 내용입니다. 실습 가능한 예제와 함께 실무에서 유용한 설정을 설명했습니다.
---
### 6장. 로그인/로그아웃 커스터마이징
#### 6.1 커스텀 로그인 페이지 제작
스프링 시큐리티의 기본 로그인 페이지는 간단한 테스트에는 유용하지만, 실무에서는 디자인과 기능을 사용자 맞춤으로 변경해야 합니다. **커스텀 로그인 페이지**를 제작하면 애플리케이션의 UI/UX를 개선하고, 추가적인 인증 로직을 통합할 수 있습니다.
##### 커스텀 로그인 페이지 설정
1. **HTML 페이지 제작**: 먼저, 로그인 폼을 포함한 커스텀 페이지를 만듭니다. 예를 들어, `src/main/resources/templates/login.html`:
```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를 사용해 로그인 실패 시 오류 메시지 표시.
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
<form method="post" action="/logout">
<button type="submit">로그아웃</button>
</form>
```
- POST 요청이어야 하며, CSRF 토큰이 필요합니다(기본적으로 활성화).
##### CSRF 비활성화 시 주의
CSRF 보호를 비활성화하면 GET 요청으로도 로그아웃 가능:
```java
http
.csrf(csrf -> csrf.disable())
.logout(logout -> logout.logoutUrl("/logout"));
```
이 경우 `<a href="/logout">로그아웃</a>`로 간단히 구현 가능하지만, 보안성이 낮아지므로 주의하세요.
##### 커스터마이징
- **핸들러 추가**: `.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분으로 설정하고, 만료 후 동작 테스트.
---
위 내용은 커스텀 로그인/로그아웃 구현과 세션 관리 방법을 실습 가능하도록 설명했습니다. 추가적인 예제나 설정이 필요하면 말씀해 주세요!