对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

对于最新的稳定版本,请使用 Spring Security 6.3.1Spring中文文档

在最终用户可以登录的应用程序中,他们也应该能够注销。Spring中文文档

默认情况下,Spring Security 会建立一个端点,因此不需要额外的代码。/logoutSpring中文文档

本节的其余部分介绍了许多用例供您考虑:Spring中文文档

了解注销的体系结构

当您包含spring-boot-starter-security依赖项或使用注解时,Spring Security将添加其注销支持,并且默认情况下会同时响应和。@EnableWebSecurityGET /logoutPOST /logoutSpring中文文档

如果请求 ,则 Spring Security 将显示注销确认页面。 除了为用户提供有价值的双重检查机制外,它还提供了一种简单的方法来向 .GET /logoutPOST /logoutSpring中文文档

在应用程序中,无需使用它来执行注销。 只要请求中存在所需的 CSRF 令牌,应用程序就可以简单地诱导注销。GET /logoutPOST /logout

如果请求 ,则它将使用一系列 LogoutHandler执行以下默认操作:POST /logoutSpring中文文档

完成后,它将执行其默认的 LogoutSuccessHandler,该 LogoutSuccessHandler 重定向到 ./login?logoutSpring中文文档

在应用程序中,无需使用它来执行注销。 只要请求中存在所需的 CSRF 令牌,应用程序就可以简单地诱导注销。GET /logoutPOST /logout

自定义注销 URI

由于 在筛选器链中显示在 AuthorizationFilter 之前,因此默认情况下无需显式允许终结点。 因此,通常只有您自己创建的自定义注销终结点才需要可访问的配置。LogoutFilter/logoutpermitAllSpring中文文档

例如,如果只想更改 Spring Security 匹配的 URI,则可以在 DSL 中按以下方式执行此操作:logoutSpring中文文档

自定义注销 Uri
http
    .logout((logout) -> logout.logoutUrl("/my/logout/uri"))
http {
    logout {
        logoutUrl = "/my/logout/uri"
    }
}
<logout logout-url="/my/logout/uri"/>

并且不需要更改授权,因为它只是调整 .LogoutFilterSpring中文文档

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

您可以使用以下方法执行此操作:authorizeHttpRequests<intercept-url>Spring中文文档

自定义注销终结点
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>

在此示例中,您告诉 to 重定向到何时完成。 并且,在 AuthorizationFilter 中显式允许终结点。LogoutFilter/my/success/endpoint/my/success/endpointSpring中文文档

但是,指定两次可能会很麻烦。 如果使用的是 Java 配置,则可以改为在注销 DSL 中设置属性,如下所示:permitAllSpring中文文档

允许自定义注销终结点
http
    .authorizeHttpRequests((authorize) -> authorize
        // ...
    )
    .logout((logout) -> logout
        .logoutSuccessUrl("/my/success/endpoint")
        .permitAll()
    )
http
    authorizeHttpRequests {
        // ...
    }
    logout {
        logoutSuccessUrl = "/my/success/endpoint"
        permitAll = true
    }

这会将所有注销 URI 添加到许可列表中。Spring中文文档

添加清理操作

如果您使用的是 Java 配置,则可以通过在 DSL 中调用该方法来添加自己的清理操作,如下所示:addLogoutHandlerlogoutSpring中文文档

自定义注销处理程序
CookieClearingLogoutHandler cookies = new CookieClearingLogoutHandler("our-custom-cookie");
http
    .logout((logout) -> logout.addLogoutHandler(cookies))
http {
    logout {
        addLogoutHandler(CookieClearingLogoutHandler("our-custom-cookie"))
    }
}
由于 LogoutHandler用于清理目的,因此它们不应引发异常。
由于 LogoutHandler 是一个函数式接口,因此您可以提供自定义接口作为 lambda。

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

例如,可以配置 CookieClearingLogoutHandler,如上所示。Spring中文文档

或者,您可以改为设置适当的配置值,如下所示:Spring中文文档

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

使用 clear-site-data 注销用户

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

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

使用 clear-site-data
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter());
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter())
http {
    logout {
        addLogoutHandler(clearSiteData)
    }
}

您向构造函数提供要清除的内容列表。ClearSiteDataHeaderWriterSpring中文文档

上述配置会清除所有站点数据,但您也可以将其配置为仅删除 cookie,如下所示:Spring中文文档

使用 clear-site-data 清除 Cookie
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.COOKIES));
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.COOKIES))
http {
    logout {
        addLogoutHandler(clearSiteData)
    }
}
由于 LogoutHandler用于清理目的,因此它们不应引发异常。
由于 LogoutHandler 是一个函数式接口,因此您可以提供自定义接口作为 lambda。
指定 cookie 不是必需的,因为 SecurityContextLogoutHandler 会通过使会话无效来删除它。JSESSIONID

自定义注销成功

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

例如,您可能只想返回状态代码,而不是重定向。 在这种情况下,您可以提供成功处理程序实例,如下所示:Spring中文文档

使用 clear-site-data 清除 Cookie
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。
由于 LogoutSuccessHandler 是一个函数式接口,因此您可以提供自定义接口作为 lambda。

创建自定义注销终结点

强烈建议您使用提供的 DSL 来配置注销。 原因之一是很容易忘记调用所需的 Spring Security 组件以确保正确和完整的注销。logoutSpring中文文档

事实上,注册自定义 LogoutHandler 通常比创建用于执行注销的 Spring MVC 端点更简单。Spring中文文档

也就是说,如果您发现自己处于需要自定义注销终结点的情况下,如下所示:Spring中文文档

自定义注销终结点
@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 以确保安全且完整的注销。 至少需要以下内容:Spring中文文档

自定义注销终结点
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"
}
未能调用 SecurityContextLogoutHandler 意味着 SecurityContext 在后续请求中仍可用,这意味着用户实际上并未注销。
未能调用 SecurityContextLogoutHandler 意味着 SecurityContext 在后续请求中仍可用,这意味着用户实际上并未注销。

测试注销

配置注销后,您可以使用 Spring Security 的 MockMvc 支持对其进行测试。Spring中文文档