Spring Security Config : HttpSecurity安全配置器 LogoutConfigurer

概述

介绍

作为一个配置HttpSecuritySecurityConfigurer,LogoutConfigurer的配置任务如下 :

  • 配置如下安全过滤器Filter

    • LogoutFilter
      1. 会根据所设置的logoutUrl,logoutRequestMatcher,以及配置器CsrfConfigurer中是否启用csrf保护等信息构建最终所使用的退出LogoutRequestMatcher,也就是触发退出的url匹配器。
      2. 会根据所设置的属性logoutSuccessHandler,defaultLogoutSuccessHandlerMappings,logoutSuccessUrl确定最终要使用LogoutSuccessHandler,它们三个之间的应用优先级是 : logoutSuccessHandler > defaultLogoutSuccessHandlerMappings > logoutSuccessUrl
      3. LogoutConfigurer所被设置的所有LogoutHandler都会被应用,最后一个LogoutHandler总是LogoutConfigurer自己提供的一个SecurityContextLogoutHandler,该SecurityContextLogoutHandler也可以被调用者设置。
  • 创建如下共享对象

    • (无)

继承关系

Spring Security Config : HttpSecurity安全配置器 LogoutConfigurer_第1张图片

使用

	// HttpSecurity 代码片段
	public LogoutConfigurer<HttpSecurity> logout() throws Exception {
		return getOrApply(new LogoutConfigurer<>());
	}

源代码

源代码版本 Spring Security Config 5.1.4.RELEASE

package org.springframework.security.config.annotation.web.configurers;

// 省略 imports

public final class LogoutConfigurer<H extends HttpSecurityBuilder<H>> extends
		AbstractHttpConfigurer<LogoutConfigurer<H>, H> {
    // 退出时要执行的处理器,可以有多个, contextLogoutHandler 总是最后一个
	private List<LogoutHandler> logoutHandlers = new ArrayList<>();
	private SecurityContextLogoutHandler contextLogoutHandler = new SecurityContextLogoutHandler();
    // 退出成功时跳转的页面,缺省使用 /login?logout
	private String logoutSuccessUrl = "/login?logout";
    // 退出成功时的处理器,如果该属性被设置,logoutSuccessUrl 的值会被忽略
	private LogoutSuccessHandler logoutSuccessHandler;
    // 触发退出登录的url,缺省使用 /logout
    // 如果是使用了 csrf 保护,访问该地址必须使用 POST, 如果没有使用 csrf 保护, 也可以使用 GET,
    // PUT,DELETE
    // logoutUrl 和 logoutRequestMatcher 通常二选其一使用,最佳实践建议使用 logoutUrl
	private String logoutUrl = "/logout";
    // 触发退出登录的 RequestMatcher, 缺省为 null
    // logoutUrl 和 logoutRequestMatcher 通常二选其一使用,最佳实践建议使用 logoutUrl
	private RequestMatcher logoutRequestMatcher;
	private boolean permitAll;
    // 如果 logoutSuccessHandler 被设置,或者 logoutSuccessUrl 被设置为非缺省值,
    // customLogoutSuccess 为 true
	private boolean customLogoutSuccess;

	private LinkedHashMap<RequestMatcher, LogoutSuccessHandler> defaultLogoutSuccessHandlerMappings 
					= new LinkedHashMap<>();

	/**
	 * Creates a new instance
	 * @see HttpSecurity#logout()
	 */
	public LogoutConfigurer() {
	}

	/**
	 * Adds a LogoutHandler. The SecurityContextLogoutHandler is added as
	 * the last LogoutHandler by default.
	 *
	 * @param logoutHandler the LogoutHandler to add
	 * @return the LogoutConfigurer for further customization
	 */
	public LogoutConfigurer<H> addLogoutHandler(LogoutHandler logoutHandler) {
		Assert.notNull(logoutHandler, "logoutHandler cannot be null");
		this.logoutHandlers.add(logoutHandler);
		return this;
	}

	/**
	 * Specifies if SecurityContextLogoutHandler should clear the Authentication at 
	 * the time of logout.
	 * @param clearAuthentication true SecurityContextLogoutHandler should clear the 
	 * 		Authentication (default), or false otherwise.
	 * @return the LogoutConfigurer for further customization
	 */
	public LogoutConfigurer<H> clearAuthentication(boolean clearAuthentication) {
		contextLogoutHandler.setClearAuthentication(clearAuthentication);
		return this;
	}


	/**
	 * Configures SecurityContextLogoutHandler to invalidate the
	 * HttpSession at the time of logout.
	 * @param invalidateHttpSession true if the HttpSession should be invalidated
	 * (default), or false otherwise.
	 * @return the LogoutConfigurer for further customization
	 */
	public LogoutConfigurer<H> invalidateHttpSession(boolean invalidateHttpSession) {
		contextLogoutHandler.setInvalidateHttpSession(invalidateHttpSession);
		return this;
	}

	/**
	 * The URL that triggers log out to occur (default is "/logout"). If CSRF protection
	 * is enabled (default), then the request must also be a POST. This means that by
	 * default POST "/logout" is required to trigger a log out. If CSRF protection is
	 * disabled, then any HTTP method is allowed.
	 *
	 * 
	 * It is considered best practice to use an HTTP POST on any action that changes state
	 * (i.e. log out) to protect against CSRF attacks. If
	 * you really want to use an HTTP GET, you can use
	 * logoutRequestMatcher(new AntPathRequestMatcher(logoutUrl, "GET"));
	 * 
	 *
	 * @see #logoutRequestMatcher(RequestMatcher)
	 * @see HttpSecurity#csrf()
	 *
	 * @param logoutUrl the URL that will invoke logout.
	 * @return the LogoutConfigurer for further customization
	 */
	public LogoutConfigurer<H> logoutUrl(String logoutUrl) {
       // 使用 logoutUrl 的时候将 logoutRequestMatcher 设置为 null
		this.logoutRequestMatcher = null;
		this.logoutUrl = logoutUrl;
		return this;
	}

	/**
	 * The RequestMatcher that triggers log out to occur. In most circumstances users will
	 * use #logoutUrl(String) which helps enforce good practices.
	 *
	 * @see #logoutUrl(String)
	 *
	 * @param logoutRequestMatcher the RequestMatcher used to determine if logout should
	 * occur.
	 * @return the LogoutConfigurer for further customization
	 */
	public LogoutConfigurer<H> logoutRequestMatcher(RequestMatcher logoutRequestMatcher) {
		this.logoutRequestMatcher = logoutRequestMatcher;
		return this;
	}

	/**
	 * The URL to redirect to after logout has occurred. The default is "/login?logout".
	 * This is a shortcut for invoking #logoutSuccessHandler(LogoutSuccessHandler)
	 * with a SimpleUrlLogoutSuccessHandler.
	 *
	 * @param logoutSuccessUrl the URL to redirect to after logout occurred
	 * @return the LogoutConfigurer for further customization
	 */
	public LogoutConfigurer<H> logoutSuccessUrl(String logoutSuccessUrl) {
		this.customLogoutSuccess = true;
		this.logoutSuccessUrl = logoutSuccessUrl;
		return this;
	}

	/**
	 * A shortcut for #permitAll(boolean) with true as an argument.
	 * @return the LogoutConfigurer for further customizations
	 */
	public LogoutConfigurer<H> permitAll() {
		return permitAll(true);
	}

	/**
	 * Allows specifying the names of cookies to be removed on logout success. This is a
	 * shortcut to easily invoke #addLogoutHandler(LogoutHandler) with a
	 * CookieClearingLogoutHandler.
	 *
	 * @param cookieNamesToClear the names of cookies to be removed on logout success.
	 * @return the LogoutConfigurer for further customization
	 */
	public LogoutConfigurer<H> deleteCookies(String... cookieNamesToClear) {
       // 设置退出成功时要删除的cookie的名称
       // 其实是添加了一个退出处理器 CookieClearingLogoutHandler
		return addLogoutHandler(new CookieClearingLogoutHandler(cookieNamesToClear));
	}

	/**
	 * Sets the LogoutSuccessHandler to use. If this is specified,
	 * #logoutSuccessUrl(String) is ignored.
	 *
	 * @param logoutSuccessHandler the LogoutSuccessHandler to use after a user
	 * has been logged out.
	 * @return the LogoutConfigurer for further customizations
	 */
	public LogoutConfigurer<H> logoutSuccessHandler(
			LogoutSuccessHandler logoutSuccessHandler) {
		this.logoutSuccessUrl = null; // 设置 logoutSuccessUrl 为 null
		this.customLogoutSuccess = true;
		this.logoutSuccessHandler = logoutSuccessHandler;
		return this;
	}

	/**
	 * Sets a default LogoutSuccessHandler to be used which prefers being invoked
	 * for the provided RequestMatcher. If no LogoutSuccessHandler is
	 * specified a SimpleUrlLogoutSuccessHandler will be used.
	 * If any default LogoutSuccessHandler instances are configured, then a
	 * DelegatingLogoutSuccessHandler will be used that defaults to a
	 * SimpleUrlLogoutSuccessHandler.
	 *
	 * @param handler the LogoutSuccessHandler to use
	 * @param preferredMatcher the RequestMatcher for this default
	 * LogoutSuccessHandler
	 * @return the LogoutConfigurer for further customizations
	 */
	public LogoutConfigurer<H> defaultLogoutSuccessHandlerFor(
			LogoutSuccessHandler handler, RequestMatcher preferredMatcher) {
		Assert.notNull(handler, "handler cannot be null");
		Assert.notNull(preferredMatcher, "preferredMatcher cannot be null");
		this.defaultLogoutSuccessHandlerMappings.put(preferredMatcher, handler);
		return this;
	}

	/**
	 * Grants access to the #logoutSuccessUrl(String) and the
	 * #logoutUrl(String) for every user.
	 *
     * 对 logoutSuccessUrl, logoutUrl 的用户访问权限进行设置
	 * @param permitAll if true grants access, else nothing is done
	 * @return the LogoutConfigurer for further customization.
	 */
	public LogoutConfigurer<H> permitAll(boolean permitAll) {
		this.permitAll = permitAll;
		return this;
	}

	/**
	 * Gets the LogoutSuccessHandler if not null, otherwise creates a new
	 * SimpleUrlLogoutSuccessHandler using the #logoutSuccessUrl(String).
	 * 如果设置了 logoutSuccessHandler,则使用 logoutSuccessHandler, 否则
     * 基于 logoutSuccessUrl 和 defaultLogoutSuccessHandlerMappings 创建缺省的 LogoutSuccessHandler
     * 并最终使用
	 * @return the LogoutSuccessHandler to use
	 */
	private LogoutSuccessHandler getLogoutSuccessHandler() {
		LogoutSuccessHandler handler = this.logoutSuccessHandler;
		if (handler == null) {
			handler = createDefaultSuccessHandler();
		}
		return handler;
	}

    // 基于 logoutSuccessUrl 和 defaultLogoutSuccessHandlerMappings 创建缺省的 LogoutSuccessHandler
    // 1. 如果 defaultLogoutSuccessHandlerMappings 不为空,则使用基于它的 DelegatingLogoutSuccessHandler
    // 2. 如果 defaultLogoutSuccessHandlerMappings 为空,使用基于 logoutSuccessUrl 创建的
    //    SimpleUrlLogoutSuccessHandler    
	private LogoutSuccessHandler createDefaultSuccessHandler() {
		SimpleUrlLogoutSuccessHandler urlLogoutHandler = new SimpleUrlLogoutSuccessHandler();
		urlLogoutHandler.setDefaultTargetUrl(logoutSuccessUrl);
		if (defaultLogoutSuccessHandlerMappings.isEmpty()) {
			return urlLogoutHandler;
		}
		DelegatingLogoutSuccessHandler successHandler = 
			new DelegatingLogoutSuccessHandler(defaultLogoutSuccessHandlerMappings);
		successHandler.setDefaultLogoutSuccessHandler(urlLogoutHandler);
		return successHandler;
	}

	@Override
	public void init(H http) throws Exception {        
        // 根据 permitAll 属性,对 HttpSecurity 构建器 http 进行设置如下 url 放行设置 :
       // 1. this.logoutSuccessUrl : 退出成功时所跳转的 url
       // 2. this.getLogoutRequestMatcher(http) 所匹配的 url : 触发退出的url
		if (permitAll) {
			PermitAllSupport.permitAll(http, this.logoutSuccessUrl);
			PermitAllSupport.permitAll(http, this.getLogoutRequestMatcher(http));
		}

       // 对  DefaultLoginPageGeneratingFilter 进行补充设置
		DefaultLoginPageGeneratingFilter loginPageGeneratingFilter = http
				.getSharedObject(DefaultLoginPageGeneratingFilter.class);
		if (loginPageGeneratingFilter != null && !isCustomLogoutSuccess()) {
        // 如果 DefaultLoginPageGeneratingFilter 存在于共享对象,并且
        // 退出成功url没有使用缺省值的情况下,对 DefaultLoginPageGeneratingFilter
        // 进行设置属性 logoutSuccessUrl
			loginPageGeneratingFilter.setLogoutSuccessUrl(getLogoutSuccessUrl());
		}
	}

	@Override
	public void configure(H http) throws Exception {
       // 创建 LogoutFilter 过滤器添加到 HttpSecurity http
		LogoutFilter logoutFilter = createLogoutFilter(http);
		http.addFilter(logoutFilter);
	}

	/**
	 * Returns true if the logout success has been customized via
	 * #logoutSuccessUrl(String) or
	 * #logoutSuccessHandler(LogoutSuccessHandler).
	 *
	 * @return true if logout success handling has been customized, else false
	 */
	boolean isCustomLogoutSuccess() {
		return customLogoutSuccess;
	}

	/**
	 * Gets the logoutSuccesUrl or null if a
	 * #logoutSuccessHandler(LogoutSuccessHandler) was configured.
	 *
	 * @return the logoutSuccessUrl
	 */
	private String getLogoutSuccessUrl() {
		return logoutSuccessUrl;
	}

	/**
	 * Gets the LogoutHandler instances that will be used.
	 * @return the LogoutHandler instances. Cannot be null.
	 */
	List<LogoutHandler> getLogoutHandlers() {
		return logoutHandlers;
	}

	/**
	 * Creates the LogoutFilter using the LogoutHandler instances, the
	 * #logoutSuccessHandler(LogoutSuccessHandler) and the
	 * #logoutUrl(String).
	 *
	 * @param http the builder to use
	 * @return the LogoutFilter to use.
	 * @throws Exception
	 */
	private LogoutFilter createLogoutFilter(H http) throws Exception {
       // 这里确保 contextLogoutHandler 总是所有 logoutHandlers 中的最后一个
		logoutHandlers.add(contextLogoutHandler);
		LogoutHandler[] handlers = logoutHandlers
				.toArray(new LogoutHandler[logoutHandlers.size()]);
                
       // 构造  LogoutFilter 并返回
		LogoutFilter result = new LogoutFilter(getLogoutSuccessHandler(), handlers);
       // 根据属性  logoutRequestMatcher, url 设置, csrf 保护设置构建最终使用的
       // logoutRequestMatcher 并设置到 LogoutFilter 上
		result.setLogoutRequestMatcher(getLogoutRequestMatcher(http));
		result = postProcess(result);
		return result;
	}

    // 根据设置构建最终使用的 logoutRequestMatcher: 
    // 1. 如果设置了属性 logoutRequestMatcher , 则使用属性 logoutRequestMatcher,否则
    // 2. 如果启用了 csrf 保护,使用 logoutUrl, POST 构建一个 AntPathRequestMatcher 
    //    到 logoutRequestMatcher 属性并最终使用,
    // 3. 如果没有启用 csrf 保护,使用 logoutUrl,GET/POST/PUT/DELETE 构造一个 
    //   OrRequestMatcher 到 logoutRequestMatcher 属性并最终使用,
	@SuppressWarnings("unchecked")
	private RequestMatcher getLogoutRequestMatcher(H http) {
		if (logoutRequestMatcher != null) {
			return logoutRequestMatcher;
		}
		if (http.getConfigurer(CsrfConfigurer.class) != null) {
			this.logoutRequestMatcher = new AntPathRequestMatcher(this.logoutUrl, "POST");
		}
		else {
			this.logoutRequestMatcher = new OrRequestMatcher(
				new AntPathRequestMatcher(this.logoutUrl, "GET"),
				new AntPathRequestMatcher(this.logoutUrl, "POST"),
				new AntPathRequestMatcher(this.logoutUrl, "PUT"),
				new AntPathRequestMatcher(this.logoutUrl, "DELETE")
			);
		}
		return this.logoutRequestMatcher;
	}
}

参考文章

你可能感兴趣的:(Spring,Security,分析)