-
서버가 기동되면 스프링 시큐리티의 초기화 작업 및 보안 설정이 이루어진다
- 별도의 설정이나 구현을 하지 않아도 기본적인 웹 보안 기능이 현재 시스템에 연동되어 작동함
- 모든 요청은 인증이 되어야 자원에 접근이 가능하다
- 인증 방식은 폼 로그인 방식과 httpBasic 로그인 방식을 제공한다
- 기본 로그인 페이지 제공한다
- 기본 계정 한 개 제공한다 – username : user / password : 랜덤 문자열
-
개선할 사항
- 계정 추가
- 권한 추가
- DB 연동
- 기본적인 보안 기능 외에 시스템에서 필요로 하는 더 세부적이고 추가적인 보안기능이 필요
-
WebSecurityConfigurerAdapter (핵심)
- 웹 보안 기능 초기화 및 활성화
- HttpSecurity 생성
- 인증 & 인가 API 제공
-
Debug
- SecurityConfig Debug
1. WebSecurityConfigurerAdapter 클래스의 기본 값 설정 디버깅
2. HttpSecurity 기본 설정
1) getHttp()
2) configure()
- 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는 필수이다.
-
인증 프로토콜과 헤더
- HTTP는 필요에 따라 고쳐 쓸 수 있는 제어 헤더를 통해, 다른 인증 프로토콜에 맞추어 확장할 수 있는 프레임워크를 제공한다.
- 헤더의 형식과 내용은 인증 프로토콜에 따라 달라진다.
- 인증 프로토콜은 HTTP 인증 헤더에 기술되어 있다.
- HTTP에는 "기본 인증"과 "DIGEST 인증"이 있다.
- Basic Authentication 의 문제점
- 기본 인증의 보안 문제
- 기본 인증은 단순하고 편리하지만 안심할 수 없다.
- 기본인증은 악의적이지 않은 누군가가 의도하지 않게 리소스에 접근하는 것을 막는데 사용하거나 SSL 같은 암호 기술과 혼용 해야 한다.
- base64 인코딩된 값은 쉽게 디코딩할 수 있다.
- 제 3자는 사용자 이름과 비밀번호를 캡처하여 악용할 수 있다.
- Proxy나 중개자가 개입하는 경우 정상적인 동작을 보장할 수 없다.
- 기본인증은 가짜 서버의 위장에 취약하다.
- 인증 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();
- Authentication
- 인증을 처리하는 필터
- LogoutFilter Flow
- 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")
;
-
RememberMe 인증이란 대체 뭐야 ?
- form 인증 방식에 추가로 사용할 수 있는 기능
- 세션이 유실 또는 웹 브라우저가 종료된 이후에도 어플리케이션이 사용자를 기억하는 기능
- form 인증 방식에 추가로 사용할 수 있는 기능
-
remember-me cookie 란?
- 발급 조건
- remember-me 기능을 활성화시킨 상태에서 인증 프로세스를 처리 시 서버가 remember-me 쿠키를 응답 헤더에 실어서 보내게 된다.
- 용도
- 사용자는 remember-me 쿠키의 만료일 전까지 세션 유지가 가능하다.
- 세션이 유실되는 경우에도 remember-me 쿠키가 존재하는 경우 쿠키 안에 사용자 정보를 통해 토큰 기반 인증하여 토큰 검증 시 로그인 유지가 가능
- 쿠키에 존재하는 remember-me 형태
- 발급 조건
-
라이프 사이클
- 인증 성공
- remember-me 쿠키 설정
- 인증 실패
- 쿠키 존재 시 쿠키 무효화
- 로그 아웃
- 쿠키 존재 시 쿠키 무효화
- 인증 성공
-
로그인 시 세션 생성 플로우
-
JSESSIONID 삭제 시 remember-me 쿠키가 인증 토큰을 재발급 하는 플로우
/* 인증 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)
;
- remember-me 발급 플로우
- Remember-Me가 존재하는 경우 JSESSIONID가 유실되는 경우에도 인증처리가 가능
- SpringSecurity의 RememberMeAuthenticationFilter 에서 request Header에 Cookie를 확인하여 userId와 userPw값으로 user 객체를 얻어 인증처리 해버림
-
사용자의 인증 여부를 판단하는 필터
-
인증 받지 않은 사용자에게 Authentication == null 이 아닌 '익명 사용자용 인증객체' 를 담도록 하는 필터
-
접근한 유저 정보의 인증정보를 null 이 아닌 익명 사용자로 확인 가능
- isAnonymous() 인 경우 loginPage로 접근할 수 있도록 유도 가능
- isAuthenticated() 인 경우 logoutPage로 접근 할 수 있도록 유도 가능
-
AnonymousAuthenticationFilter 에서 인증객체 확인 및 익명 객체 설정 기능
-
위 처리를 하게 되는 경우 AbstractSecurityInterceptor 에서 익명객체를 확인하여 인가 처리를 하게 됨
-
익명 객체 flow
/* 인증 API */
http
.authorizeRequests()
.anyRequest().authenticated();
http
.formLogin();
http
/* 인가 API */
.anonymous()
;
- 동일 계정으로 접속이 허용되는 최대 세션 수를 제한
- 이전 세션 종료 방식
- 이후 세션 접근 거부 방식
http
.authorizeRequests()
.anyRequest().authenticated();
http
.formLogin();
http
/* 세션 관리 */
.sessionManagement()
.maximumSessions(1) // 최대 허용 가능 세션 수 (-1: 무제한)
.maxSessionsPreventsLogin(true) // 동시 로그인 차단 (default: false)
.expiredUrl("/login") // 세션 만료 시 페이지 이동
/* 세션 관리 */
.sessionManagement()
.invalidSessionUrl("/login") // 세션 유효하지 않는 경우 페이지 이동
- 인증 할 때마다 세션 쿠키를 새로 발급하여 공격자의 쿠키 조작을 방지
- 사용자의 쿠키를 공격자의 쿠키로 인증처리 한 뒤 공격자가 해당 쿠키로 인증하는 세션 고정 공격을 방지 하기 위한 설정
/* 세션 고정 보호 설정 */
.sessionFixation()
/* 세센 고정 보호를 사용하지 않는 경우 위와 같은 문제가 발생할 수 있음 */
//.none()
/* Servlet 3.1 미만 버전 일 때 default 값 */
/* 세션을 생성하되 기존 세션 정보가 있다면 사용할 수 있음 */
//.migrateSession()
/* 새로운 인증 세션으로 변경하는 방법 */
//.newSession()
/* Servlet 3.1 이상 버전 일 때 default 값 */
/* 세션을 생성하되 기존 세션 정보가 있다면 사용할 수 있음 */
.changeSessionId()
- 세션 정책
- Always
- if_Required
- Never
- Stateless
-
세션 관리
- 인증 시 사용자의 세션 정보를 등록, 조회, 삭제 등의 세션 이력을 관리
-
동시적 세션 제어
- 동일 계정으로 접속이 허용되는 최대 세션 수를 제한
- ConcurrentSessionControlAuthenticationStrategy
-
세션 고정 보호
- 인증 할 때마다 세션 쿠키를 새로 발급하여 공격자의 쿠키 조작을 방지
- ChangeSessionIdAuthenticationStrategy
-
세션 생성 정책
- Always, if_Required, Never, Stateless
-
매 요청마다 현재 사용자의 세션 만료 여부 체크
-
세션이 만료되었을 경우 즉시 만료 처리
-
session.isExpired() == true
- 로그아웃 처리
- 즉시 오류 페이지 응답
-
check point
- 동시적 세션 제어를 처리하기위해 ConcurrentSessionFilter와 SessionManagementFilter의 처리과정을 살펴볼 것
- 동시적 세션 제어 전략 시나리오 Flow
-
시나리오
- Session의 등록
- 동일 계정 접속 시 세션 처리 방식
- 인증 실패 전략
- 세션 만료 전략
-
시나리오 디버깅 하기
- 서버 접근 시 최대 세션 수에 초과되는 경우
-
이후 세션 접근을 거부하는 방식
- 첫 번째 사용자 로그인
- 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) 리턴
- 93 Line: final List sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
- AbstractSessionFixationProtectionStrategy
- 96 Line: onSessionChange(originalSessionId, session, authentication);
- 현재 세션 아이디와 새로운 세션아이 변경
- 127 Line: applicationEventPublisher.publishEvent(new SessionFixationProtectionEvent(auth, originalSessionId, newSession.getId()));
- 96 Line: onSessionChange(originalSessionId, session, authentication);
- 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) -> { ... }
- 변경된 세션 정보를 등록
- 119 Line: registerNewSession(String sessionId, Object principal)
- 66 Line: sessionRegistry.registerNewSession(request.getSession().getId(),authentication.getPrincipal());
- CompositeSessionAuthenticationStrategy
- 두 번째 사용자 로그인
- 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(..); }
- 최대 세션 수가 초과되는경우 인증 싫패 처리
- 154 Line: if (exceptionIfMaximumExceeded || (sessions == null)) { throw new SessionAuthenticationException(..); }
- 162 ~ 167 Line: ... session.expireNow();
- 세션 expire 처리
- 93 Line: final List sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
- AbstractAuthenticationProcessingFilter
- 230 Line: unsuccessfulAuthentication(request, response, failed);
- 인증 실패 처리 로직 실행
- 342 Line: SecurityContextHolder.clearContext();
- 인증 객체 삭제처리
- 350 Line: rememberMeServices.loginFail(request, response);
- 352 Line: failureHandler.onAuthenticationFailure(request, response, failed);
- 실패 처리
- 230 Line: unsuccessfulAuthentication(request, response, failed);
- ConcurrentSessionControlAuthenticationStrategy
- 첫 번째 사용자 로그인
-
이전 세션 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 처리
- 162 ~ 167 Line: ... session.expireNow();
- 93 Line: final List sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);
- ConcurrentSessionControlAuthenticationStrategy
- 첫 번째 사용자 페이지 이동 시
- ConcurrentSessionFilter
- 130 Line: SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
- 세션 정보를 가져옴
- 134 Line: if (info.isExpired()) {
- 세션 expired 확인
- 140 Line: doLogout(request, response);
- 로그아웃 처리
- 130 Line: SessionInformation info = sessionRegistry.getSessionInformation(session.getId());
- ConcurrentSessionFilter
- 두 번째 사용자 로그인
-
- 서버 접근 시 최대 세션 수에 초과되는 경우
-
선언적 방식
- URL
- 경로에 대한 권한정보를 통한 인가 처리 등록
- hasRole("ROLE"), access("hasRole('ADMIN') or hasRole('SYS')")
- 설정 시 구체적인 경로가 먼저오고 그 보다 큰 범위의 경로를 뒤에 설정하도록 해야 한다.
- 경로에 대한 권한정보를 통한 인가 처리 등록
- Method
- URL
-
동적 방식
- URL
- Method
-
HttpSecurity 클래스 인가 API 리스트
- authenticated()
- 인증된 사용자의 접근을 허용
- fullyAuthenticated()
- 인증된 사용자의 접근을 허용, rememberMe 인증 제외
- permitAll()
- 무조건 접근을 허용
- denyAll()
- 무조건 접근을 허용하지 않음
- anonymous()
- 익명사용자의 접근을 허용
- rememberMe()
- remember-me cookie 를 통해 인증된 사용자의 접근을 허용
- access(String)
- 주어진 SpEL 표현식의 평가 결과가 true이면 접근을 허용
- hasRole(String)
- 사용자가 주어진 역할이 있다면 접근을 허용
- hasAuthority(String)
- 사용자가 주어진 권한이 있다면 접근을 허용
- hasAnyRole(String...)
- 사용자가 주어진 권한이 있다면 접근을 허용
- hasAnyAuthority(String...)
- 사용자가 주어진 권한 중 어떤 것이라도 있다면 접근을 허용
- hasIpAddress(String)
- 주어진 IP로부터 요청이 왔다면 접근을 허용
- authenticated()
-
유저 권한 설정
- 권한에 대한 위계가 있지 않음
- 코드에 권한 설정이되어 있어 실시간으로 수정할 수 없으므로 자원관리에 비효율적
/* 인가 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()
;
-
ExceptionTranslationFilter
- 인가 작업 시 발생하는 예외를 처리하는 필터
- 마지막에서 두번째 위치에 존재하는 필터
-
AuthenticationException
- 인증 예외 처리
- AuthenticationEntryPoint 호출
- 로그인 페이지 이동, 401 오류코드 전달
- 인증 예외가 발생하기 전의 요청 정볼르 저장
- RequestCache: 사용자의 이전 request 정보를 세션에 저장하고 이를 꺼내오는 캐시 메커니즘
- SavedRequest: 사용자가 요청했던 request 파라미터 값들, 그 당시의 헤더 값들을 저장
- AuthenticationEntryPoint 호출
- 인증 예외 처리
-
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");
}
})
;
- 시나리오
-
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 값을 저장하여 설정 정보를 제공
- 51 Line: public void saveRequest(HttpServletRequest request, HttpServletResponse response) {
- HttpSessionRequestCache
- SecurityConfig
- 212 Line: authenticationEntryPoint.commence(request, response, reason);
- 현재 request 값을 가지고 redirect 하도록 구현해 놓은 익명 클래스로 이동
- 212 Line: authenticationEntryPoint.commence(request, response, reason);
- 177 Line: if (exception instanceof AuthenticationException) {
- ExceptionTranslationFilter
-
로그인 성공 후 (인가 실패 시) 인가 예외 발생 처리 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' 페이지로 이동하는 로직을 구현해 놓은 익명 클래스로 이동
- 177 Line: else if (exception instanceof AccessDeniedException) {
-
인증 없이 '/' 페이지에 접근, 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);
- 세션 정보를 가져오려고하나 값이 없음
- 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를 저장
- 60 Line: request.getSession().setAttribute(this.sessionAttrName, savedRequest);
- HttpSessionRequestCache
- 210 Line: requestCache.saveRequest(request, response);
- 177 Line: else if (exception instanceof AuthenticationException) {
- 60 Line: HttpServletRequest wrappedSavedRequest = requestCache.getMatchingRequest((HttpServletRequest) request, (HttpServletResponse) response);
- RequestCacheAwareFilter
- 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라는 객체가 함
- 90 Line: public HttpServletRequest getMatchingRequest(HttpServletRequest request,HttpServletResponse respo
-
- SecurityConfig
- 인증 없이 '/' 페이지에 접근
-
-
기본적으로 설정되어 있는 springSecurityFilterChain 에 있는 csrf 의 존재 이유와 수행 방식
- csrf ?
- csrf 원리
- csrf ?
-
시나리오
-
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로 접근 불가 예외처리
-
- CsrfFilter
-
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);
- 다음 필터로 이동
- 107 Line: CsrfToken csrfToken = this.tokenRepository.loadToken(request);
- BasicController
- @PostMapping("/") public String csrf() { return "Home"; }
- csrfFilter의 작업을 마치고 requestMapping된 컨트롤러 로직 수행
- @PostMapping("/") public String csrf() { return "Home"; }
-