Spring Security
Spring 기반의 어플리케이션의 보안(인증과 권한)을 담당하는 프레임워크
인증, 인가 기능을 표준화하고 자동화 할 수 있어 개발자가 보안 관련 코드를 직접 작성하지 않고도 높은 수준의 보안 기능을 쉽게 구현할 수 있다.
Spring Security 주요 아키텍처
Web Context | Java Spring Context | ||
Client HTTP Request | Filter | DispatcherServlet | Controller 1 /login |
Controller 2 /main/index |
Filter 단계에서 인증, 인가, 로깅 등을 수행하면 Java Spring 컨테이너에 도달하기 전에 보안 위협을 차단하여 시스템 안정성을 높일 수 있다.
1. Spring Security의 주요 개념
인증(Authentication)
사용자가 누구인지 확인하는 과정
- 예) 로그인 (ID/PW), OAuth2, JWT 토큰 인증 등
인가(Authorization)
사용자가 특정 리소스에 접근할 수 있는지 확인하는 과정
- 예) 관리자 페이지 접근 권한, 특정 API 요청 제한
필터(Filter) 기반 보안
- Spring Security는 Filter 기반으로 동작
- SecurityFilterChain을 사용하여 필터 체인을 구성
Spring Security 기본 설정
1. 의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
2. 설정 클래스 생성
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable) // CSRF 보호 비활성화 (필요 시 활성화)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/public").permitAll() // 특정 경로는 모두 접근 가능
.requestMatchers("/admin").hasRole("ADMIN") // 관리자만 접근 가능
.anyRequest().authenticated() // 그 외 요청은 인증 필요
)
.formLogin(Customizer.withDefaults()) // 기본 로그인 폼 사용
.logout(LogoutConfigurer::permitAll); // 로그아웃 허용
return http.build();
}
}
@Configuration : config 파일로 설정
@EnableWebSecurity : WebSecurity 활성화
- SecurityFilterchain 인터페이스 형으로 메소드를 생성
- 예외처리를 위한 throws Exception
- http 를 build하여 리턴
👀 authorizeHttpRequests()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/", "/public").permitAll() // 특정 경로는 모두 접근 가능
.requestMatchers("/admin").hasRole("ADMIN") // 관리자만 접근 가능
.anyRequest().authenticated() // 그 외 요청은 인증 필요
)
특정 경로로 요청이 왔을 때 어떤 사용자에게 오픈하여 줄지 정할 수 있다.
** Spring Security 5.7 이하 : authorizeRequests()
Spring Security 5.7 + : authorizeHttpRequest()
기본 형식 : (auth) -> auth.requestMatchers("/")
.requestMatchers 메소드 안에 경로를 적으면 그 경로에 대해서 권한을 준다는 의미
.anyRequest().authenticated() : 나머지는 로그인 필요
권한 종류
.permitAll() : 모든 사용자에게 로그인 하지 않아도 접근 할 수 있도록 설정
.hasRole() : 특정한 role이 있어야 한다. 로그인을 한 뒤에 특정한 규칙이 있어야 경로로 접근 가능
.authenticated() : 로그인만 진행하면 모두 접근 가능
.denyAll() : 모든 사용자가 로그인을 진행해도 접근 못함
엔드 포인트 별 접근 권한 설정이 없는 경우,
기본적으로 정보를 보호하기 때문에 로그인을 해야 접근 가능하도록 설정된다.
즉, 로그인 하지 않으면 모든 요청이 차단된다.
* .hasRole("권한")
.hasRole("ADMIN") → 내부적으로 "ROLE_ADMIN"을 의미한다.
.hasAuthority("ROLE_ADMIN") → 정확히 "ROLE_ADMIN"을 의미
DB에 저장된 역할이 "ROLE_XXX"라면 → hasRole("XXX") 사용
여러 역할을 허용하려면 hasAnyRole("ADMIN", "USER") 사용
👀 headers()
보안 관련 헤더 설정
단순히 @EnableWebSecurity를 클래스 레벨에 붙이는 것만으로 아무것도 안붙은 headers()가 자동으로 설정되며, 대부분의 경우 가장 좋은 보안설정이 디폴트로 되어 있다.
📌 기본 디폴트 (설정을 하지 않아도 자동으로 설정되어 있다.)
X-Frame-Options: DENY
→ 클릭재킹(Clickjacking) 방지를 위해 <iframe>로 페이지를 로드할 수 없게 함.
X-Content-Type-Options: nosniff
→ MIME 타입 스니핑 방지.
X-XSS-Protection
→ XSS공격을 필터링하는 기능
HTTP Strict-Transport-Security (HSTS)
→ HTTPS 강제 사용.
Cache-Control
→ 응답 데이터를 캐시하지 않도록 설정.
Content Type Options
→ niff기능을 실행하지 않도록하는 설정
✔️ 디폴트가 아닌 설정
Content-Security-Policy (CSP)
→ XSS(크로스 사이트 스크립팅) 공격 방지.
Referrer Policy
→ 유저가 직전에 어디 페이지에 있었는지 알려주는 설정
HTTP Public Key Pinning(HPKP)
→ 브라우저에게 특정 암호화 방식이 사용된 공개키가 특정 서버의 것이라는 것을 알려주는 설정
Custom Headers
→ 개발자가 직접 보안 관련 설정을 포함
보안 헤더 적용 비활성화 방법
모든 보안 헤더 비활성화
http.headers(AbstractHttpConfigurer::disable);
특정 보안 헤더만 비활성화
http.headers(headers -> headers
.frameOptions(FrameOptionsConfig::disable) // X-Frame-Options 제거 (iframe 허용)
.contentTypeOptions(contentType -> contentType.disable()) // X-Content-Type-Options 제거
.xssProtection(xss -> xss.disable()) // X-XSS-Protection 제거
.httpStrictTransportSecurity(hsts -> hsts.disable()) // HSTS(HTTPS 강제) 제거
);
기본 형식 : .frameOptions().disable()
👀 CORS()
cors와 관련된 설정을 한다.
✔️ CORS (Cross-Origin Resource Sharing)
웹 브라우저에서 다른 도메인에 있는 리소스를 요청할 수 있도록 허용하는 보안 매커니즘
기본적으로, 웹 브라우저는 하나의 도메인에서 실행 중인 웹 애플리케이션이 다른 도메인으로 요청을 보낸는 것을 [같은 출처 정책]으로 제한한다. Cors는 이를 우회하는 방법을 제공한다.
✔️ 동작 방식
1. Preflight 요청,
서버가 해당 요청을 처리할 수 있는지 확인 - Options 메서드 사용하여 Preflight 요청을 보냄
2. 응답 헤더,
서버는 요청을 수락할 수 있는 여부를 결정하고 해당 요청을 허용할 수 있는지 응답 헤더에 정보를 담는다.
3. 응답,
브라우저가 실제 요청을 보내면, 서버가 해당 요청을 허용하는 경우에만 데이터를 반환하고, 브라우저는 데이터를 사용한다.
✔️ 기본 형식
httpSecurity.
cors(corsConfigurer ->
corsConfigurer.configurationSource(this.corsConfigurationSource()))
✔️ 기본적인 corsConfigurationSource 설정
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://example.com")); //같은 오리진 외에 어떤 오리진을 허용할 것인지.
configuration.setAllowedMethods(Arrays.asList("GET","POST")); //어떤 메서드를 허용할 것인지
UrlBasedCorsConfigurationSource source =new UrlBasedCorsConfigurationSource(); //어느 엔트리 포인트에 적용할 것인지
source.registerCorsConfiguration("/**", configuration);
return source;
}
}
✔️ Spring MVC’s CORS support (컨트롤러에서 어노테이션 활용)
메서드에 붙이는 경우
requestMapping에 명시된 method만 허용 허용할 source를 명시할 수도 있고, 안하면 모든 소스를 허용합니다.
@CrossOrigin //메서드 레벨에 명시
@RequestMapping(Method = RequestMethod.GET)
클래스에 붙이는 경우
origins, methods, allowedHeaders, exposedHeaders, allowCredentials, maxAge 모두 설정할 수 있다
👀 sessionManagment()
세션 관리 관련 설정
✔️ 세션 생성 정책 설정 옵션
등록 형식
http.sessionManagement().sessionCreationPolicy([정책 enum 타입])
- ALWAYS : 항상 새로운 세션 생성
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.ALWAYS);
- NEVER : 생성하지는 않지만, 이미 생성된 것이 있으면 사용
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
- IF_REQUIRED : 필요한 경우에만 세션을 생성, 기본값으로 사용됨
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
- STATELESS : 생성하지 않고, 절대 사용하지 않음
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
✔️ 최대 동일 세션 설정
동일한 사용자가 동시에 사용할 수 있는 세션의 최대 수를 제한할 수 있다.
사용자 계정으로 생성할 수 있는 최대 세션 수를 제한하고, 이를 초과하면 세션을 강제로 만료시키는 등의 설정을 할 수 있다.
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(1) // 한 사용자가 최대 1개의 세션만 유지
.maxSessionsPreventsLogin(true) // 세션이 초과되면 새로 로그인하는 것을 방지
.expiredUrl("/login?expired=true") // 세션 만료 시 리다이렉트 URL 설정
.and()
.authorizeRequests()
.antMatchers("/login").permitAll() // 로그인 페이지는 모두 접근 가능
.anyRequest().authenticated(); // 나머지 요청은 인증 필요
}
}
이를 사용하려면 [SecurityConfig] 와 [ConcurrentSessionFilter]를 필요로 한다.
✔️ 둘다 포함된 설정 예시
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// SessionRegistry 빈 정의
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.sessionManagement()
.maximumSessions(1) // 한 사용자당 최대 1개의 세션만 허용
.maxSessionsPreventsLogin(true) // 세션 초과 시 새로운 로그인 방지
.expiredUrl("/login?expired=true") // 세션 만료 시 리다이렉트할 URL
.sessionRegistry(sessionRegistry()) // SessionRegistry 설정
.and()
.authorizeRequests()
.antMatchers("/login").permitAll() // 로그인 페이지는 모두 접근 가능
.anyRequest().authenticated(); // 나머지 요청은 인증 필요
}
// 로그인 이후 세션 관리 필터 추가 (보통 필터는 SecurityConfig에서 자동으로 처리됩니다)
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth);
}
}
👀 CSRF()
CSRF 공격 방어 기능
✔️ CSRF 공격
사용자가 인증된 상태에서 악의적으로 사이트가 해당 사용자를 가장하여 요청을 보내는 공격
사용자 인증 정보를 악용하여 사용자가 원하지 않는 작업을 대신 실행 할 수 있게 만든다.
이러한 공격을 방버하여 사용자가 의도하지 않은 요청을 방지할 수 있다.
✔️ 고려사항
- AJAX 요청, 클라이언트 측에서 AJAX 요청을 보내는 경우 CSRF 토큰을 포함시키는 것이 중요하다.
- Restful API 경우, 일반적으로 stateless 하므로, CSRF 를 비활성화하는 경우가 많다.
이 경우, API 토큰(JWT)을 사용하여 인증 및 권한 부여를 처리한다.
- 자동으로 Spring Security 가 CSRF 토큰을 처리해주므로 특별한 추가 작업이 필요하지 않다.
✔️ 설정 방법
@EnableWebSecurity 설정 시 자동으로 활성화 된다.
✔️ 비활성화 방법 (2가지)
1. csrf().disable()
2. csrf(AbstractHttpConfigurer::disable)
🔗 비활성화 구문, 두 개의 차이점
기능적으로는 동일하다.
1은 단순히 메서드 호출을 사용한 방식이고,
2는 AbstractHttpConfigurer의 disable 메서드를 통해 비활성화하는 방식 이다.
AbstractHttpConfigurer는 Spring Security에서 다양한 보안 설정을 위한 기본 클래스이므로,
이 방법은 좀 더 유연한 설정을 할 수 있는 기회를 제공한다.
하지만, 아무거나 사용해도 상관은 없다.
👀 formLogin()
주로 SSR 방식의 어플리케이션에서 Form 기반으로 로그인을 수행하는 경우 관련 설정을 할 때 사용한다.
하지만, 그렇지 않다면 disable하여 관련 필터 사용하지 않을 수 있다.
✔️ 형식
.formLogin(AbstractHttpConfigurer::disable)
👀 httpBasic()
username과 password를 요구하여, 서버에 저장된 것과 비교해서 인증하는 방법
credential이 암호화 되어 있지않고, 쿠키/세션/로긴 페이지를 사용하지 않기 때문에 보안이 취약하다.
disable()할 수 있다.
👀 addFilter()
커스텀한 필터를 추가한다.
👀 addFilterAfter()
✔️ 형식
addFilterAfter(추가할 필터 인스턴스, 추가할 기준이되는 필터 클래스정보.class)
두번째 인자를 기준으로 그 뒤에 필터를 추가합니다.
👀 apply()
외부에서 정의한 설정 클래스를 적용
✔️ 형식
http.apply(new CustomFilterConfiguerer())
'Spring' 카테고리의 다른 글
Spring, Entity와 DTO의 차이점 및 구조 파악 / 변환 방법 (0) | 2025.03.13 |
---|---|
Spring, Spring Boot 기본 구조 (0) | 2025.03.13 |
Spring Boot, Delete 메서드 구현 (@DeleteMapping) (0) | 2025.02.25 |
Spring Boot, Swagger란? (0) | 2025.02.25 |
Sprint Boot, Sprint Boot + MyBatis (0) | 2024.11.14 |