개발자의 오르막

[SpringSecurity #11] Security Auth 관련 Filter 본문

SpringFrameWork/SpringSecurity

[SpringSecurity #11] Security Auth 관련 Filter

계단 2020. 10. 3. 15:03

# UsernamePasswordAuthenticationFilter

  : 폼 로그인을 처리하는 인증 필터

  - 사용자가 폼에 입력한 username 과 password로 Authentication을 만들고

    AuthenticationManager를 사용하여 인증을 시도한다.

  - AuthenticationManager (ProviderManager) 는 여러 AuthenticationProvider 를 사용하여

    인증을 시도하는데, 그 중에 DaoAuthenticationProvider 는 UserDetailsService를 사용하여

    UserDetails 정보를 가져와 사용자가 입력한 password 와 비교한다.

 

# DefaultLoginPageGeneratingFilter

- 기본 로그인 폼 페이지를 생성해주는 필터, Get /login 요청을 처리하는 필터

http.formLogin().loginPage("/login");

  이 설정을 추가하면 커스텀 한 로그인 페이지로 이동하기 때문에, DefaultLoginPageGeneratingFilter 를

  사용하지 않게 된다.

 

 

# BasicAuthenticationFilter

- Basic 인증

- 요청 헤더에 username 와 password를 실어 보내면 브라우저 또는 서버가 그 값을 읽어서 인증하는 방식

  예) Authorization : Basic QWxhZGRpb~ ( Base64 로 username, password 를 인코딩 )

- 보통 브라우저 기반 요청이 클라이언트의 요청을 처리할 때 자주 사용

- 보안에 취약하기 때문에 반드시 HTTPS를 사용할 것을 권장

 

 

# RequestCacheAwareFilter

- 현재 요청과 관련 있는 캐시된 요청이 있는지 찾아서 적용하는 필터

  캐시된 요청이 없다면, 현재 요청 처리

  캐시된 요청이 있다면, 해당 캐시된 요청 처리

 

 

# SecurityContextHolderAwareRequestFilter

- 시큐리티 관련 서블릿 API를 구현해주는 필터

  HttpServletRequest#authenticate(HttpServletResponse)

  HttpServletRequest#login(String, String)

  HttpServletRequest#logout()

  AsyncContext#start(Runnable)

 

 

# AnonymousAuthenticationFilter

- 현재 SecurityContext에 Authentication 이 null 이면 "익명 Authentication" 을 만들어

  넣어주고, null 이 아니면 아무일도 하지 않는다.

- 기본으로 만들어 사용할 "익명 Authentication" 객체를 설정할 수 있다.

http.anonymous()
.principal()
.authorities()
.key()

 

 

# SessionManagementFilter

- 세션 변조 방지 전략 설정 : sessionFixation

  세션 변조

  none

  newSession

  migrateSession (서블릿 3.0- 컨테이너 사용시 기본값)

  → 인증이 됐을 때 새로운 세션을 만들고, 기존 세션의 정보를 복사해오는 것

  changeSessionId (서블릿 3.1+ 컨테이너 사용시 기본값)

  → 스프링 부트를 사용하기 때문에 내장된 톰캣  서블릿 4.0 버전을 사용하고 있음

  → 세션 ID 만 바꾸기 때문에 migrateSession 보다 빠름

 

 

SecurityConfig 파일에서 세션 전략을 지정할 수 있다.

http.sessionManagement()
                .sessionFixation().changeSessionId();
        http.sessionManagement()
                .sessionFixation().migrateSession();
                

 

- 유효하지 않은 세션을 리다이렉트 시킬 URL 설정

http.sessionManagement()
                .sessionFixation()
                .changeSessionId()
                .invalidSessionUrl("/login");

 

 

- 동시성 제어 : maximumSessions

  추가 로그인을 막을지 여부 설정 (기본값, false)

  한 계정을 여러명이 사용할 수 있음 (기본값)

http.sessionManagement()
                .sessionFixation()
                .changeSessionId()
                .maximumSessions(1)
                .maxSessionsPreventsLogin(true);

  maximumSessions 를 1로 설정함으로써 동시 로그인 유저 수를 1명으로 제한 할 수 있다.

  그리고 maxSessionsPreventsLogin 은 false 인 경우 기존 로그인 세션을 만료 시키고 새로운 로그인 인증만

  사용하고, true 인 경우는 기존 로그인 세션만 유지하고, 새로운 로그인 세션을 막아주는 설정이다.

 

 

- 세션 생성 전략 : sessionCreationPolicy

  IF_REQUIRED (기본값, 필요하면 만든다

  NEVER (시큐리티에서는 생성 X, 기존의 세션을 활용함)

  STATELESS (세션을 사용하지 않음)

  ALWAYS (세션을 항상 사용)

 

http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS);

 

* RequestCacheAwareFilter 도 세션을 유지해야만 제대로 동작함

 

 

 


# ExceptionTranslationFilter

- FilterSecurityInterceptor (AccessDecisionManger, AffirmativeBased) 와 밀접한 관계가 있음

  → ExceptionTranslationFilter 가 FilterSecurityInterceptor 보다 이전에 있어야 함.

  → ExceptionTranslationFilter 가 FilterSecurityInterceptor 를 감싸고 실행되어야 함. ( try-catch 구문 )

  → Authentication 에러 ( AuthenticationEntryPoint 를 활용해서 처리 ) 로그인 페이지로 이동함으로써 처리

      AccessDeniedException 에러 ( AccessDeniedHandler를 활용해서 처리 ) 403 Error 로 처리

 

- 에러페이지 커스텀 설정

http.exceptionHandling()
                    .accessDeniedPage("/access-denied");

 

 

# FilterSecurityInterceptor

- HTTP 리소스 시큐리티 처리를 담당하는 필터. AccessDecisionManager 를 사용하여 인가를 처리한다.

http.authorizeRequests()
                .mvcMatchers("/", "/info", "/account/**", "/signup").permitAll()
                .mvcMatchers("/admin").hasRole("ADMIN")
                .mvcMatchers("/user").hasRole("USER")
                .anyRequest().authenticated()
                .expressionHandler(expressionHandler());

 

 

 

 

 

# RememberMeAuthenticationFilter

- 세션이 사라지거나 만료가 되더라도 쿠키 또는 DB를 사용하여 저장된 토큰 기반으로 

  인증을 지원하는 필터

- RememberMe 설정

http.rememberMe()
                // 폼에서 remember me 값을 안보내줘도 remember me 사용
                .alwaysRemember(false)
                // remember Me 파라미터 이름 설정
                .rememberMeParameter("remember")
                // 토큰 유효기간 설정, 기본값 2주
                .tokenValiditySeconds(1)
                // 쿠키 보안 설정
                .useSecureCookie(true)
                .userDetailsService(accountService)
                .key("remember-me-sample");

 

- RememberMeAuthenticationFilter

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
			throws IOException, ServletException {
		HttpServletRequest request = (HttpServletRequest) req;
		HttpServletResponse response = (HttpServletResponse) res;

		if (SecurityContextHolder.getContext().getAuthentication() == null) {
			Authentication rememberMeAuth = rememberMeServices.autoLogin(request,
					response);

			if (rememberMeAuth != null) {
				// Attempt authenticaton via AuthenticationManager
				try {
					rememberMeAuth = authenticationManager.authenticate(rememberMeAuth);

					// Store to SecurityContextHolder
					SecurityContextHolder.getContext().setAuthentication(rememberMeAuth);

					onSuccessfulAuthentication(request, response, rememberMeAuth);

					if (logger.isDebugEnabled()) {
						logger.debug("SecurityContextHolder populated with remember-me token: '"
								+ SecurityContextHolder.getContext().getAuthentication()
								+ "'");
					}

					// Fire event
					if (this.eventPublisher != null) {
						eventPublisher
								.publishEvent(new InteractiveAuthenticationSuccessEvent(
										SecurityContextHolder.getContext()
												.getAuthentication(), this.getClass()));
					}

					if (successHandler != null) {
						successHandler.onAuthenticationSuccess(request, response,
								rememberMeAuth);

						return;
					}

				}
				catch (AuthenticationException authenticationException) {
					if (logger.isDebugEnabled()) {
						logger.debug(
								"SecurityContextHolder not populated with remember-me token, as "
										+ "AuthenticationManager rejected Authentication returned by RememberMeServices: '"
										+ rememberMeAuth
										+ "'; invalidating remember-me token",
								authenticationException);
					}

					rememberMeServices.loginFail(request, response);

					onUnsuccessfulAuthentication(request, response,
							authenticationException);
				}
			}

			chain.doFilter(request, response);
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("SecurityContextHolder not populated with remember-me token, as it already contained: '"
						+ SecurityContextHolder.getContext().getAuthentication() + "'");
			}

			chain.doFilter(request, response);
		}
	}

  먼저 SecurityContextHolder 에서 인증정보가 있는지 체크하고 없으면 remember-me 인증 로직을 거친다.

  rememberMeServices.autoLogin(request, response) 에서 저장된 인증정보를 불러와

  SecurityContextHolder에 넣어준다.

 

 


 

- 터미널에서 아래와 같은 명령어로 url 을 호출해볼 수 있다.

 curl -u jake:123 http://localhost:8080/dashboard

 

Comments