处理注销

在允许最终用户登录的应用程序中,也应该允许他们注销。

默认情况下,Spring Security 会启动一个 /logout 端点,因此无需任何额外代码。

本节的其余部分涵盖了一些需要考虑的用例

了解注销的架构

当您包含spring-boot-starter-security 依赖项或使用 @EnableWebSecurity 注解时,Spring Security 将添加其注销支持,并且默认情况下会响应 GET /logoutPOST /logout

如果您请求 GET /logout,则 Spring Security 会显示注销确认页面。除了为用户提供宝贵的双重检查机制外,它还提供了一种简单的方法来提供所需的 CSRF 令牌POST /logout

请注意,如果在配置中禁用 CSRF 保护,则不会向用户显示注销确认页面,并且会直接执行注销。

在您的应用程序中,无需使用 GET /logout 执行注销。只要请求中存在所需的 CSRF 令牌,您的应用程序就可以简单地 POST /logout 以触发注销。

如果您请求 POST /logout,则它将使用一系列LogoutHandler执行以下默认操作

完成后,它将执行其默认的 LogoutSuccessHandler,该处理器重定向到 /login?logout

自定义注销 URI

由于LogoutFilter出现在 AuthorizationFilter 之前 在过滤器链中,因此默认情况下无需显式允许 /logout 端点。因此,只有您自己创建的 自定义注销端点 通常需要 permitAll 配置才能访问。

例如,如果您只想更改 Spring Security 正在匹配的 URI,可以在 logout DSL 中以如下方式进行操作

自定义注销 Uri
  • Java

  • Kotlin

  • Xml

http
    .logout((logout) -> logout.logoutUrl("/my/logout/uri"))
http {
    logout {
        logoutUrl = "/my/logout/uri"
    }
}
<logout logout-url="/my/logout/uri"/>

并且不需要进行授权更改,因为它只是调整了 LogoutFilter

但是,如果您启动自己的注销成功端点(或在极少数情况下,您自己的注销端点),例如使用 Spring MVC,则需要在 Spring Security 中允许它。这是因为 Spring MVC 在 Spring Security 处理请求之后处理您的请求。

您可以使用 authorizeHttpRequests<intercept-url> 如下所示进行操作

自定义注销端点
  • Java

  • Kotlin

  • Xml

http
    .authorizeHttpRequests((authorize) -> authorize
        .requestMatchers("/my/success/endpoint").permitAll()
        // ...
    )
    .logout((logout) -> logout.logoutSuccessUrl("/my/success/endpoint"))
http {
    authorizeHttpRequests {
        authorize("/my/success/endpoint", permitAll)
    }
    logout {
        logoutSuccessUrl = "/my/success/endpoint"
    }
}
<http>
    <filter-url pattern="/my/success/endpoint" access="permitAll"/>
    <logout logout-success-url="/my/success/endpoint"/>
</http>

在此示例中,您告诉 LogoutFilter 在完成时重定向到 /my/success/endpoint。并且,您在 AuthorizationFilter 中显式允许 /my/success/endpoint 端点。

虽然指定两次可能会很麻烦。如果您使用 Java 配置,则可以改为在注销 DSL 中设置 permitAll 属性,如下所示

允许自定义注销端点
  • Java

  • Kotlin

http
    .authorizeHttpRequests((authorize) -> authorize
        // ...
    )
    .logout((logout) -> logout
        .logoutSuccessUrl("/my/success/endpoint")
        .permitAll()
    )
http
    authorizeHttpRequests {
        // ...
    }
    logout {
        logoutSuccessUrl = "/my/success/endpoint"
        permitAll = true
    }

这将为您将所有注销 URI 添加到允许列表中。

添加清理操作

如果您使用 Java 配置,可以通过在 logout DSL 中调用 addLogoutHandler 方法来添加您自己的清理操作,如下所示

自定义注销处理器
  • Java

  • Kotlin

CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
    .logout((logout) -> logout.addLogoutHandler(cookies))
http {
    logout {
        addLogoutHandler(CookieClearingLogoutHandler("our-custom-cookie"))
    }
}
因为 LogoutHandler 用于清理目的,所以不应抛出异常。
由于 LogoutHandler 是一个函数式接口,因此您可以将其作为 lambda 提供自定义接口。

某些注销处理器配置非常常见,因此它们直接在 logout DSL 和 <logout> 元素中公开。一个示例是配置会话失效,另一个示例是应删除哪些其他 Cookie。

例如,您可以配置 CookieClearingLogoutHandler,如上所示。

或者,您可以改为设置相应的配置值,如下所示

  • Java

  • Kotlin

  • Xml

http
    .logout((logout) -> logout.deleteCookies("our-custom-cookie"))
http {
    logout {
        deleteCookies = "our-custom-cookie"
    }
}
<http>
    <logout delete-cookies="our-custom-cookie"/>
</http>
指定 JSESSIONID Cookie 不需要,因为 SecurityContextLogoutHandler 通过使会话失效来删除它。

使用 Clear-Site-Data 注销用户

Clear-Site-Data HTTP 标头是浏览器支持的一种标头,作为清除属于拥有网站的 Cookie、存储和缓存的指令。这是一种方便且安全的方法,可确保在注销时清理所有内容,包括会话 Cookie。

您可以添加配置 Spring Security 以在注销时写入 Clear-Site-Data 标头,如下所示

使用 Clear-Site-Data
  • Java

  • Kotlin

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
http {
    logout {
        addLogoutHandler(clearSiteData)
    }
}

您为 ClearSiteDataHeaderWriter 构造函数提供要清除的内容列表。

上述配置清除了所有站点数据,但您也可以将其配置为仅删除 Cookie,如下所示

使用 Clear-Site-Data 清除 Cookie
  • Java

  • Kotlin

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.COOKIES))
http {
    logout {
        addLogoutHandler(clearSiteData)
    }
}

自定义注销成功

虽然在大多数情况下使用 logoutSuccessUrl 就足够了,但您可能需要在注销完成后执行与重定向到 URL 不同的操作。 LogoutSuccessHandler 是用于自定义注销成功操作的 Spring Security 组件。

例如,您可能希望仅返回状态代码,而不是重定向。在这种情况下,您可以提供一个成功处理程序实例,如下所示

使用 Clear-Site-Data 清除 Cookie
  • Java

  • Kotlin

  • Xml

http
    .logout((logout) -> logout.logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler()))
http {
    logout {
        logoutSuccessHandler = HttpStatusReturningLogoutSuccessHandler()
    }
}
<bean name="mySuccessHandlerBean" class="org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler"/>
<http>
    <logout success-handler-ref="mySuccessHandlerBean"/>
</http>
由于 LogoutSuccessHandler 是一个函数式接口,因此您可以将其作为 lambda 提供自定义接口。

创建自定义注销端点

强烈建议您使用提供的 logout DSL 配置注销。原因之一是,很容易忘记调用必要的 Spring Security 组件以确保正确且完整的注销。

事实上,通常比为执行注销创建 Spring MVC 端点更简单 注册自定义 LogoutHandler

也就是说,如果您发现自己处于需要自定义注销端点的情况下,例如以下端点

自定义注销端点
  • Java

  • Kotlin

@PostMapping("/my/logout")
public String performLogout() {
    // .. perform logout
    return "redirect:/home";
}
@PostMapping("/my/logout")
fun performLogout(): String {
    // .. perform logout
    return "redirect:/home"
}

那么您需要让该端点调用 Spring Security 的 SecurityContextLogoutHandler 以确保安全且完整的注销。至少需要以下内容

自定义注销端点
  • Java

  • Kotlin

SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();

@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
    // .. perform logout
    this.logoutHandler.doLogout(request, response, authentication);
    return "redirect:/home";
}
val logoutHandler = SecurityContextLogoutHandler()

@PostMapping("/my/logout")
fun performLogout(val authentication: Authentication, val request: HttpServletRequest, val response: HttpServletResponse): String {
    // .. perform logout
    this.logoutHandler.doLogout(request, response, authentication)
    return "redirect:/home"
}

这样将根据需要清除 SecurityContextHolderStrategySecurityContextRepository

此外,您需要 显式允许端点

未能调用 SecurityContextLogoutHandler 意味着 SecurityContext 仍然可能在后续请求中可用,这意味着用户实际上并没有注销。

测试注销

配置完注销后,您可以使用 Spring Security 的 MockMvc 支持 测试它。

其他与注销相关的参考