Files
spring-boot-examples/docs/security.md
2025-04-08 19:56:24 +09:00

474 lines
24 KiB
Markdown

### 스프링 시큐리티에 대한 설명
스프링 시큐리티(Spring Security)는 스프링 기반 애플리케이션에서 보안 기능을 제공하는 강력하고 유연한 프레임워크입니다. 인증(Authentication)과 인가(Authorization)를 비롯한 다양한 보안 요구사항을 처리하며, 웹 애플리케이션, REST API, 마이크로서비스 등에서 널리 사용됩니다. 스프링 부트와 통합 시 자동 설정 기능을 통해 최소한의 코드로 보안을 구현할 수 있다는 점이 큰 장점입니다. 아래에서 주요 개념, 동작 방식, 설정 방법 등을 설명합니다.
---
#### 1. 스프링 시큐리티의 주요 기능
- **인증(Authentication)**: 사용자가 누구인지 확인합니다 (예: 로그인).
- **인가(Authorization)**: 인증된 사용자가 어떤 작업을 수행할 수 있는지 권한을 확인합니다 (예: 관리자만 접근 가능).
- **보안 필터 체인**: HTTP 요청을 가로채 보안 로직을 적용합니다.
- **세션 관리**: 로그인 상태 유지 및 세션 공격 방지.
- **CSRF 방어**: 크로스 사이트 요청 위조 공격 방지.
- **비밀번호 암호화**: 안전한 비밀번호 저장을 위한 해시 알고리즘 제공.
---
#### 2. 스프링 시큐리티의 동작 원리
스프링 시큐리티는 **필터 기반 아키텍처**를 사용합니다. HTTP 요청이 들어오면 `FilterChain`을 통해 보안 필터들이 순차적으로 실행되며, 각 필터는 특정 보안 작업(인증, 인가 등)을 처리합니다.
- **핵심 컴포넌트**:
- `SecurityFilterChain`: 요청을 처리하는 필터 체인을 정의.
- `AuthenticationManager`: 인증 요청을 처리.
- `UserDetailsService`: 사용자 정보를 로드 (예: DB에서 조회).
- `PasswordEncoder`: 비밀번호 암호화 및 검증.
- **동작 흐름**:
1. 사용자가 `/login`으로 로그인 요청.
2. `UsernamePasswordAuthenticationFilter`가 요청을 가로채 인증 시도.
3. `AuthenticationManager``UserDetailsService`로 사용자 정보를 조회.
4. 비밀번호 일치 여부 확인 후 인증 객체(`Authentication`) 생성.
5. `SecurityContextHolder`에 인증 정보 저장.
6. 이후 요청에서 인가 확인 후 접근 허용/차단.
---
#### 3. 스프링 부트에서 기본 설정
스프링 부트에 `spring-boot-starter-security` 의존성을 추가하면 기본 보안 설정이 활성화됩니다.
- **의존성 추가** (`pom.xml`):
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
```
- **기본 동작**:
- 모든 요청에 대해 인증 요구.
- 기본 사용자 생성 (ID: `user`, 비밀번호: 실행 시 로그에 출력).
- `/login` 폼 제공.
- **application.yaml로 기본 사용자 설정**:
```yaml
spring:
security:
user:
name: admin
password: password123
```
---
#### 4. 커스터마이징 예시
스프링 시큐리티를 커스터마이징하려면 `SecurityFilterChain`을 정의하는 설정 클래스를 작성합니다.
- **예시**: 기본 인증 및 인가 설정
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // 공개 경로
.requestMatchers("/admin/**").hasRole("ADMIN") // 관리자만 접근
.anyRequest().authenticated() // 나머지는 인증 필요
)
.formLogin(form -> form
.loginPage("/login") // 커스텀 로그인 페이지
.permitAll()
)
.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/login?logout")
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password(passwordEncoder().encode("pass"))
.roles("USER")
.build();
UserDetails admin = User.withUsername("admin")
.password(passwordEncoder().encode("admin"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user, admin);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
```
- **설명**:
- `/public/**`: 인증 없이 접근 가능.
- `/admin/**`: "ADMIN" 역할 필요.
- 그 외: 인증된 사용자만 접근.
- 메모리 내 사용자 정의 (실제로는 DB 연동 추천).
---
#### 5. 데이터베이스 연동
실제 애플리케이션에서는 `UserDetailsService`를 구현해 데이터베이스에서 사용자 정보를 가져옵니다.
- **예시**:
```java
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
UserEntity user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
return User.withUsername(user.getUsername())
.password(user.getPassword())
.roles(user.getRole())
.build();
}
}
```
- `SecurityConfig`에 주입:
```java
@Bean
public UserDetailsService userDetailsService(CustomUserDetailsService customUserDetailsService) {
return customUserDetailsService;
}
```
---
#### 6. REST API와 JWT
REST API에서는 폼 로그인 대신 JWT(JSON Web Token)를 사용해 stateless 인증을 구현할 수 있습니다.
- **구현 개요**:
1. `/login`에서 사용자 인증 후 JWT 발급.
2. 클라이언트가 `Authorization` 헤더에 JWT 포함.
3. `JwtAuthenticationFilter`로 요청마다 토큰 검증.
- **필터 예시**:
```java
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
// JWT 검증 로직 추가
chain.doFilter(request, response);
}
}
```
---
#### 7. 주요 어노테이션
- `@EnableWebSecurity`: 시큐리티 설정 활성화.
- `@PreAuthorize`, `@PostAuthorize`: 메서드 수준에서 인가 확인.
- 예: `@PreAuthorize("hasRole('ADMIN')")`
- `@Secured`: 역할 기반 접근 제어.
---
#### 8. 모범 사례
- **비밀번호 암호화**: `BCryptPasswordEncoder` 사용.
- **CSRF 비활성화**: REST API에서는 `http.csrf().disable()` 설정.
- **HTTPS**: 프로덕션에서 보안 통신 강제.
- **에러 처리**: 인증/인가 실패 시 커스텀 응답 제공.
---
### 결론
스프링 시큐리티는 강력한 보안 기능을 제공하며, 스프링 부트와의 통합으로 설정이 간편합니다. 기본 설정으로 빠르게 시작할 수 있고, 커스터마이징을 통해 복잡한 요구사항도 충족할 수 있습니다. 웹 애플리케이션부터 REST API, OAuth2 통합까지 다양한 시나리오를 지원하므로, 보안이 중요한 애플리케이션에서 필수적인 도구입니다. 추가 질문이 있다면 언제든 물어보세요!
---
스프링 시큐리티와 관련된 주요 어노테이션과 클래스들을 표로 정리하겠습니다. 스프링 시큐리티는 인증(Authentication)과 인가(Authorization)를 관리하기 위한 다양한 어노테이션과 클래스를 제공하며, 이를 통해 보안 설정을 코드 수준에서 유연하게 적용할 수 있습니다.
---
### 1. 스프링 시큐리티 관련 어노테이션
| **어노테이션** | **설명** | **사용 예시** |
|---------------------------|-------------------------------------------------------------------------------------------|------------------------------------------------|
| `@EnableWebSecurity` | 스프링 시큐리티를 활성화하고, 웹 보안 설정을 커스터마이징할 수 있게 함 | `@EnableWebSecurity public class SecurityConfig` |
| `@EnableGlobalMethodSecurity` | 메서드 수준 보안을 활성화 (예: `@PreAuthorize`, `@Secured` 등 사용 가능) | `@EnableGlobalMethodSecurity(prePostEnabled = true)` |
| `@PreAuthorize` | 메서드 실행 전에 권한을 확인 (SpEL 지원) | `@PreAuthorize("hasRole('ADMIN')")` |
| `@PostAuthorize` | 메서드 실행 후 반환값을 기반으로 권한 확인 | `@PostAuthorize("returnObject.owner == authentication.name")` |
| `@Secured` | 특정 역할(Role)이 있는 사용자만 메서드 실행 허용 | `@Secured("ROLE_ADMIN")` |
| `@RolesAllowed` | JSR-250 표준 어노테이션으로 `@Secured`와 유사한 역할 기반 인가 | `@RolesAllowed("ROLE_USER")` |
| `@AuthenticationPrincipal`| 현재 인증된 사용자의 `Principal` 객체를 메서드 파라미터로 주입 | `@GetMapping("/me") public String getUser(@AuthenticationPrincipal UserDetails user)` |
| `@EnableMethodSecurity` | 메서드 보안을 개별적으로 활성화 (Spring 6.1 이상에서 `@EnableGlobalMethodSecurity` 대체) | `@EnableMethodSecurity` |
- **참고**:
- `@PreAuthorize`와 `@PostAuthorize`는 Spring Expression Language(SpEL)를 지원해 복잡한 조건을 정의할 수 있습니다.
- `@Secured`와 `@RolesAllowed`는 단순 역할 체크에 적합하지만, SpEL은 지원하지 않습니다.
---
### 2. 스프링 시큐리티 관련 주요 클래스
| **클래스** | **설명** | **주요 사용처** |
|------------------------------|-------------------------------------------------------------------------------------------|-----------------------------------------------|
| `SecurityFilterChain` | HTTP 요청을 처리하는 보안 필터 체인을 정의 | `HttpSecurity` 설정에서 반환 |
| `HttpSecurity` | HTTP 보안 설정을 구성 (인증, 인가, CSRF, 로그인 등) | `SecurityFilterChain` 구성 |
| `Authentication` | 인증된 사용자의 정보를 나타내는 인터페이스 | `SecurityContext`에서 가져옴 |
| `AuthenticationManager` | 인증 요청을 처리하고 `Authentication` 객체를 반환 | 사용자 인증 로직 |
| `UserDetails` | 사용자 정보를 나타내는 인터페이스 (ID, 비밀번호, 권한 등) | `UserDetailsService`에서 반환 |
| `UserDetailsService` | 사용자 정보를 로드하는 인터페이스 (DB 연동 시 구현 필요) | 커스텀 사용자 조회 로직 |
| `PasswordEncoder` | 비밀번호를 암호화하고 검증하는 인터페이스 | `BCryptPasswordEncoder` 구현체 사용 |
| `SecurityContext` | 현재 인증 상태를 저장하는 컨텍스트 | `SecurityContextHolder`에서 관리 |
| `SecurityContextHolder` | 현재 스레드의 보안 컨텍스트를 관리 (인증 정보 접근) | `Authentication` 객체 가져오기 |
| `UsernamePasswordAuthenticationToken` | 사용자 이름과 비밀번호 기반 인증 토큰 | 로그인 인증 시 생성 |
| `AccessDeniedException` | 권한 부족으로 접근이 거부될 때 발생하는 예외 | 인가 실패 처리 |
| `AuthenticationEntryPoint` | 인증 실패 시 응답을 처리하는 인터페이스 (예: 401 반환) | 커스텀 로그인 실패 처리 |
| `OncePerRequestFilter` | 요청당 한 번만 실행되는 필터를 구현하기 위한 추상 클래스 | JWT 인증 필터 구현 |
| `InMemoryUserDetailsManager` | 메모리 내에서 사용자 정보를 관리하는 `UserDetailsService` 구현체 | 테스트용 사용자 설정 |
| `WebSecurityCustomizer` | 웹 보안 설정을 커스터마이징 (특정 경로 무시 등) | 정적 리소스 보안 제외 |
---
### 추가 설명
#### 어노테이션 사용 팁
- **`@EnableWebSecurity`**: 필수 설정으로, `SecurityFilterChain` 빈을 정의할 때 사용됩니다.
- **`@PreAuthorize` vs `@Secured`**: `@PreAuthorize`는 조건이 복잡할 때, `@Secured`는 단순 역할 체크에 적합합니다.
- **`@AuthenticationPrincipal`**: 컨트롤러에서 현재 로그인한 사용자를 쉽게 가져올 수 있어 편리합니다.
#### 클래스 활용 예시
- **HttpSecurity 설정**:
```java
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.formLogin();
return http.build();
}
```
- **UserDetailsService 구현**:
```java
@Service
public class CustomUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) {
// DB에서 사용자 조회 로직
return new User(username, "{bcrypt}hashedPassword", Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")));
}
}
```
- **PasswordEncoder 설정**:
```java
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
```
---
### 결론
스프링 시큐리티의 어노테이션은 설정과 메서드 수준 보안을 간편하게 적용할 수 있게 해주며, 관련 클래스들은 인증/인가 로직을 세밀하게 제어할 수 있도록 돕습니다. 이를 조합하면 웹 애플리케이션부터 REST API까지 다양한 보안 시나리오를 구현할 수 있습니다. 추가적인 질문이나 구체적인 예제가 필요하면 말씀해주세요!
---
### HttpSecurity 클래스의 메서드 정리와 예시 설명
스프링 시큐리티에서 `HttpSecurity` 클래스는 HTTP 요청에 대한 보안 설정을 구성하는 핵심 클래스입니다. `SecurityFilterChain`을 생성할 때 사용되며, 인증(Authentication), 인가(Authorization), 로그인, 로그아웃, CSRF 보호 등 다양한 보안 기능을 메서드 체이닝 방식으로 설정할 수 있습니다. 아래에서 주요 메서드를 표로 정리하고, 예시와 함께 설명하겠습니다.
---
### 1. HttpSecurity 주요 메서드 표
| **메서드** | **설명** |
|-------------------------------|-------------------------------------------------------------------------------------------|
| `authorizeHttpRequests()` | HTTP 요청에 대한 인가 규칙을 정의 (경로별 접근 제어 설정) |
| `formLogin()` | 폼 기반 로그인 설정 (로그인 페이지, 성공/실패 처리 등) |
| `httpBasic()` | HTTP 기본 인증 설정 (클라이언트가 사용자 이름과 비밀번호를 헤더로 전송) |
| `logout()` | 로그아웃 설정 (로그아웃 URL, 성공 후 리다이렉션 등) |
| `csrf()` | CSRF(Cross-Site Request Forgery) 보호 설정 (활성화/비활성화) |
| `cors()` | CORS(Cross-Origin Resource Sharing) 설정 |
| `sessionManagement()` | 세션 관리 설정 (세션 생성 정책, 최대 세션 수 등) |
| `exceptionHandling()` | 인증/인가 예외 처리 설정 (접근 거부, 인증 실패 시 동작) |
| `headers()` | HTTP 헤더 보안 설정 (X-Frame-Options, XSS 보호 등) |
| `requestMatchers()` | 특정 요청 경로에만 보안 설정 적용 |
| `anonymous()` | 익명 사용자에 대한 설정 (익명 사용자의 권한 정의) |
| `oauth2Login()` | OAuth2 기반 로그인 설정 (소셜 로그인 등) |
| `addFilter()` | 커스텀 필터를 필터 체인에 추가 |
| `addFilterBefore()` | 기존 필터 앞에 커스텀 필터 추가 |
| `addFilterAfter()` | 기존 필터 뒤에 커스텀 필터 추가 |
| `authenticationProvider()` | 커스텀 인증 제공자(AuthenticationProvider)를 추가 |
| `accessDeniedHandler()` | 인가 실패(403) 시 호출되는 핸들러 설정 |
| `and()` | 메서드 체이닝에서 이전 설정을 종료하고 새로운 설정 섹션으로 이동 |
---
### 2. HttpSecurity 설정 예시와 설명
#### 기본 설정 예시
다음은 일반적인 웹 애플리케이션의 보안 설정 예시입니다.
```java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll() // 공개 경로
.requestMatchers("/admin/**").hasRole("ADMIN") // 관리자만 접근
.anyRequest().authenticated() // 나머지 인증 필요
)
.formLogin(form -> form
.loginPage("/login") // 커스텀 로그인 페이지
.defaultSuccessUrl("/home") // 로그인 성공 시 이동
.permitAll() // 로그인 페이지는 누구나 접근 가능
)
.logout(logout -> logout
.logoutUrl("/logout") // 로그아웃 요청 URL
.logoutSuccessUrl("/login?logout") // 로그아웃 후 이동
)
.exceptionHandling(ex -> ex
.accessDeniedPage("/access-denied") // 인가 실패 시 이동 페이지
)
.csrf(csrf -> csrf
.disable() // CSRF 비활성화 (테스트용)
);
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("user")
.password("{noop}password") // {noop}은 암호화 없이 사용
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
```
#### 메서드별 설명
1. **`authorizeHttpRequests()`**
- **설명**: 요청 경로별로 접근 권한을 설정합니다. `requestMatchers()`로 경로를 지정하고, `permitAll()`, `hasRole()`, `authenticated()` 등으로 권한을 정의합니다.
- **예시**: `/public/**`은 누구나 접근 가능, `/admin/**`은 "ROLE_ADMIN" 역할 필요.
2. **`formLogin()`**
- **설명**: 폼 기반 로그인을 활성화하고, 로그인 페이지와 성공/실패 동작을 커스터마이징합니다.
- **예시**: `/login` 페이지로 이동하며, 성공 시 `/home`으로 리다이렉션.
3. **`logout()`**
- **설명**: 로그아웃 요청을 처리하고, 성공 후 이동할 경로를 설정합니다.
- **예시**: `/logout` 요청으로 로그아웃 후 `/login?logout`으로 이동.
4. **`exceptionHandling()`**
- **설명**: 인증/인가 실패 시 동작을 정의합니다. `accessDeniedPage()`로 403 오류 페이지를 지정할 수 있습니다.
- **예시**: 권한 없는 사용자가 접근 시 `/access-denied`로 이동.
5. **`csrf()`**
- **설명**: CSRF 보호를 설정합니다. REST API에서는 보통 비활성화(`disable()`)하지만, 웹 폼에서는 활성화 권장.
- **예시**: `csrf().disable()`로 비활성화 (REST API 환경에서 유용).
---
#### REST API용 설정 예시 (CSRF 비활성화 및 HTTP Basic)
REST API에서는 폼 로그인 대신 HTTP Basic 인증을 사용할 수 있습니다.
```java
@Configuration
@EnableWebSecurity
public class RestSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/public/**").permitAll()
.anyRequest().authenticated()
)
.httpBasic(httpBasic -> httpBasic
.realmName("My API") // Basic 인증 영역 이름
)
.csrf(csrf -> csrf.disable()) // REST API는 CSRF 비활성화
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 세션 사용 안 함
);
return http.build();
}
}
```
- **`httpBasic()`**: 클라이언트가 `Authorization: Basic base64(username:password)` 헤더로 인증.
- **`sessionManagement()`**: 세션 생성 정책을 `STATELESS`로 설정해 RESTful 특성 유지.
---
#### 커스텀 필터 추가 예시
JWT 기반 인증을 위해 커스텀 필터를 추가할 수 있습니다.
```java
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
// JWT 검증 로직
chain.doFilter(request, response);
}
}
@Configuration
@EnableWebSecurity
public class JwtSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated()
)
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class)
.csrf(csrf -> csrf.disable());
return http.build();
}
}
```
- **`addFilterBefore()`**: `UsernamePasswordAuthenticationFilter` 앞에 JWT 필터 추가.
---
### 3. 결론
`HttpSecurity`의 메서드는 스프링 시큐리티에서 HTTP 요청 보안을 세밀하게 제어할 수 있게 해줍니다. `authorizeHttpRequests()`로 경로별 권한을 설정하고, `formLogin()`, `httpBasic()`, `logout()` 등으로 인증 방식을 정의하며, `csrf()`, `sessionManagement()`으로 추가 보안 옵션을 조정할 수 있습니다. 예시를 통해 웹 애플리케이션과 REST API 모두에 적용 가능한 설정을 확인할 수 있으며, 필요에 따라 커스텀 필터를 추가해 확장성도 확보할 수 있습니다. 추가 질문이 있다면 언제든 물어보세요!