处理注销

在允许最终用户登录的应用程序中,他们也应该能够注销。spring-doc.cadn.net.cn

默认情况下,Spring Security 会自动创建一个 /logout 端点,因此无需额外编写代码。spring-doc.cadn.net.cn

本节其余部分涵盖了一些供您参考的使用场景:spring-doc.cadn.net.cn

了解登出架构

当你包含 spring-boot-starter-security 依赖项 或使用 @EnableWebSecurity 注解时,Spring Security 将添加其注销支持,并默认响应 GET /logoutPOST /logoutspring-doc.cadn.net.cn

如果你请求 GET /logout,Spring Security 会显示一个注销确认页面。 除了为用户提供有价值的二次确认机制外,它还提供了一种简单的方式,将所需的 CSRF Tokens传递给 POST /logoutspring-doc.cadn.net.cn

请注意,如果在配置中禁用了CSRF 保护,则不会向用户显示注销确认页面,而是直接执行注销操作。spring-doc.cadn.net.cn

在您的应用程序中,无需使用 GET /logout 来执行注销操作。 只要所需的 CSRF Tokens存在于请求中,您的应用程序只需向 POST /logout 发起请求即可触发注销。

如果您请求 POST /logout,它将使用一系列 LogoutHandler 实例执行以下默认操作:spring-doc.cadn.net.cn

完成后,它将执行其默认的 LogoutSuccessHandler,该操作将重定向到 /login?logoutspring-doc.cadn.net.cn

自定义登出 URI

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

例如,如果你只是想更改 Spring Security 所匹配的 URI,可以通过以下方式在 logout DSL 中进行设置:spring-doc.cadn.net.cn

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

并且无需进行任何授权更改,因为它只是调整了 LogoutFilterspring-doc.cadn.net.cn

然而,如果你自己配置了一个登出成功端点(或在极少数情况下,你自己的登出端点),例如使用Spring MVC,你就需要在 Spring Security 中允许该端点的访问。 这是因为 Spring MVC 在 Spring Security 之后处理你的请求。spring-doc.cadn.net.cn

你可以使用 authorizeHttpRequests<intercept-url> 来实现,如下所示:spring-doc.cadn.net.cn

自定义注销端点
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 端点。spring-doc.cadn.net.cn

不过,指定两次可能会有些繁琐。 如果你使用的是 Java 配置,可以改为在注销(logout)DSL 中设置 permitAll 属性,如下所示:spring-doc.cadn.net.cn

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

这将自动将所有注销 URI 添加到您的许可列表中。spring-doc.cadn.net.cn

添加清理操作

如果你使用的是 Java 配置,可以通过在 addLogoutHandler DSL 中调用 logout 方法来添加你自己的清理操作,如下所示:spring-doc.cadn.net.cn

自定义注销处理器
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。spring-doc.cadn.net.cn

例如,您可以如上配置 CookieClearingLogoutHandlerspring-doc.cadn.net.cn

或者,你也可以像这样设置相应的配置值:spring-doc.cadn.net.cn

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-doc.cadn.net.cn

你可以配置 Spring Security,使其在用户登出时写入 Clear-Site-Data 响应头,如下所示:spring-doc.cadn.net.cn

使用 Clear-Site-Data
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(Directive.ALL));
http
    .logout((logout) -> logout.addLogoutHandler(clearSiteData))
val clearSiteData = HeaderWriterLogoutHandler(ClearSiteDataHeaderWriter(Directive.ALL))
http {
    logout {
        addLogoutHandler(clearSiteData)
    }
}

你将希望清除的内容列表传递给 ClearSiteDataHeaderWriter 构造函数。spring-doc.cadn.net.cn

上述配置会清除所有站点数据,但你也可以将其配置为仅删除 Cookie,如下所示:spring-doc.cadn.net.cn

使用 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)
    }
}

自定义登出成功

在使用 logoutSuccessUrl 通常足以满足大多数情况,但您可能需要在登出完成后执行不同于重定向到 URL 的操作。 LogoutSuccessHandler 是 Spring Security 中用于自定义登出成功操作的组件。spring-doc.cadn.net.cn

例如,你可能不想执行重定向,而只想返回一个状态码。 在这种情况下,你可以提供一个成功处理器(success handler)实例,如下所示:spring-doc.cadn.net.cn

自定义注销成功后返回的 HTTP 状态码
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-doc.cadn.net.cn

事实上,通常更简单的方法是注册自定义LogoutHandler,而不是为执行注销操作创建一个Spring MVC端点。spring-doc.cadn.net.cn

话虽如此,但如果你发现自己处于需要自定义注销端点的情况,例如以下这种:spring-doc.cadn.net.cn

自定义注销端点
@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-doc.cadn.net.cn

自定义注销端点
SecurityContextLogoutHandler logoutHandler = new SecurityContextLogoutHandler();

@PostMapping("/my/logout")
public String performLogout(Authentication authentication, HttpServletRequest request, HttpServletResponse response) {
    // .. perform logout
    this.logoutHandler.logout(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.logout(request, response, authentication)
    return "redirect:/home"
}
未能调用 SecurityContextLogoutHandler 意味着 SecurityContext 仍可能在后续请求中可用,这意味着用户实际上并未注销。

测试注销

一旦你配置好了注销功能,就可以使用Spring Security 的 MockMvc 支持对其进行测试。spring-doc.cadn.net.cn