SpringSecurity에 보안 설정을 하기위해서 Configuration Class에서 HttpSecurity를 통해 SecurityFilterChain을 Bean으로 생성하는데요. 이때 HttpSecurity에 어떤 설정들을 해줄 수 있는지 정리해보았습니다.
기본 형식
기존에는 WebSecurityConfigurerAdapter
를 통해 보안설정되었지만, 현재는 deprecated되어 SecurityFilterChain를 통해 아래처럼 보안설정을 하게 되었습니다.
@Configuration
@EnableWebSecurity
public class FormLoginSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers("/**").hasRole("USER").and().formLogin();
return http.build();
}
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
headers()
보안 관련 헤더 설정을 합니다. 단순히 @EnableWebSecurity
를 클래스 레벨에 붙이는 것만으로, 아무것도 안붙은 headers()가 자동으로 설정이 되며, 대부분의 경우 가장 좋은 보안설정이 디폴트로 되어있습니다.
1. Cache Control
브라우저가 캐시를 지원하면서, 보안관련 정보가 캐시에 저장된걸 악용할 수 있게 됐습니다. 그래서 자동으로 아래의 캐시 설정을 추가해, 캐시를 사용하지 못하게 합니다. headers().cacheControl.disable()을 통해 캐시사용하게 설정할 수 있습니다.Cache-Control: no-cache, no-store, max-age=0, must-revalidate
Pragma: no-cache
Expires: 0
2. Content Type Options
브라우저에 content sniff라는 content type을 유추하는 기능이 있습니다. 예를 들어, content type을 명시하지 않은 채, 자바스크립트 파일이 오면 content sniff를 통해 자바스크립트라는 것을 알아낸 후 이것을 실행합니다. 또한 어떤 파일들은 유효한 여러 content type을 가질 수 있습니다. 자바스크립트 이외의 다른 content type인 것처럼 보내놓고 실재로는 자바스크립트를 포함시킨다면 content sniff에 의해 실행될 수 있고, 이 기능을 악용하여 XSS공격을 할 수 있습니다. 그래서 sniff기능을 실행하지 않도록하는 설정이 디폴트로 들어감. 역시 disable할 수 있습니다
3. HTTP Strict Transport Security(HSTS)
응용계층 프로토콜을 명시하지 않아도, 무조건 https로 해석하게 합니다.
4. HTTP Public Key Pinning(HPKP)
공격자가 CA를 해킹하게 될 경우, 브라우저는 CA를 신뢰하기 때문에 중간자 공격을 감행할 수 있습니다. 이것을 방지하기 위해, 최초에 이 설정이 들어간 서버와 통신할 때, 특정 암호화 방식이 사용된 공개키를 기억하게 합니다. 브라우저에게 특정 암호화 방식이 사용된 공개키가 특정 서버의 것이라는 것을 알려주는 설정입니다.
중간자 공격 : 클라이언트와 서버 사이에서 통신을 가로채서 통신 상대방인 척 속이는 응답을 하는 공격
5. X-Frame-Options
이 속성이 포함된 response를 frame 안에서 랜더링되는 것을 막음으로써, Clickjacking 공격을 방어할 수 있습니다. 기본적으로 모든 요청에 대해 이 속성이 적용되지만, 아래와 같이 커스텀 할 수 있습니다.
http.headers().frameOptions().sameOrigin();
Clickjacking: 사용자가 구분하기 어려운 방법으로 html 페이지 내에 버튼 등을 숨겨서, 의도치 않게 클릭하게 하는 공격
6. X-XSS-Protection
XSS공격을 필터링하는 기능이며, 디폴트로 설정되어 있습니다.
XSS: Cross-Site Script의 약자. CSS라고 줄일 수 있으나, 우리가 아는 CSS와 혼동이 생길 수 있어 XSS로 표현한답니다. 아무튼 이 공격은 해커가 공격하려는 사이트에 스크립트 코드를 삽입하여, 사용자가 이 페이지를 열 때 이 스크립트가 실행되어 의도치 않는 행동을 하거나 토큰/세션 등을 탈취하는 것입니다.
7. Content Security Policy(CSP)
데이터가 로드되는 소스를 명시합니다. 공식문서에는 XSS 공격을 막기 위한 ‘first line’이라고 설명되어 있습니다. 확실한 방어 방법은 아닐 수 있어도, 최소한의 보안설정입니다. 기본 설정으로는 안되어 있고, 명시해줄 수 있습니다.
8. Referrer Policy
유저가 직전에 어디 페이지에 있었는지 알려주는 설정입니다. 디폴트로 설정은 안되어 있어서, 설정해줘야합니다. http.headers().referrerPolicy(ReffererPolicy.SAME\_ORIGIN);
9. Custom Headers
개발자가 직접 보안 관련 설정을 포함할 수 있습니다.http.headers().addHeaderWriter(new StaticHeadersWriter(”헤더”, “벨류”));
cors()
CORS와 관련된 설정을 합니다.
CORS: Cross-Origin Resource Sharing. Orign이 다른 곳에서 요청이 온 경우 기본적으로는 해킹을 차단하기 위해 접근을 막지만, 필요한 경우가 있음으로 CORS를 지킨 요청을 허용합니다.
http.cors().and()…
라고 설정을 하면, 디폴트로 corsConfigurationSouce
라는 이름의 빈을 찾아서 사용하게 됩니다. Spring MVC’s CORS support를 사용하고 있고, corsConfigurationSource
가 없다면, Spring MVC에게 제공된 CORS 설정을 사용합니다.
기본적인 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()
세션 관리 관련 설정을 합니다.
세션 생성 정책 설정
- 4가지 옵션이 있습니다.
- ALWAYS
- NEVER(생성하지는 않지만, 이미 생성된 것 있으면 사용
- IF_REQUIRED
- STATELESS(생성하지 않고, 절대 사용하지 않음)
- 아래와 같이 등록합니다.
- http.sessionManagement().sessionCreationPolicy([정책 enum타입])
최대 동일 세션 설정
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeRequests().anyRequest().hasRole("USER").and().formLogin()
.permitAll().and().sessionManagement().maximumSessions(1)
.expiredUrl("/login?expired");
return http.build();
}
httpSessionEventPublisher 설정해줘야함
@Bean
public static ServletListenerRegistrationBean httpSessionEventPublisher() {
return new ServletListenerRegistrationBean(new HttpSessionEventPublisher());
}
csrf()
CSRF 공격 방어기능이며, @EnableWebSecurity 설정시 자동으로 활성화 된다.
CSRF: Cross-Site Request Forgery. 해커가 사용자에게 다른 사이트에 접속하여 해커가 의도한대로 행동하게 하는 악성코드를 메일 등 방법을 통하여 보내게 되면, 사용자가 이것을 열었을 때 의도치 않은 행동을 합니다.
이런 공격은 대게 사용자가 어떤 사이트에 인증과정을 거친 후 토큰을 발급받은 상황을 기대하고 공격합니다. 인증을 한 사이트는 발급했던 토큰을 검사하여 인가를 진행하기 때문에, CSRF 공격으로 인한 요청도 사용자가 의도한 것과 상관없이 브라우저에 저장된 토큰을 함께 싣어 사이트에 요청을 날리기 때문에, 해당 사이트는 인가를 할 수 밖에 없습니다.
이런 공격을 막기 위해 사용하는 것이 CSRF TOKEN입니다. 이 토큰과 대게 사용하는 JWT 토큰의 차이점은 소멸시점과 접근 권한입니다.
JWT 토큰은 역시 소멸시간을 주로 짧게 주는 편이지만, CSRF 토큰은 서버에 요청을 보낼 때마다 삭제를 하고 다시 발급을 하여, 그 유지기간이 상당히 짧습니다. 또한 JWT 토큰은 주로 브라우저의 Local Storage에 저장을 하는데 이 저장소는 브라우저를 종료하더라도 유지됩니다. 반면 CSRF는 Session Storage에 저장하게 되는데, 이 저장소는 브라우저 내에 탭을 종료하는 순간 사라집니다.
또한 접근 권한에도 차이가 있는데요. 앞에서 설명한 것처럼 CSRF 토큰은 Session Storage에 저장하게 되는데, 이 저장소는 같은 Tab에서만 공유가 됩니다. 반면 JWT가 주로 저장되는 Local Storage는 모든 탭에서 공유할 수 있습니다.
따라서 CSRF 공격을 수행하더라도 CSRF 토큰이 이미 사라지거나 유효하지 않을 가능성이 크고, 또한 브라우저 내에 다른 탭에서 공유할 수 없기 때문에 탈취하기도 어렵습니다.
이러한 csrf 토큰을 자동으로 발행하고 검사하는 기능을 csrf()를 통해 등록할 수 있습니다.
csrf를 활성화하면 서버로 요청을 보낼 때 항상 csrf 토큰을 포함하여 요청을 보내야하는데, 개발 단계에서는 불필요할 수도 있기 때문에 아래와 같이 비활성화할 수 있습니다.
csrf().disable()
formLogin()
주로 SSR 방식의 어플리케이션에서 Form 기반으로 로그인을 수행하는 경우 관련 설정을 할 때 사용합니다. 그렇지 않다면 disable 하여 관련 필터 사용하지 않을 수 있습니다.
httpBasic()
username과 password를 요구하여, 서버에 저장된 것과 비교해서 인증하는 방법입니다. credential이 암호화 되어 있지않고, 쿠키/세션/로긴 페이지를 사용하지 않기 때문에 보안이 취약합니다. disable()할 수 있습니다.
addFilter()
커스텀한 필터를 추가합니다.
addFilterAfter(추가할 필터 인스턴스, 추가할 기준이되는 필터 클래스정보.class)
두번째 인자를 기준으로 그 뒤에 필터를 추가합니다.
apply()
외부에서 정의한 설정 클래스를 적용합니다.
http.apply(new CustomFilterConfiguerer())
public class CustomFilterConfigurer extends AbstractHttpConfigurer<CustomFilterConfigurer, HttpSecurity> {
@Override
public void configure(HttpSecurity builder) throws Exception {
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
JwtAuthenticationFilter jwtAuthenticationFilter = new JwtAuthenticationFilter(authenticationManager, jwtTokenizer);
jwtAuthenticationFilter.setFilterProcessesUrl("/v11/auth/login");
builder.addFilter(jwtAuthenticationFilter);
}
}
authorizeHttpRequest()
엔트리 포인트 별 접근 권한을 설정할 수 있습니다.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authorizeHttpRequests) ->
authorizeHttpRequests
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/**").hasRole("USER")
)
.formLogin(withDefaults());
return http.build();
}
참조
https://docs.spring.io/spring-security/site/docs/5.0.x/reference/html/
'Back-end > Spring Boot' 카테고리의 다른 글
[Oauth2] Oauth2로 회원가입 / 로그인 기능 구현하기 (0) | 2022.11.03 |
---|---|
[JPA] Infinite Recursion 해결법 (0) | 2022.10.23 |
[SpringBoot] SpringSecurity 인증 처리 흐름 (1) | 2022.09.26 |
[SpringBoot] WebClient 이해하기(feat. block/non-block과 동기/비동기) (0) | 2022.08.01 |
[SpringBoot] 빌더 어노테이션(@Builder)와 빌더 패턴 (0) | 2022.07.22 |