Backend/Spring

[Spring] RequestCacheAwareFilter, ChannelProcessingFilter 사용하기

mirae.kwak 2022. 9. 2. 17:49
728x90

RequestCacheAwareFilter

FilterChainProxy를 구성하는 주요 Security Filter 중 하나로 로그인 성공 이후 가로채어진 사용자의 원래 요청으로 이동하기 위해 사용된다.

 

익명의 사용자(로그인 안한 사용자)가 보호받는 리소스 페이지에 접근하는 경우

  • 접근 권한이 없기 때문에 AccessDecisionManager에서 접근 거부 예외가 발생함
  • ExceptionTranslationFilter 접근 거부 예외를 처리함
    • 현재 사용자가 익명 사용자라면, 보호받는 리소스로의 접근을 캐시 처리하고, 로그인 페이지로 이동시킴
    • 로그인이 완료되면 원래 접근하려 했던 캐싱 페이지로 접근할 수 있게 함
private void handleAccessDeniedException(HttpServletRequest request, HttpServletResponse response,
		FilterChain chain, AccessDeniedException exception) throws ServletException, IOException {
	Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
	boolean isAnonymous = this.authenticationTrustResolver.isAnonymous(authentication);
	if (isAnonymous || this.authenticationTrustResolver.isRememberMe(authentication)) {
		if (logger.isTraceEnabled()) {
			logger.trace(LogMessage.format("Sending %s to authentication entry point since access is denied",
					authentication), exception);
		}
		sendStartAuthentication(request, response, chain,
				new InsufficientAuthenticationException(
						this.messages.getMessage("ExceptionTranslationFilter.insufficientAuthentication",
								"Full authentication is required to access this resource")));
	}
	else {
		if (logger.isTraceEnabled()) {
			logger.trace(
					LogMessage.format("Sending %s to access denied handler since access is denied", authentication),
					exception);
		}
		this.accessDeniedHandler.handle(request, response, exception);
	}
}

protected void sendStartAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
		AuthenticationException reason) throws ServletException, IOException {
	// SEC-112: Clear the SecurityContextHolder's Authentication, as the
	// existing Authentication is no longer considered valid
	SecurityContextHolder.getContext().setAuthentication(null);
	this.requestCache.saveRequest(request, response);
	this.authenticationEntryPoint.commence(request, response, reason);
}
  • RequestCacheAwareFilter를 통해 위에서 살펴본 캐시된 요청을 처리할 수 있음
    • 캐시된 요청이 있다면 캐시된 요청을 처리하고, 캐시된 요청이 없다면 현재 요청을 처리함
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
		throws IOException, ServletException {
	HttpServletRequest wrappedSavedRequest = this.requestCache.getMatchingRequest((HttpServletRequest) request,
			(HttpServletResponse) response);
	chain.doFilter((wrappedSavedRequest != null) ? wrappedSavedRequest : request, response);
}

 

ChannelProcessingFilter

전송 레이어 보안을 위해 SSL 인증서를 생성하고, 이를 Spring Boot 웹 어플리케이션에 적용한다. 이를 통해 웹 어플리케이션은 HTTPS 프로토콜을 통해 서비스 된다.

 

HTTP와 HTTPS

HTTP(Hyper Text Transfer Protocol)

  • 인터넷 상에서 데이터를 주고 받기 위한 프로토콜
  • 클라이언트와 서버가 주고 받는 데이터는 암호화되어 있지 않음
  • 따라서 악의적인 데이터 감청, 데이터 변조의 가능성이 있음

HTTPS(Hyper Text Transfer Protocol Secure)

  • HTTP 프로토콜의 암호화 버전
  • 클라이언트와 서버가 주고받는 모든 데이터는 암호화되어 있음
  • 데이터 암호화를 위해 SSL(Secure Sockets Layer)을 사용
    • SSL은 Netscape가 개발했으며 SSL 3.0부터 TLS라는 이름으로 변경되었다. 일반적으로 SSL, TLS은 같은 의미를 지닌다. 그러나 SSL이란 용어가 더 많이 사용된다.
  • SSL 암호화를 위해 SSL 인증서가 필요함
    • 서버는 SSL인증서를 클라이언트에 전달함
    • 클라이언트는 서버가 전달한 SSL인증서를 검증하고, 신뢰할 수 있는 서버인지 확인함
    • 신뢰할 수 있는 서버라면 SSL 인증서의 공개키를 이용해 실제 데이터 암호화에 사용될 암호화키를 암호화하여 서버에 전달함
      • 실제 데이터 암복호화하는 대칭키 방식
      • 서버와 클라이언트 사이의 대칭키 공유를 위해 RSA암호화를 사용함

 

SSL 인증서 생성

keytool 도구를 이용해 임의로 SSL인증서를 생성할 수 있음 (keytool은 Java 설치 경로 bin 디렉토리 아래에 위치함)

물론 실제 서비스에는 사용할 수 없으며, 어디까지나 로컬 테스트 용도로만 활용해야함

 

keystore 만들기

keytool -genkey -alias [keystore 별칭] -keyalg RSA -storetype PKCS12 -keystore [keystore 파일]
$ keytool -genkey -alias prgrms_keystore -keyalg RSA -storetype PKCS12 -keystore prgrms_keystore.p12
키 저장소 비밀번호 입력:
새 비밀번호 다시 입력:
이름과 성을 입력하십시오.
  [Unknown]:  localhost
조직 단위 이름을 입력하십시오.
  [Unknown]:  prgrms
조직 이름을 입력하십시오.
  [Unknown]:  prgrms
구/군/시 이름을 입력하십시오?
  [Unknown]:  Seoul
시/도 이름을 입력하십시오.
  [Unknown]:  Seoul
이 조직의 두 자리 국가 코드를 입력하십시오.
  [Unknown]:  KR
CN=localhost, OU=prgrms, O=prgrms, L=Seoul, ST=Seoul, C=KR이(가) 맞습니까?
  [아니오]:  y

 

keystore에서 인증서 추출하기

keytool -export -alias [keystore 별칭] -keystore [keystore 파일] -rfc -file [인증서 파일]
$ keytool -export -alias prgrms_keystore -keystore prgrms_keystore.p12 -rfc -file prgrms.cer
키 저장소 비밀번호 입력:
인증서가 <prgrms.cer> 파일에 저장되었습니다.

 

 

trust-store 만들기

keytool -import -alias [trust keystore 별칭] -file [인증서 파일] -keystore [trust keystore 파일]
$ keytool -import -alias prgrms_truststore -file prgrms.cer -keystore prgrms_truststore.p12
키 저장소 비밀번호 입력:
새 비밀번호 다시 입력:
소유자: CN=localhost, OU=prgrms, O=prgrms, L=Seoul, ST=Seoul, C=KR
발행자: CN=localhost, OU=prgrms, O=prgrms, L=Seoul, ST=Seoul, C=KR
일련 번호: 37c3cb60
적합한 시작 날짜: Sat Sep 03 13:23:01 KST 2022 종료 날짜: Fri Dec 02 13:23:01 KST 2022
인증서 지문:
         SHA1: 72:85:A2:F5:C6:A3:F7:96:E0:2B:69:C6:44:7A:0F:6B:3A:0B:19:6E
         SHA256: A1:47:86:97:DE:53:93:80:53:99:9C:01:D4:4B:3D:A1:8C:A8:00:17:02:84:4A:80:66:40:1D:41:82:F9:75:30
서명 알고리즘 이름: SHA256withRSA
주체 공용 키 알고리즘: 2048비트 RSA 키
버전: 3

확장:

#1: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 29 E6 C4 87 D9 A6 B5 CA   59 89 A3 DC 91 34 9E 0D  ).......Y....4..
0010: 0F 33 2E 5B                                        .3.[
]
]

이 인증서를 신뢰합니까? [아니오]:  y
인증서가 키 저장소에 추가되었습니다.

 

SSL 인증서 적용

prgrms_keystore.p12, prgrms_truststore.p12 2개 파일을 resources 디렉토리로 복사 후 application.xml 파일에 설정 추가

  • port를 443으로 변경 (https 프로토콜의 디폴트 포트가 443)
  • server.ssl 설정 추가
server:
  port: 443
  ssl:
    enabled: true
    key-alias: prgrms_keystore
    key-store: classpath:prgrms_keystore.p12
    key-store-password: prgrms123
    key-password: prgrms123
    trust-store: classpath:prgrms_truststore.p12
    trust-store-password: prgrms123

 

웹 어플리케이션을 시작하면 443(https) 포트를 통해 서비스가 기동하는 것을 로그로 확인할 수 있음

웹브라우저 주소에 https://localhost를 입력

  • 정상적으로 페이지 접근을 확인할 수 있음
    • 로그인 로그아웃도 정상적으로 수행가능
  • SSL 인증서가 유효하지 않기 때문에 경고가 뜸 (유효한 인증서라면 경고가 뜨지 않음)
    • 브라우저마다 경고가 다를 수 있음 (크롬의 경우 HTTPS 연결이 사용되지 않았다는 경고메시지 발생)

 

Spring Security 설정하기

ChannelProcessingFilter 설정을 통해 HTTPS 채널을 통해 처리해야 하는 웹 요청을 정의할 수 있음

  • FilterInvocationSecurityMetadataSource 클래스에 HTTPS 프로토콜로 처리해야 URL 정보가 담김
  • 실제적인 처리를 ChannelDecisionManager 클래스로 위임함

HttpSecurity 클래스를 통해 ChannelProcessingFilter 세부 설정을 할 수 있음

@Override
protected void configure(HttpSecurity http) throws Exception {
  http
       /**
       * HTTP 요청을 HTTPS 요청으로 리다이렉트
       */
      .requiresChannel()
        .anyRequest().requiresSecure()
  ;
}

 

728x90