관리 메뉴

KorSA

Spring Security 메모 본문

Study Memos/Spring Framework

Spring Security 메모

Praiv. 2021. 6. 7. 02:41
320x100

※ 이 글은 백기선 강사님의 인프런 강좌 '스프링 시큐리티'를 수강하고 개인적으로 남긴 메모입니다.

 

* 스프링 시큐리티는 필터를 이해하고 각 필터를 어떻게 설정하는지가 핵심이다.

 

[ 폼 인증 예제 살펴보기 ]

1. Principal -> 현재 페이지에 요청을 하는 유저 정보를 알 수 있다.

 

[ 스프링 웹 프로젝트 만들기 ]

1. 개발할 땐 chrome의 cognitive tab을 사용하면 세션 유지 등을 하지 않아서 좀 더 편리하다.

 

2. Controller에서 받는 Principal 파라미터에 로그인한 사용자 정보가 담겨 온다.

로그인이 안되어 있을 경우 null이 담겨 온다.

 

[ 스프링 시큐리티 연동 ]

1. dependency에 spring-boot-starter-security 추가

 

2. 이전의 문제였던 '인증할 방법'과 '인증된 사용자 정보'를 제공해준다.

 

3. 로그인 패스워드 등 보안에 관련된 내용은 절대 로그에 남기면 안된다.

 

4. 기본적으로 별도 설정이 없는 한 모든 요청은 인증을 필요로 한다.

(Principal을 안쓰더라도)

 

[ 스프링 시큐리티 설정하기 ]

1. @Configuration, @EnableWebSecurity 어노테이션을 붙이고 WebSecurityConfigurerAdapter 클래스를 상속받으면 스프링 시큐리티 설정 클래스를 만들 수 있다.

 

2. http.authorizeRequests().anyRequest() => 기타 등등의 요청

 

3. http.authorizeRequest(), .formLogin(), .httpBasic() 정도가 기본 설정이다.

 

4. formLogin() 은 logout 기능도 지원해준다.

 

[ 스프링 시큐리티 커스터마이징: 인메모리 유저 추가 ]

1. 스프링 시큐리티가 UserDetailsServiceAutoConfiguration 클래스에서 자동으로 User 로그인 정보 하나를 생성해줌.

           -> application.properties에서 계정 이름, 패스워드, Role을 개발자가 직접 고정해서 사용할 수 있음.

           -> 좋은 방법은 아님

           -> 보통 overring을 통해서 개발자가 원하는 유저 정보를 설정할 수 있음

                      -> configure(AuthenticationManagerBuilder auth)

 

[ 스프링 시큐리티 커스터마이징: JPA 연동 ]

1. spring-boot-starter-data-jpa dependency 추가

 

2. 새로 만든 @Service AccountService에 UserDetailsService 클래스를 상속

           -> @Override loadUserByUsername()

 

3. 스프링 시큐리티 최신 버전은 비밀번호에 특정 패턴을 요구한다.

           -> ex) {noop}123

 

4. UserDetailsService를 상속한 클래스가 Bean에 등록되어 있으면 스프링 시큐리티가 알아서 가져다가 사용한다.

 

[ 스프링 시큐리티 커스터마이징: PasswordEncoder ]

1. 스프링에서 제공해주는 PasswordEncoder를 사용하면 "{noop}" 처럼 직접 지정하지 않아도 된다.

 

2. PasswordEncoder를 @Bean으로 등록한 후 사용할 수 있다.

 

3. 여러 패스워드 인코딩 방식을 제공하기 위해 Spring Security 5부터 PasswordEncoder 방식을 지원한다.

 

[ 스프링 시큐리티 테스트 1부 ]

1. 직접 실행해서 일일이 확인해보는 방식은 꽤나 시간을 많이 잡아먹고 비효율적이다.

 

2. spring-security-test 모듈을 dependency에 추가한다.

이 때 scope은 test로 지정해준다.

version은 ${spring-security.version} 으로 지정해준다.

 

3. MockMvc를 이용하여 REST API의 테스트 코드를 작성할 수 있다.

 

4. 어노테이션이 계속 중복되면 직접 커스텀 어노테이션을 만들고 @Retention을 RUNTIME으로 지정해서 사용하자.

 

[ 스프링 시큐리티 테스트 2부 ]

1. formLogin() 기능을 테스트할 수도 있다.

 

[ 스프링 시큐리티 : 아키텍처 - SecurityContextHolder와 Authentication ]

1. SecurityContextHolder -> SecurityContext -> Authentication -> Principal

 

[ AuthenticationManager와 Authentication ]

1. ProviderManager가 authentication을 수행하고, ProviderManager는 내부에 여러 개의 Provider들을 관리한다.

 

2. 사용자가 특정 Authentication을 요청했을 때 ProviderManager가 해당 토큰을 처리할 Provider가 없다면, 부모 ProviderManager에게 던져서 토큰 처리를 요청한다.

 

3. Provider의 내부에서 우리가 선언한 UserDetailsService 상속 구현체를 사용하게 된다. (비밀번호 토큰 확인 요청의 경우 DaoAuthenticationProvider)

 

[ ThreadLocal ]

1. 변수를 Thread Scope으로 사용하는 기술. ThreadLOcal을 쓰면 한 Thread 내에서 변수를 공유한다.

 

2. ThreadLocal<T> 로 변수를 선언한다.

 

3. ThreadLocal을 사용하면 함수 간에 데이터를 전달할 때 파라미터로 넘길 필요가 없다.

 

[ Authentication과 SecurityContextHolder ]

1. SecurityContextHolder는 ThreadLocal이어서 필요할 때 SecurityContextHolder를 참조해서 Context를 얻어올 수 있다.

 

2. SecurityContextPersistenceFilte

           => SecurityContext를 HTTP session에 캐시(기본 전략)하여 여러 요청에서 Authentication을 공유할 수 있도록 해주는 필터.

           => SecurityContextRepository를 교체하여 session을 HTTP session이 아닌 다른 곳에 저장하는 것도 가능하다.

 

[ 스프링 시큐리티 Filter와 FilterChainProxy ]

1. FilterChainProxy는 여러 Filter들을 순차적으로 실행하는 역할을 한다.

 

2. FilterChain이 만들어지는 부분은 우리가 처음에 보았던 @Configuration SecurityConfig 클래스의 configure() 함수이다.

           => http 객체에 체이닝으로 연결한 기능들이 FilterChain이 된다.

 

[ DelegatingFilterProxy와 FilterChainProxy ]

 

 

[ AccessDecisionManager ]

1. "인가"를 관리한다.

 

2. Access Control 결정을 내리는 인터페이스로, 구현체 3가지를 기본으로 제공한다.

           => AffirmativeBased: 여러 Voter중에 한명이라도 허용하면 허용, 기본 전략.

           => ConsensusBased: 다수결

           => UnanimousBased: 만장일치

 

3. @Configure 구현체에서 http 객체의 expressionHandler()를 사용하면 그나마 간단하게 ROLE의 Hierarchy를 추가할 수 있다.

(예를 들어 ADMIN 권한이 USER 권한을 포함한다는 정보 등)

 

4. AccessDecisionVoter 여러 개를 관리한다.

 

[ FilterSecurityInterceptor ]

1. 앞에서 언급한 FilterChain에 포함된 Filter이다.

 

[ ExceptionTranslationFilter ]

1.AbstractSecurityInterceptor의 하위 클래스에서 발생한 예외에 대해서만 Exception 발생

 

2. AuthenticationException, AccessDeniedException을 발생시킴

 

[ 스프링 시큐리티 아키텍처 정리 ]

1. WebSecurity가 FilterChain을 만든다. 우리가 맨처음 만든 @Configuration 클래스가 WebSecurity를 만드는 작업이다.

 

2. WebSecurity는 자동적으로 HttpSecurity를 생성한다.

 

[ 스프링 시큐리티 ignoring() ]

1. localhost로 접속할 때 favicon.ico 등의 리소스가 있을 경우 이 리소스들을 얻기 위해 불필요한 login Request가 발생한다.

(favicon.ico 파일을 요청하면 login 페이지로 redirect 되기 때문)

 

2. web.ignoring() 함수를 사용하면 특정 리소스에 대한 인증 처리를 무시할 수 있다.

           => filterChain에 아무것도 등록되지 않는다. 그래서 인증 자동 통과.

 

3. configure(WebSecurity web) 함수를 overriding해서 ignoring()을 사용한다.

 

4. configure(HttpSecurity http) 함수를 overriding해서 http의 requestMatchers() 함수를 써도 ignoring()과 유사한 효과가 나오지만, 이 경우 무시할 리소스에 대한 filterChain이 모두 생성되서 비효율적이다.

 

[ Async 웹 MVC를 지원하는 필터: WebAsyncManagerIntegrationFilter ]

1. Callable을 사용하면 async로 동작한다.

 

2. SecurityContext는 여러 쓰레드에서 공유한다.

 

[ 스프링 시큐리티와 @Async ]

1. 특정 Bean 안의 함수에 @Async를 붙이기만 한다고 해서 비동기적으로 실행되는 게 아니다.

           => @EnableAsync를 Main에서 붙여주어야 한다.

 

2. @Async를 사용한 곳에서는 SecurityContext 공유가 안된다.

           => 이를 해결하려면 SecurityContextHolder의 setStrategyName()으로 MODE_INHERITABLETHREADLOCAL 을 사용해야 한다.

           => MODE_INHERITABLETHREADLOCAL은 Thread Local에서 생성한 Thread도 SecurityContext를 공유하게 된다.

 

[ SecurityContext 영속화 필터: SecurityContextPersistenceFilter ]

1. SecurityContextPersistenceFilter

=> HTTP session에서 기존에 만들어져 있던 SecurityContext를 읽어오고, 만일 SecurityContext가 없으면 비어있는 SecurityContext를 얻어온다.

 

[ Security 관련 헤더 추가하는 필터: HeaderWriterFilter ]

1. HeaderWriterFilter: 응답 헤더에 시큐리티 관련 헤더를 추가해주는 필터

 

2. 기본적으로 5개의 HeaderWriter가 동작함.

 

3. 개발자가 거의 건드릴 일은 없지만 Spring Security가 해주는 역할에 대한 고마움을 느낄 수 있음

 

[ CSRF 어택 방지 필터: CsrfFilter ]

1. CSRF 토큰: 사용자가 보낸 요청이 안전한 요청인지 알려주는 토큰

 

2. CORS: 다른 도메인의 요청도 허용해주는 방식 (보안상 안 좋지만 필요한 경우가 있음)

 

3. configure()에서 CSRF를 disable 할 수 있음

 

4. 되도록 CSRF 기능은 쓰자.

 

[ CSRF 토큰 사용 예제 ]

1. Thymeleaf에서 URL 적을 때에는 th:action="@{...}" 처럼 적어주는 게 안전하다.

 

2. Thymeleaf 2.1 이상에서는 Form 화면에서 csrf 태그를 알아서 넣어준다.

 

3. 로그인한 웹 브라우저가 아닌 PostMan 등에서 요청을 보낼 경우 CSRF 값이 없기 때문에 401 UnAuthorized 가 리턴된다.

 

4. 테스트 코드를 작성할 때에는 Spring이나 Thymeleaf가 알아서 CSRF를 체크해주지 않기 때문에, 프로그래머가 직접 HTTP Content의 String을 검사하여 "_csrf" 가 있는지 확인해야 한다.

 

5. post를 테스트할 때에는 with(csrf())를 넣어주어야 POST 요청에 CSRF가 함께 전달된다.

 

[ 로그아웃 처리 필터: LogoutFilter ]

1. LogoutFilter는 LogoutHandler 여러개와 LogoutSuccessHandler를 가지고 있다.

 

[ 폼 인증 처리 필터: UsernamePasswordAuthenticationFilter ]

1. UsernamePasswordAuthenticationFilter

=> AuthenticationManager를 활용하여 인증을 시도한다.

 

[ 로그인/로그아웃 폼 페이지 생성 필터 : DefaultLogin/LogoutPageGeneratingFilter ]

1. 사용자가 http.loginPage()를 직접 지정해주면 DefaultLoginPageGeneratingFilter와 DefaultLogoutPageGeneratingFilter는 필터 체인에 등록되지 않는다.

 

[ 로그인/로그아웃 폼 커스터마이징 ]

1. th:if="${param.error}" 로 로그인 등의 작업에서 나온 에러를 catch할 수 있다.

 

[ Basic 인증 처리 필터: BasicAuthenticationFilter ]

1. Basic 인증

           => id:password를 Base64로 인코딩한 값으로 Authentication을 진행함

           => 보안에 취약하기 때문에 HTTPS로 사용할 것을 권장함.

 

2. curl은 Basic 인증 토큰을 만들어서 전송해주는 툴이다. 이 방식은 기본적으로 stateless여서 스프링의 필터 방식처럼 토큰의 캐싱이 되지 않는다.

 

[ 요청 캐시 필터: RequestCacheAwareFilter ]

1. 로그인 안 된 상태에서 dashboard 가려고 했던 요청을 RequestCacheAwareFilter로 캐싱하고, Spring Security는 로그인 화면을 먼저 띄운다.

사용자의 로그인이 완료되면 Spring Security는 RequestCacheAwareFilter에 캐싱했던 Dashboard 요청을 꺼내서 마저 진행한다.

 

2. 알아서 진행되기 때문에 크게 신경쓸 필요는 없다.

 

[ 시큐리티 관련 서블릿 스팩 구현 필터: SecurityContextHolderAwareRequestFilter ]

1. 알아서 진행되기 때문에 크게 신경쓸 필요는 없다.

 

[ 익명 인증 필터: AnonymousAuthenticationFilter ]

1. 인증이 안된 사용자를 AnonymousAuthentication 객체를 만들어서 관리함

 

2. AnonymousAuthenticationFilter는 AnonymousAuthentication 객체를 만들어서 SecurityContext에 넣어주는 역할을 함

 

3. NullObject 패턴과 비슷한 역할

 

[ 세션 관리 필터: SessionManagementFilter ]

1. 중요한 역할을 하는 필터

           => 세션 변조 방지 전략을 설정

                      => migrateSession 전략 (서블릿 3.0 이하)

                      => changeSessionId 전략 (서블릿 3.1 이상)

           => 유효하지 않은 세션 redirect

                      => 로그아웃 했을 때 기존 세션의 접근을 invalid시킴

           => 동시성 제어

                      => Spring은 기본적으로 무한대로 로그인이 가능함. 즉, 한 계정을 여러명이서 쓸 수 있음

                      => 한 계정에 대해 하나의 세션만 유지하고 싶을 때 동시성을 제어할 수 있음

           => 세션 생성 전략 설정

                      => 기본값은 IF_REQUIRED

                     

[ 인증/인가 예외 처리 필터: ExceptionTranslationFilter ]

1. FilterSecurityInterceptor와 밀접한 관련이 있음

 

2. ExceptionTranslationFilter가 요청을 감싼 후 FilterSecurityInterceptor가 후속 처리를 하기 때문에 ExceptionTranslationFilter가 FilterSecurityInterceptor 앞에 와야 함

 

3. 인증이 안된 경우 => AuthenticationException -> AuthenticationEntryPoint

 

4. 인가가 안된 경우 => AccessDeniedException -> AccessDeniedHandler

 

5. http.exceptionHnadling().accessDeniedPage("/access-denied") 를 이용하면 인가가 안된 경우 좀 더 사용자 친화적인 페이지로 redirect할 수 있음

 

[ 인가 처리 필터: FilterSecurityIntercetpor ]

1. AccessDecisionManager를 사용하여 인가를 처리

 

2. mvcMatchers().fullyAuthenticationed() => 결제 등 중요한 작업에서 인증을 다시 요청

 

[ 토큰 기반 인증 필터: RememberMeAuthtenticationFilter ]

1. Remember Me : 명시적으로 로그아웃하거나 만료 기간이 지나기 전까지 로그인이 유지됨.

세션보다 수명이 더 길다.

 

2. Remember Me를 설정하면 기존의 세션 쿠키 이외에 Remember Me 전용 쿠키가 하나 더 생성됨.

 

[ 커스텀 필터 추가하기 ]

1. GenericFilterBean을 상속받아 커스텀 필터를 만들고 http.addFilter() 로 추가하면 된다.

 

[ 타임리프 스프링 시큐리티 확장팩 ]

1. thymeleaf.extras

 

2. 사용자의 로그인 유무에 따라 로그인/로그아웃 버튼 중 하나만 보여주고 싶은 경우 등에 사용

 

[ sec 네임스페이스 ]

1. 타임리프 확장팩 쓰는 것보다 좀 더 편리한 방법

 

2. xmlns:sec="http://www.thymeleaf.org/extras/spring-security" 을 네임스페이스로 추가

 

[ 메소드 시큐리티 ]

1. 웹 기반으로 Spring을 쓸 땐 별로 안쓰고, 혹 데스크탑에서 Spring을 쓸 땐 메소드 시큐리티를 사용함

 

2. 함수 호출 전에 인증 유무를 먼저 검사함

 

3. authenticationManager는 기본적으로 Bean으로 노출되어져 있지 않기 때문에, 코드적으로 인증을 하려면 authenticationManager를 overriding해서 @Bean 어노테이션을 붙인 후 사용해야 한다.

 

[ @AuthenticationPrincipal ]

1. Principal 자체는 java standard에서 기본적으로 제공해주는 객체임

           => SecurityContextHolder 안에 들어있는 Principal 객체와는 다름

 

[ 스프링 데이터 연동 ]

1. spring-security-data 의존성을 추가해주어야 함

           => spring security에서 데이터 연동을 도와주는 툴

 

728x90
728x90

'Study Memos > Spring Framework' 카테고리의 다른 글

Java Coding Convention  (0) 2021.05.18
스프링 기본  (0) 2021.03.22
Servlet Container, Servlet, 그리고 RequestDispatcher  (0) 2019.01.17
Spring Framework 공부한 내용 정리  (0) 2019.01.15
Comments