Skip to content

Latest commit

 

History

History
707 lines (604 loc) · 32.6 KB

2.security-basic.md

File metadata and controls

707 lines (604 loc) · 32.6 KB

2. 스프링 시큐리티 기본 API & Filter 이해

2.1. 인증 API

2.1.1. 프로젝트 구성 및 의존성 추가

  • 서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어진다

    • 별도의 설정이나 구현을 하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동함
    • 모든 요청은 인증이 되어야 자원에 접근이 가능하다
      1. 인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공한다
      2. 기본 로그인 페이지 제공한다
      3. 기본 계정 한 개 제공한다 – username : user / password : 랜덤 문자열
  • 개선할 사항

    • 계정 추가
    • 권한 추가
    • DB 연동
    • 기본적인 보안 기능 외에 시스템에서 필요로 하는 더 세부적이고 추가적인 보안기능이 필요

2.1.2. 사용자 정의 보안 기능 구현

  • WebSecurityConfigurerAdapter (핵심)

    • 웹 보안 기능 초기화 및 활성화
    • HttpSecurity 생성
      • 인증 & 인가 API 제공
  • WebSecurityConfigurerAdapter WebSecurityConfigurerAdapter

  • HttpSecurity HttpSecurity

  • SecurityConfig.java

  • Debug

- SecurityConfig Debug
    1. WebSecurityConfigurerAdapter 클래스의 기본 값 설정 디버깅

    2. HttpSecurity 기본 설정
        1) getHttp()
        2) configure()

2.1.3. HTTP Basic 인증

- Http는 자체적인 인증 관련 기능을 제공하며 HTTP 표준에 정의된 가장 단순한 인증 기법이다.
- 간단한 설정과 Stateless가 장점 - Session Cookie(JESSIONID) 사용하지 않음
- 보호차원 접근 시 서버가 클라이언트에게 401 UnAuthorized 응답과 함께 WWW-Authenticate header를 기술해서 인증 요구를 보냄
- Client Id: Password 값을 Base64로 Encoding한 문자열을 Authorization Header에 추가한 뒤 Server에게 Resource를 요청
    - Authorization: Basic cmVzdDpyZXN0
- ID, Password가 Base64로 Encoding되어 있어 ID, Password가 외부에 쉽게 노출되는 구조이기 때문에 SSL이나 TLS는 필수이다.

2.1.4. BasicAuthenticationFilter

  • RFC 7617

    • RFC 7617 Diagram
  • 인증 프로토콜과 헤더

- HTTP는 필요에 따라 고쳐 쓸 수 있는 제어 헤더를 통해, 다른 인증 프로토콜에 맞추어 확장할 수 있는 프레임워크를 제공한다.
- 헤더의 형식과 내용은 인증 프로토콜에 따라 달라진다.
- 인증 프로토콜은 HTTP 인증 헤더에 기술되어 있다.
- HTTP에는 "기본 인증"과 "DIGEST 인증"이 있다.
  • Basic Authentication 의 문제점
- 기본 인증의 보안 문제
    - 기본 인증은 단순하고 편리하지만 안심할 수 없다.
    - 기본인증은 악의적이지 않은 누군가가 의도하지 않게 리소스에 접근하는 것을 막는데 사용하거나 SSL 같은 암호 기술과 혼용 해야 한다.
    - base64 인코딩된 값은 쉽게 디코딩할 수 있다.
    - 제 3자는 사용자 이름과 비밀번호를 캡처하여 악용할 수 있다.
    - Proxy나 중개자가 개입하는 경우 정상적인 동작을 보장할 수 없다.
    - 기본인증은 가짜 서버의 위장에 취약하다.

2.1.5. Form 인증

  • 인증 API
    • formLogin() 사용 시 FormLoginConfigurer 설정 등록
    • FormLoginConfigurer 설정 시 UserPasswordAuthenticationFilter 등록
- 인증
    - formLogin()
    - loginPage("/loginPage")
        - 스프링 시큐리티의 기본 로그인 페이지를 설정
    - defaultSuccessUrl("/")
        - 로그인 성공 시 redirect 되는 URL 설정
    - usernameParameter("userId")
        - 로그인 기본페이지의 username 의 name 값을 설정
    - passwordParameter("passwd")
        - 로그인 기본페이지의 password 의 name 값을 설정
    - loginProcessingUrl("/login_proc")
        - ?
    - successHandler(new AuthenticationSuccessHandler() {})
        - 로그인 성공 시 실행되는 Handler
    - failureHandler(new AuthenticationFailureHandler() {})
        - 로그인 실패 시  실행되는 Handler
    - permitAll()
        - formLogin에 접근하기 위한 url의 접근 권한 all 설정
/* 인가 정책 */
http
        .authorizeRequests()
        .anyRequest().authenticated();

/* 인증 정책 */
http
        .formLogin()
        .defaultSuccessUrl("/")
        .failureUrl("/login")
        .usernameParameter("userId")
        .passwordParameter("passwd")
        .loginProcessingUrl("/login_proc")
        .successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                System.out.println("authentication : " + authentication.getName());
                response.sendRedirect("/");
            }
        })
        .failureHandler(new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                System.out.println("exception" + exception.getMessage());
                response.sendRedirect("/login");
            }
        })
        .permitAll();

2.1.6. UserPasswordAuthenticationFilter

  • Authentication
    • 인증을 처리하는 필터

UsernamePasswordAuthenticationFilter

2.1.7. Logout, LogoutFilter

  • LogoutFilter Flow

LogoutFilter

  • Logout
    • request("/logout")
    • 세션 무효화
    • 인증 토큰 삭제
    • 쿠키정보 삭제,
    • 로그인 페이지로 리다이렉트
// 인가 정책
http
        .authorizeRequests()
        .anyRequest().authenticated();
// 인증 정책
http
        .formLogin()
        .defaultSuccessUrl("/")
        .failureUrl("/login")
        .usernameParameter("userId")
        .passwordParameter("passwd")
        .loginProcessingUrl("/login_proc")

        /* AuthenticationSuccessHandler */
        .successHandler(new AuthenticationSuccessHandler() {
            @Override
            public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                System.out.println("authentication : " + authentication.getName());
                response.sendRedirect("/");
            }
        })

        /* AuthenticationFailureHandler */
        .failureHandler(new AuthenticationFailureHandler() {
            @Override
            public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
                System.out.println("exception" + exception.getMessage());
                response.sendRedirect("/login");
            }
        })
        .permitAll();
http
        .logout()
        /* GET, POST 가능하나 POST가 Default */
        .logoutUrl("/logout")
        .logoutSuccessUrl("/login")

        /* LogoutHandler */
        .addLogoutHandler(new LogoutHandler() {
            @Override
            public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
                HttpSession session = request.getSession();
                session.invalidate();
            }
        })

        /* LogoutSuccessHandler */
        .logoutSuccessHandler(new LogoutSuccessHandler() {
            @Override
            public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                response.sendRedirect("/login");
            }
        })
        /* 쿠키명 */
        .deleteCookies("remember-me")
        ;

2.1.8. Remember Me 인증

  • RememberMe 인증이란 대체 뭐야 ?

    • form 인증 방식에 추가로 사용할 수 있는 기능
      • 세션이 유실 또는 웹 브라우저가 종료된 이후에도 어플리케이션이 사용자를 기억하는 기능
  • remember-me cookie 란?

    • 발급 조건
      • remember-me 기능을 활성화시킨 상태에서 인증 프로세스를 처리 시 서버가 remember-me 쿠키를 응답 헤더에 실어서 보내게 된다.
    • 용도
      • 사용자는 remember-me 쿠키의 만료일 전까지 세션 유지가 가능하다.
      • 세션이 유실되는 경우에도 remember-me 쿠키가 존재하는 경우 쿠키 안에 사용자 정보를 통해 토큰 기반 인증하여 토큰 검증 시 로그인 유지가 가능
    • 쿠키에 존재하는 remember-me 형태

RememberMe 생성 확인

  • 라이프 사이클

    • 인증 성공
      • remember-me 쿠키 설정
    • 인증 실패
      • 쿠키 존재 시 쿠키 무효화
    • 로그 아웃
      • 쿠키 존재 시 쿠키 무효화
  • 로그인 시 세션 생성 플로우

  • JSESSIONID 삭제 시 remember-me 쿠키가 인증 토큰을 재발급 하는 플로우

RememberMe Flow

/* 인증 API */
http
    .authorizeRequests()
    .anyRequest().authenticated();

http
    .formLogin();

http
    /* 인가 API */
    .rememberMe()
    .rememberMeParameter("remember") // 기본 파라미터 명 -> "remember-me"
    .tokenValiditySeconds(3600) // 만료시간 default 14일
    .alwaysRemember(true) // remember me  기능이 활성화되지 않아도 항상 실행
    /* user 계정 확인 메서드 */
    .userDetailsService(userDetailsService)
    ;

2.1.9. RememberMeAuthenticationFilter

  • remember-me 발급 플로우

RememberMeFilter Flow

  • Remember-Me가 존재하는 경우 JSESSIONID가 유실되는 경우에도 인증처리가 가능
    • SpringSecurity의 RememberMeAuthenticationFilter 에서 request Header에 Cookie를 확인하여 userId와 userPw값으로 user 객체를 얻어 인증처리 해버림

2.1.10. AnonymousAuthenticationFilter

  • 사용자의 인증 여부를 판단하는 필터

  • 인증 받지 않은 사용자에게 Authentication == null 이 아닌 '익명 사용자용 인증객체' 를 담도록 하는 필터

  • 접근한 유저 정보의 인증정보를 null 이 아닌 익명 사용자로 확인 가능

    • isAnonymous() 인 경우 loginPage로 접근할 수 있도록 유도 가능
    • isAuthenticated() 인 경우 logoutPage로 접근 할 수 있도록 유도 가능
  • AnonymousAuthenticationFilter 에서 인증객체 확인 및 익명 객체 설정 기능

  • 위 처리를 하게 되는 경우 AbstractSecurityInterceptor 에서 익명객체를 확인하여 인가 처리를 하게 됨

  • 익명 객체 flow

AnonymousFilter

/* 인증 API */
http
    .authorizeRequests()
    .anyRequest().authenticated();

http
    .formLogin();

http
    /* 인가 API */
    .anonymous()
    ;

2.1.11. 동시적 세션 제어 전략

  • 동일 계정으로 접속이 허용되는 최대 세션 수를 제한
    • 이전 세션 종료 방식
    • 이후 세션 접근 거부 방식
http
    .authorizeRequests()
    .anyRequest().authenticated();

http
    .formLogin();

http
    /* 세션 관리 */
    .sessionManagement()
        .maximumSessions(1) // 최대 허용 가능 세션 수 (-1: 무제한)
        .maxSessionsPreventsLogin(true) // 동시 로그인 차단 (default: false)
        .expiredUrl("/login") // 세션 만료 시 페이지 이동

    /* 세션 관리 */
    .sessionManagement()
        .invalidSessionUrl("/login") // 세션 유효하지 않는 경우 페이지 이동

2.1.12. 세션 고정 보호

  • 인증 할 때마다 세션 쿠키를 새로 발급하여 공격자의 쿠키 조작을 방지
  • 사용자의 쿠키를 공격자의 쿠키로 인증처리 한 뒤 공격자가 해당 쿠키로 인증하는 세션 고정 공격을 방지 하기 위한 설정
/* 세션 고정 보호 설정 */
.sessionFixation()

    /* 세센 고정 보호를 사용하지 않는 경우 위와 같은 문제가 발생할 수 있음 */
     //.none()

    /* Servlet 3.1 미만 버전 일 때 default 값 */
    /* 세션을 생성하되 기존 세션 정보가 있다면 사용할 수 있음 */
     //.migrateSession()
    
    /* 새로운 인증 세션으로 변경하는 방법 */
     //.newSession()

    /* Servlet 3.1 이상 버전 일 때 default 값 */
    /* 세션을 생성하되 기존 세션 정보가 있다면 사용할 수 있음 */
    .changeSessionId()

2.1.13. 세션 정책

  • 세션 정책
    • Always
    • if_Required
    • Never
    • Stateless

2.1.14. SessionManagementFilter

  1. 세션 관리

    • 인증 시 사용자의 세션 정보를 등록, 조회, 삭제 등의 세션 이력을 관리
  2. 동시적 세션 제어

    • 동일 계정으로 접속이 허용되는 최대 세션 수를 제한
    • ConcurrentSessionControlAuthenticationStrategy
  3. 세션 고정 보호

    • 인증 할 때마다 세션 쿠키를 새로 발급하여 공격자의 쿠키 조작을 방지
    • ChangeSessionIdAuthenticationStrategy
  4. 세션 생성 정책

    • Always, if_Required, Never, Stateless

2.1.15. ConcurrentSessionFilter

  • 매 요청마다 현재 사용자의 세션 만료 여부 체크

  • 세션이 만료되었을 경우 즉시 만료 처리

  • session.isExpired() == true

    • 로그아웃 처리
    • 즉시 오류 페이지 응답
  • check point

    • 동시적 세션 제어를 처리하기위해 ConcurrentSessionFilter와 SessionManagementFilter의 처리과정을 살펴볼 것

ConcurrentSessionFilter

  • 동시적 세션 제어 전략 시나리오 Flow

SessionManagementFilter

  • 시나리오

    1. Session의 등록
    2. 동일 계정 접속 시 세션 처리 방식
      • 인증 실패 전략
      • 세션 만료 전략
  • 시나리오 디버깅 하기

    • 서버 접근 시 최대 세션 수에 초과되는 경우
      1. 이후 세션 접근을 거부하는 방식

        • 첫 번째 사용자 로그인
          • CompositeSessionAuthenticationStrategy
            • SessionAuthenticationStrategy의 구현체가 돌면서 처리
          • ConcurrentSessionControlAuthenticationStrategy
            • 93 Line: final List sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
              • 해당 계정으로 저장된 세션의 개수 확인
            • 97 Line: int allowedSessions = getMaximumSessionsForThisUser(authentication);
              • 기존에 설정된 세션 정보의 개수를 확인
            • 99 Line: if (sessionCount < allowedSessions) { return; }
              • 설정된 세션 개수보다 현 세션 개수가 적은 경우 (size 0) 리턴
          • AbstractSessionFixationProtectionStrategy
            • 96 Line: onSessionChange(originalSessionId, session, authentication);
              • 현재 세션 아이디와 새로운 세션아이 변경
              • 127 Line: applicationEventPublisher.publishEvent(new SessionFixationProtectionEvent(auth, originalSessionId, newSession.getId()));
          • RegisterSessionAuthenticationStrategy
            • 66 Line: sessionRegistry.registerNewSession(request.getSession().getId(),authentication.getPrincipal());
              • 변경된 세션을 등록
            • SessionRegistryImpl
              • 119 Line: registerNewSession(String sessionId, Object principal)
                • 변경된 세션을 등록하는 메서드
              • 132 Line: sessionIds.put(sessionId, new SessionInformation(principal, sessionId, new Date()));
              • 135 Line: principals.compute(principal, (key, sessionsUsedByPrincipal) -> { ... }
                • 변경된 세션 정보를 등록
        • 두 번째 사용자 로그인
          • ConcurrentSessionControlAuthenticationStrategy
            • 93 Line: final List sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
              • 해당 계정으로 저장된 세션의 개수 확인
            • 97 Line: int allowedSessions = getMaximumSessionsForThisUser(authentication);
              • 기존에 설정된 세션 정보의 개수를 확인
            • 125 Line: allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
              • 허용된 세션을 초과 로직 수행
              • maxSessionsPreventsLogin(true) 설정 시
                • 154 Line: if (exceptionIfMaximumExceeded || (sessions == null)) { throw new SessionAuthenticationException(..); }
                  • 최대 세션 수가 초과되는경우 인증 싫패 처리
              • 162 ~ 167 Line: ... session.expireNow();
                • 세션 expire 처리
          • AbstractAuthenticationProcessingFilter
            • 230 Line: unsuccessfulAuthentication(request, response, failed);
              • 인증 실패 처리 로직 실행
              • 342 Line: SecurityContextHolder.clearContext();
                • 인증 객체 삭제처리
              • 350 Line: rememberMeServices.loginFail(request, response);
              • 352 Line: failureHandler.onAuthenticationFailure(request, response, failed);
                • 실패 처리
      2. 이전 세션 expired 처리 방식

        • 두 번째 사용자 로그인
          • ConcurrentSessionControlAuthenticationStrategy
            • 93 Line: final List sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
              • 해당 계정으로 저장된 세션의 개수 확인
            • 97 Line: int allowedSessions = getMaximumSessionsForThisUser(authentication);
              • 기존에 설정된 세션 정보의 개수를 확인
            • 125 Line: allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
              • 162 ~ 167 Line: ... session.expireNow();
                • 세션 expire 처리
        • 첫 번째 사용자 페이지 이동 시
          • ConcurrentSessionFilter
            • 130 Line: SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
              • 세션 정보를 가져옴
            • 134 Line: if (info.isExpired()) {
              • 세션 expired 확인
            • 140 Line: doLogout(request, response);
              • 로그아웃 처리

2.2. 인가 API

2.2.1. 권한 설정

  • 선언적 방식

    • URL
      • 경로에 대한 권한정보를 통한 인가 처리 등록
        • hasRole("ROLE"), access("hasRole('ADMIN') or hasRole('SYS')")
        • 설정 시 구체적인 경로가 먼저오고 그 보다 큰 범위의 경로를 뒤에 설정하도록 해야 한다.
    • Method
  • 동적 방식

    • URL
    • Method

2.2.2. 표현식

  • HttpSecurity 클래스 인가 API 리스트

    1. authenticated()
      • 인증된 사용자의 접근을 허용
    2. fullyAuthenticated()
      • 인증된 사용자의 접근을 허용, rememberMe 인증 제외
    3. permitAll()
      • 무조건 접근을 허용
    4. denyAll()
      • 무조건 접근을 허용하지 않음
    5. anonymous()
      • 익명사용자의 접근을 허용
    6. rememberMe()
      • remember-me cookie 를 통해 인증된 사용자의 접근을 허용
    7. access(String)
      • 주어진 SpEL 표현식의 평가 결과가 true이면 접근을 허용
    8. hasRole(String)
      • 사용자가 주어진 역할이 있다면 접근을 허용
    9. hasAuthority(String)
      • 사용자가 주어진 권한이 있다면 접근을 허용
    10. hasAnyRole(String...)
      • 사용자가 주어진 권한이 있다면 접근을 허용
    11. hasAnyAuthority(String...)
      • 사용자가 주어진 권한 중 어떤 것이라도 있다면 접근을 허용
    12. hasIpAddress(String)
      • 주어진 IP로부터 요청이 왔다면 접근을 허용
  • 유저 권한 설정

    • 권한에 대한 위계가 있지 않음
    • 코드에 권한 설정이되어 있어 실시간으로 수정할 수 없으므로 자원관리에 비효율적
/* 인가 API 설정 테스트를 위한 InMemory 계정 등록 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.inMemoryAuthentication().withUser("user").password("{noop}1234").roles("USER");
    auth.inMemoryAuthentication().withUser("sys").password("{noop}1234").roles("SYS");
    auth.inMemoryAuthentication().withUser("admin").password("{noop}1234").roles("ADMIN");
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    // 인가 정책
    http
            /* 모든 경로에 대해서 권한 요청 설정 */
            .authorizeRequests()
                /* /user 경로의 request가 들어오는 경우 인가 처리를 통해 USER role을 가진 사용자에 대해서 resource를 제공하겠다는 설정 */
                .antMatchers("/user").hasRole("USER")
                .antMatchers("/admin/pay").hasRole("ADMIN")
                .antMatchers("/admin/**").access("hasRole('ADMIN') or hasRole('SYS')")

            .anyRequest()
            .authenticated()
    ;

2.2.3 인가 프로세스를 돕는 클래스들

ExceptionTranslationFilter

  • ExceptionTranslationFilter

    • 인가 작업 시 발생하는 예외를 처리하는 필터
    • 마지막에서 두번째 위치에 존재하는 필터
  • AuthenticationException

    • 인증 예외 처리
      1. AuthenticationEntryPoint 호출
        • 로그인 페이지 이동, 401 오류코드 전달
      2. 인증 예외가 발생하기 전의 요청 정볼르 저장
        • RequestCache: 사용자의 이전 request 정보를 세션에 저장하고 이를 꺼내오는 캐시 메커니즘
        • SavedRequest: 사용자가 요청했던 request 파라미터 값들, 그 당시의 헤더 값들을 저장
  • AccessDeniedException

    • 인가 예외 처리
      • AccessDeniedHandler 에서 예외 처리하도록 제공
  • RequestCacheAwareFilter

  • configure 설정 코드

/* 인가 정책 */
http
    /* 모든 경로에 대해서 권한 요청 설정 */
    .authorizeRequests()
    ...
    ;

/* formLogin 설정 */
http
    .formLogin()
    /* 캐시 필터 구현 시 formLogin 에 추가되는 handler */
    .successHandler(new AuthenticationSuccessHandler() {
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
            RequestCache requestCache = new HttpSessionRequestCache();
            SavedRequest savedRequest = requestCache.getRequest(request, response);
            String redirectUrl = savedRequest.getRedirectUrl();
            response.sendRedirect(redirectUrl);
        }
    })
    ;

/* 인가 예외시 실행 handler 설정 */
http
    .exceptionHandling()
    /* 인가 프로세스 전 요청(request) 값을 갖고 있는 EntryPoint 클래스 (시나리오 3번 수행 시 주석처리 필요) */
    .authenticationEntryPoint(new AuthenticationEntryPoint() {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.sendRedirect("/login");
        }
    })
    /* 접근 거부 예외 처리 handler*/
    .accessDeniedHandler(new AccessDeniedHandler() {
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.sendRedirect("/denied");
        }
    })
    ;
  • 시나리오
    1. resource에 접근 하려는 익명의 사용자 발생 시 flow

      • ExceptionTranslationFilter
        • 177 Line: if (exception instanceof AuthenticationException) {
          • 인증을 받지 않은 사용자가 접근 시 AuthenticationException 처리
        • 209 Line: SecurityContextHolder.getContext().setAuthentication(null);
        • 210 Line: requestCache.saveRequest(request, response);
          • HttpSessionRequestCache
            • 51 Line: public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
              • 60 Line: request.getSession().setAttribute(this.sessionAttrName, savedRequest);
              • 현재 request 값을 저장하여 설정 정보를 제공
        • SecurityConfig
          • 212 Line: authenticationEntryPoint.commence(request, response, reason);
            • 현재 request 값을 가지고 redirect 하도록 구현해 놓은 익명 클래스로 이동
    2. 로그인 성공 후 (인가 실패 시) 인가 예외 발생 처리 flow

      • 177 Line: else if (exception instanceof AccessDeniedException) {
        • 인증을 받지 않은 사용자가 접근 시 AccessDeniedHandler로 가지않고 AccessDeniedException 발생
      • 178 Line: Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        • 인증 정보 확인
      • 179 Line: if (authenticationTrustResolver.isAnonymous(authentication) || authenticationTrustResolver.isRememberMe(authentication)) {
        • 익명 클래스 또는 remember-me에 저장된 인증 정보가 아닌경우
      • 198 Line: accessDeniedHandler.handle(request, response,(AccessDeniedException) exception);
        • 접근 권한 거부로 인해 '/denied' 페이지로 이동하는 로직을 구현해 놓은 익명 클래스로 이동
    3. 인증 없이 '/' 페이지에 접근, redirect 페이지에서 다시 인증 하는 경우 cache 정보를 통해 '/' 페이지에 접근하는 flow

      • 인증 없이 '/' 페이지에 접근
        • RequestCacheAwareFilter
          • 60 Line: HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest((HttpServletRequest) request, (HttpServletResponse) response);
            • request 값이 존재하는지 확인, 값이 존재하는 경우 해당 값을 다음 필터로 넘겨주는 역할
          • HttpSessionRequestCache
            • 90 Line: public HttpServletRequest getMatchingRequest(HttpServletRequest request,HttpServletResponse response) {
            • 69 Line: public SavedRequest getRequest(HttpServletRequest currentRequest, HttpServletResponse response) {
              • 71 Line: HttpSession session = currentRequest.getSession(false);
                • 세션 정보를 가져오려고하나 값이 없음
          • ExceptionTranslationFilter
            • 177 Line: else if (exception instanceof AuthenticationException) {
              • 다시 인증 예외 발생
            • 204 Line: protected void sendStartAuthentication(... ) throws ServletException, IOException {
              • 210 Line: requestCache.saveRequest(request, response);
                • HttpSessionRequestCache
                  • 60 Line: request.getSession().setAttribute(this.sessionAttrName, savedRequest);
                    • session정보에 해당하는 사용자의 request를 저장
      • redirect 페이지에서 다시 인증하여 저장된 cache를 통해 '/' 페이지에 접근
        • SecurityConfig
          • RequestCache requestCache = new HttpSessionRequestCache();

          • SavedRequest savedRequest = requestCache.getRequest(request, response);

          • String redirectUrl = savedRequest.getRedirectUrl();

          • response.sendRedirect(redirectUrl);

            • 인증 시 이전에 가지고 있던 request정보를 통해 '/' 로 이동
          • HttpSessionRequestCache

            • 90 Line: public HttpServletRequest getMatchingRequest(HttpServletRequest request,HttpServletResponse respo
              • ExceptionTranslationFilter의 210 Line에서 requestCache.saveRequest(request, response); 를 통해 저장된 정보를 사용가능 상태
            • 73 ~ 75 Line: if (session != null) { return (SavedRequest) session.getAttribute(this.sessionAttrName); }
              • 저장된 세션에 해당하는 request 정보를 가져옴
            • 92 Line: SavedRequest saved = getRequest(request, response);
              • saved라는 변수를 통해 request 정보를 호출 할 수 있음
            • 101 Line: return new SavedRequestAwareWrapper(saved, request);
              • 캐싱된 데이터를 다음 필터로 전달하는 역할을 request 객체가 아닌 SavedRequestAwareWrapper라는 객체가 함

2.3. CsrfFilter

  • 기본적으로 설정되어 있는 springSecurityFilterChain 에 있는 csrf 의 존재 이유와 수행 방식

    • csrf ?
      • csrf 원리
  • 시나리오

    1. csrfToken 없이 '/' 접근 시 csrfFilter 처리 Flow

      • CsrfFilter
        • 107 Line: CsrfToken csrfToken = this.tokenRepository.loadToken(request);

          • request에 해당하는 csrfToken을 조회
        • 109 Line: final boolean missingToken = csrfToken == null;

          • csrfToken 값이 존재하지 않으므로 ture 값 저장
        • 110 Line: csrfToken = this.tokenRepository.generateToken(request);

        • 111 Line: this.tokenRepository.saveToken(csrfToken, request, response);

          • 서버에 request에 해당하는 token이 없으므로 새로 생성 및 저장
        • 113 Line: request.setAttribute(CsrfToken.class.getName(), csrfToken);

        • 114 Line: request.setAttribute(csrfToken.getParameterName(), csrfToken);

          • 생성된 csrfToken을 request에 저장
          • csrfToken.getParameterName() -> "_csrf"
        • 121 Line: String actualToken = request.getHeader(csrfToken.getHeaderName());

          • csrfToken의 헤더 값에 해당하는 request의 헤더 값을 호출하여 actualToken으로 사용
          • csrfToken.getHeaderName() -> "X-CSRF-TOKEN"
        • 125 Line: if (!csrfToken.getToken().equals(actualToken)) {

          • 서버의 token과 생성된 토큰 비교하여 같지 않으면 Invalid csrf 예외처리
        • 130 Line: if (missingToken) { ... }

          • accessDeniedHandler로 접근 불가 예외처리
    2. csrfToken 헤더에 담아서 '/' 접근 시 csrfFilter 처리 Flow

      • X-CSRF-TOKEN: 4017f008-d6df-4933-97fa-96afd01d7389
      • CsrfFilter
        • 107 Line: CsrfToken csrfToken = this.tokenRepository.loadToken(request);
          • request에 해당하는 csrfToken을 조회
        • 109 Line: final boolean missingToken = csrfToken == null;
          • 서버에 저장된 csrfToken 값이 존재하므로 false 값 저장
        • 113 Line: request.setAttribute(CsrfToken.class.getName(), csrfToken);
        • 114 Line: request.setAttribute(csrfToken.getParameterName(), csrfToken);
          • 생성된 csrfToken을 request에 저장
          • csrfToken.getParameterName() -> "_csrf"
        • 121 Line: String actualToken = request.getHeader(csrfToken.getHeaderName());
          • request에 저장된 csrfToken의 값을 조회
          • csrfToken.getHeaderName() -> "X-CSRF-TOKEN"
        • 125 Line: if (!csrfToken.getToken().equals(actualToken)) {
          • 서버의 token과 생성된 토큰 비교하여 같지 않으면 Invalid csrf 예외처리
        • 141 Line: filterChain.doFilter(request, response);
          • 다음 필터로 이동
      • BasicController
        • @PostMapping("/") public String csrf() { return "Home"; }
          • csrfFilter의 작업을 마치고 requestMapping된 컨트롤러 로직 수행