목표: 스프링 시큐리티 파트1
Thread Per Request 모델과 ThreadLocal
- Thread Per Request 모델
- 병렬처리 기법 중 하나
- WAS는 ThradPool을 생성하는데 HTTP 요청이 들어오면 Queue에 적재되고, ThreadPool 내의 특정 Thread가 Queue에서 요청을 가져와 처리
- WAS의 최대 동시 처리 HTTP 요청의 갯수는 ThreadPool의 갯수와 같음
- ThreadLocal
- 동일 Thread 내에서는 언제든 읽고 쓸 수 있는 변수
final static ThreadLocal<Integer> threadLocalValue = new ThreadLocal<>();
public static void main(String[] args) {
System.out.println(getCurrentThreadName() + " ### main get value = 1");
threadLocalValue.set(1);
a();
b();
//다른스레드에서 람다식 실행하는 비동기 실행코드
CompletableFuture<Void> task = runAsync(() -> {
a();
b();
});
//람다식 코드 끝날때까지 대기
task.join();
}
출력
main ### main get value = 1
main ### a() get value = 1
main ### b() get value = 1
ForkJoinPool.commonPool-worker-3 ### a() get value = null
ForkJoinPool.commonPool-worker-3 ### b() get value = null
스레드 풀과 ThreadLocal 변수 함께 사용할 때 주의 할 점
- ThreadPool과 함께 사용하는 경우 Thread가 ThreadPool에 반환되기 직전 ThreadLocal 변수 값을 반드시 제거
- 이전 요청 처리에 사용된 ThreadLocal 변수가 남아있으면 이를 참조하여 잘못된 동작을 수행할 수 있기 때문
SecurityContextHolder, SecurityContext, Authentication
SecurityContextHolder는 SecurityContext 데이터를 쓰거나 읽을수 있는 API를 제공 (
기본 구현은 ThreadLocal를 이용
Thread Per Request 모델을 고려
SecurityContextHolder.clearContext()통해 변수 값 제거
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>(); //구현 clearContext() getContext() setContext(SecurityContext context) createEmptyContext() }
SecurityContext
- SecurityContextHolder 클래스를 통해 코드 어느 부분에서든 SecurityContext에 접근
- SecurityContextHolder.getContext();
- SecurityContext 안에 getAuthentication();
Authentication
사용자를 표현하는 인증 토큰 인터페이스
jsp를 타임리프로 바꾸면서 스프링 시큐리티에서 객체를 어떻게 가져오는지 해결만하고 끝냈었는데 여기에 이렇게 구현되어 있었다니 너무 반갑다!! 문제를 해결만 할게 아니라 깊게 파는 것도 중요할 것 같다.
정리하면 이런 그림
출처
인증 처리
사용자가 주장하는 본인이 맞는지 확인하는 절차를 의미(아이디/비밀번호)
- DefaultLoginPageGeneratingFilter
- HTTP GET 요청에 대해 디폴트 로그인 페이지를 생성
- 커스터마이징
http .formLogin() .loginPage("/my-login") .usernameParameter("my-username") .passwordParameter("my-password")
- AbstractAuthenticationProcessingFilter
- 사용자 인증을 처리
- 구현체 UsernamePasswordAuthenticationFilter
- Authentication 객체 생성
- UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
- return this.getAuthenticationManager().authenticate(authRequest);
- AuthenticationManager, ProviderManager
- AuthenticationManager 인터페이스는 사용자 인증을 위한 API를 제공
- 구현체 ProviderManager
- RememberMeAuthenticationFilter
- remember-me 쿠키를 갖고 있다면 사용자를 자동으로 인증
- RememberMeServices로 사용자 인증
- RememberMeAuthenticationToken
- Authentication 인터페이스 구현체
- RememberMeAuthenticationProvider: RememberMeAuthenticationToken 기반 인증 처리
- remember-me 설정 시 입력한 key 값 검증
세션 처리
SecurityContextPersistenceFilter
- 사용자의 SecurityContext를 가져오거나 갱신
- HttpSessionSecurityContextRepository 클래스가 구현
SessionManagementFilter
세션 고정 보호: session-fixation attack(정상 사용자의 세션을 탈취하여 인증 우회) 방지
세션 생성 전략
IF_REQUIRED: 필요시 생성 (기본값)
NEVER: Spring Security에서는 세션을 생성x 세션 존재하면 사용
STATELESS: 세션 사용x (JWT 인증이 사용되는 REST API 서비스에 적합)
ALWAYS: 항상 세션 사용
@Override protected void configure(HttpSecurity http) throws Exception { http .sessionManagement() //새로운 세션을 만들지 않지만, session-fixation 공격 방어 .sessionFixation().changeSessionId() //필요시 생성 .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) //유효하지 않은 세션 감지 시 지정된 URL로 리다이렉트 .invalidSessionUrl("/") //동일 사용자의 최대 동시 세션 개수 .maximumSessions(1) //최대 개수를 초과시 인증 시도 차단 여부(기본false) .maxSessionsPreventsLogin(false) .and() .and() ; }
인가 처리
권한이 부여된 사용자들만 특정 기능 또는 데이터에 접근 허용
- 인증된 사용자와 권한을 매핑(ROLE_USER)
- 보호되는 리소스에 대한 권한 확인(관리자 페이지)
사진
출처
- FilterSecurityInterceptor
- 사용자의 권한과 리소스에서 요구하는 권한을 취합-> 접근 허용 결정(AccessDecisionManager 구현)
- 익명 사용자(ROLE_ANONYMOUS)도 인증된것!
- 권한 정보 SecurityMetadataSource 통해 ConfigAttribute 가져옴
- AccessDecisionManager
- 사용자의 권한과 리소스에서 요구하는 권한 확인후 접근처리(AccessDecisionVoter로 판단)
- AccessDecisionVoter
- 접근 승인, 거절, 보류 판단
- WebExpressionVoter가 구현
- WebSecurityExpressionRoot클래스 SpEL 표현식 사용해 접근 승인 여부 규칙 지정
@Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/me").hasAnyRole("USER", "ADMIN") //인증 영역 .antMatchers("/admin").access("isFullyAuthenticated() and hasRole('ADMIN')") //isFullyAuthenticated 리멤머미로 인증되지 않은 사용자 .anyRequest().permitAll(); //익명영역 }
- WebSecurityExpressionRoot클래스 SpEL 표현식 사용해 접근 승인 여부 규칙 지정
인증 이벤트
- 인증 성공 또는 실패가 발생했을 때 관련 이벤트(ApplicationEvent) 발생(AuthenticationEventPublisher)
- 해당 이벤트에 관심있는 컴포넌트는 이벤트 구독
- 컴포넌트 간의 느슨한 결합 유지 위해
@Async //얘 오래걸릴 수 있음 -> 스레드 분리(비동기처리) @EventListener public void handleAuthenticationSuccessEvent(AuthenticationSuccessEvent event) { //Async 전: 5초 후 로그인 //Async 후: 로그인 되고 5초 후 로그찍힘, 스레드 달라짐 try { Thread.sleep(5000L); } catch (InterruptedException e) { e.printStackTrace(); } Authentication authentication = event.getAuthentication(); log.info("Successful authentication result: {}", authentication.getPrincipal()); }
그 밖의 필터
- HeaderWriterFilter: 응답 헤더에 보안 관련 헤더 추가
- XContentTypeOptionsHeaderWriter: MIME sniffing 공격 방어
- XXssProtectionHeaderWriter: 브라우저에 내장된 XSS(Cross-Site Scripting) 필터 활성화
- CacheControlHeadersWriter: 캐시 사용하지 않도록 설정
- XFrameOptionsHeaderWriter: clickjacking(다른 것을 클릭하게 속임) 공격 방어
- HstsHeaderWriter: HTTPS만을 사용하여 통신
- CsrfFilter
- CSRF (Cross-site request forgery): 공격자가 의도한 행위를 특정 웹사이트에 요청(사용자의 권한 도용 -> 중요 기능 실행)
- Referrer 검증: Request의 referrer 확인
- CSRF Token 활용: 세션에 임의의 토큰 값을 저장
- 타임리프 th:action="@{/send}" 써야 인풋타입히든에 토큰 만들어줌!
- WebAsyncManagerIntegrationFilter
- MVC Async Request가 처리될 때, 쓰레드간 SecurityContext를 공유할수 있게 해줌
- @Async 어노테이션을 추가한 Service 레이어 메소드에는 해당 안됨
- MODE_INHERITABLETHREADLOCAL으로 변경하면 가능(ThreadLocal->InheritableThreadLocal) 비권장
- ThreadLocal의 clear 처리가 제대로되지 않아 문제될 수 있음
- DelegatingSecurityContextAsyncTaskExecutor
- Pooling 되지않는 TaskExecutor와 함께 사용해야 함(SimpleAsyncTaskExecutor-매번 새로운 스레드 생성)
- 스레드 풀 사용하면서 스프링시큐리티 참조 다른 스레드로전파 어떻게?
- DelegatingSecurityContextRunnable 객체 생성자에서 SecurityContextHolder.getContext() 메소드를 호출하여 SecurityContext 참조 획득
@프로그래머스 미니 데브코스 & CNU SW Academy 강의 내용 정리
'모각코' 카테고리의 다른 글
[모각코 9회차] 상품관리 REST API 클론 프로젝트(feat.React) (0) | 2022.08.30 |
---|---|
[모각코 8회차] Spring Security with JWT (0) | 2022.08.25 |
[모각코 6회차] 주문관리 API (0) | 2022.08.11 |
[모각코 5회차] SPA와 CORS (0) | 2022.08.04 |
[모각코 4회차] 스프링 AOP와 트랜잭션 (0) | 2022.07.29 |