티스토리 뷰

728x90

인증 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에서 설정한 것이다.

 

참고

https://www.inflearn.com/course/%EC%BD%94%EC%96%B4-%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0/dashboard

 

스프링 시큐리티 - Spring Boot 기반으로 개발하는 Spring Security - 인프런 | 강의

초급에서 중.고급에 이르기까지 스프링 시큐리티의 기본 개념부터 API 사용법과 내부 아키텍처를 학습하게 되고 이를 바탕으로 실전 프로젝트를 완성해 나감으로써 스프링 시큐리티의 인증과

www.inflearn.com

 

728x90
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/05   »
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
글 보관함