스프링 시큐리티는 기본적으로 DefaultLogoutPageGeneratingFilter를 통해 로그아웃 페이지 제공 “get/logout” URL로 접근이 가능
로그아웃 실행은 기본적으로 “ POST / logout “으로만 가능, CSRF 기능을 비활성화, 혹은 RequestMatcher를 사용할 경우 GET,PUT,DELETE 모두 사용 가능
로그아웃 필터를 거치지 않고 스프링 MVC에서 커스텀하게 구현할 수 있으며, 로그인 페이지가 커스텀하게 생성될 경우, 로그아웃 기능도 커스텀하게 구현
logoutAPI
http.logout(httpSecurityLogoutConfigurer -> httpSecurityLogoutConfigurer
.logoutUrl("/logoutProc") // 로그아웃이 발생하는 URL 을 지정한다 (기본값은 “/logout” 이다)
.logoutRequestMatcher(new AntPathRequestMatcher("/logoutProc","POST")) // 로그아웃이 발생하는 RequestMatcher 을 지정한다. logoutUrl 보다 우선적이다
// Method 를 지정하지 않으면logout URL이 어떤 HTTP 메서드로든 요청될 때 로그아웃 할 수 있다
.logoutSuccessUrl("/logoutSuccess") // 로그아웃이 발생한 후 리다이렉션 될 URL이다. 기본값은 "/login?logout"이다
.logoutSuccessHandler((request, response, authentication) -> { // 사용할 LogoutSuccessHandler 를 설정합니다.
response.sendRedirect("/logoutSuccess"); // 이것이 지정되면 logoutSuccessUrl(String)은 무시된다
})
.deleteCookies("JSESSIONID“, “CUSTOM_COOKIE”) // 로그아웃 성공 시 제거될 쿠키의 이름을 지정할 수 있다
.invalidateHttpSession(true) // HttpSession을 무효화해야 하는 경우 true (기본값), 그렇지 않으면 false 이다
.clearAuthentication(true) // 로그아웃 시 SecurityContextLogoutHandler가 인증(Authentication)을 삭제 해야 하는지 여부를 명시한다
.addLogoutHandler((request, response, authentication) -> {}) // 기존의 로그아웃 핸들러 뒤에 새로운 LogoutHandler를 추가 한다
(기존 + 새로 생성)
.permitAll() // logoutUrl(), RequestMatcher() 의 URL 에 대한 모든 사용자의 접근을 허용 함
client → logoutFIlter → requestMatcher -(요청 정보가 매칭 되는지)→ chain.doFilter (post 방식의 logout url이 오지 않은 경우)
client → logoutFIlter → requestMatcher -(요청 정보가 매칭 되는지)→ LogoutHandler → LogoutSuccessHandler
@Configuration
@EnableCaching
public class SecurityConfig {
//메모리 상에서 사용자 설정
//설정 파일과 중복이 있다면, @Bean 인메모리 방식이 우선
@Bean
public InMemoryUserDetailsManager inMemoryUserDetailsManager() {
//User객체는 SpringSecurity가 갖고 있는 User
UserDetails user = User.withUsername("user")
.password("{noop}1111") //(noop)을 사용하면 평서문 처럼 사용 가능
.authorities("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
//SecurityFilterChainConfiguration -> filter 조건 성립 안됨
//SecurityFilterChain -> Bean을 생성했기 때문 -> Bean이 없을 경우에만 성립된다. -> 사용자가 설정한 쪽으로 오게 된다.
.authorizeHttpRequests(auth -> auth
.requestMatchers("/logoutSuccess").permitAll()
.anyRequest().authenticated())
.formLogin(Customizer.withDefaults())
//.csrf(csrf -> csrf.disable()) //csrf 방식이 disable -> post 방식 뿐만 아니라 get 방식도 적용이 가능
.logout(logout -> logout
.logoutUrl("/logout")
.logoutRequestMatcher(new AntPathRequestMatcher("/logout", "POST")) //get 방식도 가능
.logoutSuccessUrl("/logoutSuccess") // 해당하는 Mapping 생성 logoutRequestMatcher 처리 안하면 post 방식만 가능
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/logoutSuccess");
//복잡한 경로 -> handler 사용
}
})
.deleteCookies("JSESSIONID", "remember-me") //보통적으로 JSESSIONID는 삭제
.invalidateHttpSession(true) // logout시 session 무효화
.clearAuthentication(true) //securityContext 안에 있는 authentication 객체를 제거
.addLogoutHandler(new LogoutHandler() {
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
//세션 무효화
HttpSession session = request.getSession();
session.invalidate();
//시큐리티 컨택스트 안에있는 authentication을 제거 가능 / 기존 authenticaiton 객체가 제거
SecurityContextHolder.getContextHolderStrategy().getContext().setAuthentication(null);
SecurityContextHolder.getContextHolderStrategy().clearContext(); //SecurityContextHolder 또한 clear
}
})
.permitAll() //logoutProc
);
return http.build(); // securityFilterChain 생성된다.
}
}