티스토리 뷰
인증 API - Logout
클라이언트가 서버에 로그아웃을 요청을 보내면 서버는 세션을 무효화시키고 인증 토큰을 삭제 시킨다.
이때 인증 토큰이 저장되어있는 SecurityContext 객체도 삭제한다. 쿠키정보도 삭제할 수 있고 로그아웃 이후에 리다이렉트 할 페이지를 지정할 수도 있다.
@Override
protected void configure(HttpSecurity http) throws Exception {
//로그아웃
http
.logout()
.logoutUrl("/logout")
.logoutSuccessUrl("/login")
.addLogoutHandler((request, response, authentication) -> {
HttpSession session = request.getSession();
session.invalidate();
})
.logoutSuccessHandler((request, response, authentication) -> response.sendRedirect("/login"))
.deleteCookies("remember-me");
}
스프링 시큐리티는 원칙적으로 로그아웃 처리할 때는 POST 방식으로 한다. LogoutFilter를 사용
그러나 GET 방식으로도 로그아웃할 수 있는데 이때는 SecurityContextLogoutHandler를 사용하면 된다.
@GetMapping("/logout")
public String logout(HttpServletRequest request, HttpServletResponse response) {
//실제 form 방식 POST로 로그아웃 하면 LogoutFilter를 사용해서 로그아웃을 한다.
//a 태그를 사용해서 GET으로 요청 SecurityContextLogoutHandler 활용
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null) {
new SecurityContextLogoutHandler().logout(request, response, authentication);
}
return "redirect:/login";
}
스프링 부트 시큐리티가 기본으로 제공하는 logout 페이지를 보면 POST 방식의 요청 url이 logout인걸 확인할 수 있다.
실제 로그아웃을 진행해보고 LogoutFilter를 좀만 봐보자. 로그아웃 필터는 다른것보다 굉장히 심플하다.
public class LogoutFilter extends GenericFilterBean {
private RequestMatcher logoutRequestMatcher;
private final LogoutHandler handler;
private final LogoutSuccessHandler logoutSuccessHandler;
/**
* Constructor which takes a <tt>LogoutSuccessHandler</tt> instance to determine the
* target destination after logging out. The list of <tt>LogoutHandler</tt>s are
* intended to perform the actual logout functionality (such as clearing the security
* context, invalidating the session, etc.).
*/
public LogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.notNull(logoutSuccessHandler, "logoutSuccessHandler cannot be null");
this.logoutSuccessHandler = logoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
public LogoutFilter(String logoutSuccessUrl, LogoutHandler... handlers) {
this.handler = new CompositeLogoutHandler(handlers);
Assert.isTrue(!StringUtils.hasLength(logoutSuccessUrl) || UrlUtils.isValidRedirectUrl(logoutSuccessUrl),
() -> logoutSuccessUrl + " isn't a valid redirect URL");
SimpleUrlLogoutSuccessHandler urlLogoutSuccessHandler = new SimpleUrlLogoutSuccessHandler();
if (StringUtils.hasText(logoutSuccessUrl)) {
urlLogoutSuccessHandler.setDefaultTargetUrl(logoutSuccessUrl);
}
this.logoutSuccessHandler = urlLogoutSuccessHandler;
setFilterProcessesUrl("/logout");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
}
private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (requiresLogout(request, response)) {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (this.logger.isDebugEnabled()) {
this.logger.debug(LogMessage.format("Logging out [%s]", auth));
}
this.handler.logout(request, response, auth);
this.logoutSuccessHandler.onLogoutSuccess(request, response, auth);
return;
}
chain.doFilter(request, response);
}
/**
* Allow subclasses to modify when a logout should take place.
* @param request the request
* @param response the response
* @return <code>true</code> if logout should occur, <code>false</code> otherwise
*/
protected boolean requiresLogout(HttpServletRequest request, HttpServletResponse response) {
if (this.logoutRequestMatcher.matches(request)) {
return true;
}
if (this.logger.isTraceEnabled()) {
this.logger.trace(LogMessage.format("Did not match request to %s", this.logoutRequestMatcher));
}
return false;
}
public void setLogoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
Assert.notNull(logoutRequestMatcher, "logoutRequestMatcher cannot be null");
this.logoutRequestMatcher = logoutRequestMatcher;
}
public void setFilterProcessesUrl(String filterProcessesUrl) {
this.logoutRequestMatcher = new AntPathRequestMatcher(filterProcessesUrl);
}
}
브레이킹 포인트를 잡고 LogoutHandler를 살펴보면 5개의 핸들러가 있는것을 확인할 수 있다.
5개중에 1번 인덱스부터 4번 인덱스에 있는 Handler는 스프링부트 시큐리티가 기본으로 제공하는 핸들러이고 0번째 인덱스에 있는 핸들러는 위에 configure 메소드에서 사용자가 직접 설정한 핸들러가 익명클래스 람다로 표시되고 있는것을 확인할 수 있다.
5개 핸들러가 차례대로 실행되고 로그아웃을 처리한다.
logoutHandlers에 있는 핸들러는 addLogoutHandler에서 설정한 것이고 logoutSuccessHandler는 configure에서 logoutSuccessHandler에서 설정한 것이다.
참고
- Total
- Today
- Yesterday
- window
- docker
- LocalDateTime
- Spring Security
- Mac
- LocalDate
- springboot
- maven
- 북리뷰
- mybatis config
- 오라클
- mybatis
- localtime
- config-location
- Linux
- k8s
- oracle
- intellij
- Spring
- Kotlin
- 베리 심플
- Github Status
- Bash tab
- JavaScript
- elasticsearch
- Java
- rocky
- jQuery
- svn
- input
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |