ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Spring] RequestCacheAwareFilter, ChannelProcessingFilter 사용하기
    Backend/Spring 2022. 9. 2. 17:49

    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()
      ;
    }

     

    댓글

Designed by Tistory.