개발자의 오르막
[SpringSecurity #10] HeaderWriterFilter, CsrfFilter 본문
[SpringSecurity #10] HeaderWriterFilter, CsrfFilter
계단 2020. 10. 3. 00:54# 응답 헤더에 시큐리티 관련 헤더를 추가해주는 필터
- XContentTypeOptionsHeaderWriter : 마임 타입 스니핑 방어
( 실행할 수 없는 마임 타입을 실행하려는 요청을 방어해줌 )
- XXssProtectionHeaderWriter : 브라우저에 내장된 XSS필터 적용
- CacheControlHeaderWriter : 캐시 히스토리 취약점 방어
- HstsHeaderWriter : HTTPS로만 소통하도록 강제
- XFrameOptionsHeaderWriter : clickjacking 방어
- Cache-Control : no-cache, no-store, max-age=0, must-revalidate
( 캐쉬를 쓰지 않도록 설정 )
- Content-Language : en-US
- Content-Type : text/html;charset=UTF-8
( 컨텐츠 타입에 명시된 마임 타입으로만 랜더링 하게 되어 있음 )
- Date : Sun, 04 Aug 2019 16:25:10 GMT
- Expires : 0
- Pragma : no-cache
( 캐쉬를 쓰지 않도록 설정해주는 것 )
- Transfer-Encoding : chunked
- X-Content-Type-Options : nosniff
( 실행이 되지 않음 )
- X-Frame-Options : DENY
( 보이지 않는 영역을 클릭 할 때 정보를 전송하는 것을 방지하는 것 )
- X-XSS-Protection : 1; mode=block
( 1이 기능을 활성화 하는 것을 의미 )
# CsrfFilter
- CSRF 어택 방지 필터
인증된 유저의 계정을 사용해 악의적인 변경 요청을 만들어 보내는 기법
CORS를 사용할 때 특히 주의해야 함
( 타 도메인에서 보내오는 요청을 허용하기 때문 )
- 의도한 사용자만 리소스를 변경할 수 있도록 허용하는 필터
( CSRF 토큰을 사용하여 다른 사용자 접근 방지 )
위의 계좌 이체 부분에서 해당 CSRF 가 없기 때문에 요청을 막아준다.
- CsrfFilter
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
request.setAttribute(HttpServletResponse.class.getName(), response);
CsrfToken csrfToken = this.tokenRepository.loadToken(request);
final boolean missingToken = csrfToken == null;
if (missingToken) {
csrfToken = this.tokenRepository.generateToken(request);
this.tokenRepository.saveToken(csrfToken, request, response);
}
request.setAttribute(CsrfToken.class.getName(), csrfToken);
request.setAttribute(csrfToken.getParameterName(), csrfToken);
if (!this.requireCsrfProtectionMatcher.matches(request)) {
filterChain.doFilter(request, response);
return;
}
String actualToken = request.getHeader(csrfToken.getHeaderName());
if (actualToken == null) {
actualToken = request.getParameter(csrfToken.getParameterName());
}
if (!csrfToken.getToken().equals(actualToken)) {
if (this.logger.isDebugEnabled()) {
this.logger.debug("Invalid CSRF token found for "
+ UrlUtils.buildFullRequestUrl(request));
}
if (missingToken) {
this.accessDeniedHandler.handle(request, response,
new MissingCsrfTokenException(actualToken));
}
else {
this.accessDeniedHandler.handle(request, response,
new InvalidCsrfTokenException(csrfToken, actualToken));
}
return;
}
filterChain.doFilter(request, response);
}
위의 필터는 요청이 들어올 때마다 CSRF 토큰을 검사해주는 필터이다.
위의 requset.setAttribute(CsrfTokec.class.getName(), csrfToken); 에서 프로젝트가 생성한 csrf 토큰을
set 해준다.
- 로그인 Form
그리고 로그인 폼에서 보낸 csrf 토큰 값을 위의 소스에서 actualToken 값으로 받아온다.
그러면 우리의 csrfToken 값과 actualToken 값이 일치하는지 여부를 확인할 수 있다.
즉, 내가 작성한 Form 에서 요청한 동작이 맞구나 란 것을 알 수 있다.
?? ( input hidden 에 있는 csrf 밸류를 다른 웹에서 보내면 어떻게 되는거지? )
- 위의 내용을 securityConfig 메소드에 넣으면 csrf 를 사용하지 않을 수 있다.
http.csrf().disable();
# LogoutFilter
- 여러 LogoutHandler 를 사용하여 로그아웃 시 필요한 처리를 하며, 이후에는 LogoutSuccessHandler 를 사용하여
로그아웃 처리를 한다.
- CsrfLogoutHandler
- SecurityContextLogoutHandler
- SimplUrlLogoutSuccessHandler
- LogoutFilter 는 크게 LogoutHandler 와 LogoutSuccessHandler 로 나뉘어진다.
LogoutHandler 는 Composit Handler 로 여러 Handler 가 결합된 것이고,
LogoutSuccessHandler 는 로그아웃이 성공했을 때의 처리를 컨트롤 한다.
/logout 으로 요청이 들어오면 DefaultLogoutPageGeneratingFilter 를 통해 기본 시큐리티에
내장되어 있는 로그아웃 페이지로 이동하게 되고,
로그아웃 Request 가 들어오면 밑의 로직의 requiresLogout 안으로 로직이 실행된다.
- LogoutFilter
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (logger.isDebugEnabled()) {
logger.debug("Logging out user '" + auth
+ "' and transferring to logout destination");
}
this.handler.logout(request, response, auth);
logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
즉, 이 필터는 모든 Request 마다 실행되지만, requiresLogout 조건을 충족하지 않으면 chain.doFilter 로
넘어가게 된다.
이 때 인증 받은 auth 를 securityContextHolder 를 통해 불러오고, 그 객체를 handler.logout 메소드를 실행한다.
- CompositeLogoutHandler
public final class CompositeLogoutHandler implements LogoutHandler {
private final List<LogoutHandler> logoutHandlers;
public CompositeLogoutHandler(LogoutHandler... logoutHandlers) {
Assert.notEmpty(logoutHandlers, "LogoutHandlers are required");
this.logoutHandlers = Arrays.asList(logoutHandlers);
}
public CompositeLogoutHandler(List<LogoutHandler> logoutHandlers) {
Assert.notEmpty(logoutHandlers, "LogoutHandlers are required");
this.logoutHandlers = logoutHandlers;
}
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
for (LogoutHandler handler : this.logoutHandlers) {
handler.logout(request, response, authentication);
}
}
}
- SecurityConfig
http.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/")
.logoutRequestMatcher()
.invalidateHttpSession(true)
.deleteCookies()
.addLogoutHandler()
.logoutSuccessHandler();
시큐리티 설정 메소드에 로그아웃과 관련된 설정을 할 수 있다.
http.logout().invalidateHttpSession 은 로그아웃 시 세션을 없애는 여부에 대한 설정
http.logout().deleteCookies() 는 쿠키를 사용한 로그인을 했을 시 그에 대한 로그아웃 설정
.addLogoutHandler(), logoutSuccessHandler 는 로그아웃 핸들러를 커스텀해서 주입함으로 써 사용하는 설정이다.
'SpringFrameWork > SpringSecurity' 카테고리의 다른 글
[SpringSecurity #12] Security Custom Filter 만들기 (0) | 2020.10.04 |
---|---|
[SpringSecurity #11] Security Auth 관련 Filter (0) | 2020.10.03 |
[SpringSecurity #09] Spring Security Filter (0) | 2020.10.02 |
[SpringSecurity #08] 스프링시큐리티 아키텍처 정리 (0) | 2020.10.02 |
[SpringSecurity #07] AccessDecisionManager (0) | 2020.09.28 |