网页制作的网站,企业信用信息查询公示系统天津,织梦好还是wordpress,wordpress主机记录本章内容概览
Spring Security提供的/logout登出接口做了什么与如何自定义。Spring Authorization Server提供的/connect/logout登出接口做了什么与如何自定义。Spring Authorization Server提供的/oauth2/revoke撤销token接口做了什么与如何自定义。
前言 既然系统中有登录功…本章内容概览
Spring Security提供的/logout登出接口做了什么与如何自定义。Spring Authorization Server提供的/connect/logout登出接口做了什么与如何自定义。Spring Authorization Server提供的/oauth2/revoke撤销token接口做了什么与如何自定义。
前言 既然系统中有登录功能那么必然也会有登出的需要登出的效果一般都是清除认证相关的Session同时也要让access token无效化换而言之就是清除当前用户认证相关的状态使用户在项目中回到未认证状态因为表单登录以后存储在Session中的状态和请求携带access token访问后端时都代表着用户的认证信息有了这些认证信息才代表着用户登录过可以继续接下来的操作。
本章基本上是理论与实验性的内容编码内容很少内容可能也会比较多请大家耐心观看。
Spring Security提供的/logout
访问方式
浏览器直接访问http://127.0.0.1:8080/logout(地址栏直接输入该地址后按回车)
其中127.0.0.1是认证服务所在服务器的ip8080是认证服务对外提供服务的端口。
分析 该接口是Security提供的一个接口该接口是过滤器LogoutFilter拦截处理的在过滤器内分别调用了LogoutHandler与LogoutSuccessHandler来处理登出的逻辑和登出成功的逻辑跟踪源码可知具体处理登出的是SecurityContextLogoutHandler在该类中根据当前请求获取Http Session并销毁之后清除SecurityContextRepository中保存的认证信息之后就是登出成功的处理了处理登出成功的是SimpleUrlLogoutSuccessHandler该类中会默认重定向至defaultTargetUrl属性的值(应该为url)默认情况下启动时在加载LogoutFilter时会初始化一个SimpleUrlLogoutSuccessHandler的实例给登出过滤器使用默认会设置defaultTargetUrl属性的值为/login?logout大致逻辑就是这样。
自定义 根据上边的分析可知有两个很明显的扩展点一个是登出处理一个是登录成功处理大部分情况下只需要自定义登录成功处理即可那么该如何配置请看下方示例代码
在认证相关的过滤器中添加如下代码
// 自定义登出配置
http.logout(logout - logout// 定义登出路径.logoutUrl(/logout)// 登出时同步撤销Token.addLogoutHandler(new RevokeAccessTokenLogoutHandler(serverProperties))// 自定义登出成功响应处理.logoutSuccessHandler(new JsonLogoutSuccessHandler())
);RevokeAccessTokenLogoutHandler的具体实现
package com.example.authorization.handler;import com.example.model.Result;
import com.example.util.JsonUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.web.ServerProperties;
import org.springframework.http.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import org.springframework.web.client.RestTemplate;import java.nio.charset.StandardCharsets;/*** 登出处理-撤销token** author vains*/
Slf4j
RequiredArgsConstructor
public class RevokeAccessTokenLogoutHandler implements LogoutHandler {private final ServerProperties serverProperties;private final AuthorizationServerSettings authorizationServerSettings;private final RestTemplate restTemplate new RestTemplate();OverrideSneakyThrowspublic void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {// 获取token(可以是Access Token也可以是Refresh Token)String token request.getParameter(OAuth2ParameterNames.TOKEN);if (ObjectUtils.isEmpty(token)) {// 写回json数据ResultString result Result.error(token is empty);response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.getWriter().write(JsonUtils.objectCovertToJson(result));response.getWriter().flush();response.getWriter().close();return;}// 撤销token逻辑Integer port serverProperties.getPort();// 撤销token地址String revokeUrl http://127.0.0.1: (port null ? 8080 : port)// 默认是/oauth2/revoke authorizationServerSettings.getTokenRevocationEndpoint();MultiValueMapString, String formData new LinkedMultiValueMap();formData.add(OAuth2ParameterNames.TOKEN, token);// 准备请求的headerHttpHeaders headers new HttpHeaders();headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);headers.add(HttpHeaders.AUTHORIZATION, Basic HttpHeaders.encodeBasicAuth(messaging-client, 123456, StandardCharsets.UTF_8));// 封装请求HttpEntityMultiValueMapString, String entity new HttpEntity(formData, headers);// 撤销token默认无响应try {ResponseEntityString responseEntity restTemplate.postForEntity(revokeUrl, entity, String.class);if (responseEntity.getStatusCode().is2xxSuccessful()) {log.info(revoked access token);}} catch (Exception e) {log.warn(revoked access token failed, cause: {}, e.getMessage());// 撤销token失败写回json数据ResultString result Result.error(revoked access token failed, cause: e.getMessage());response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.getWriter().write(JsonUtils.objectCovertToJson(result));response.getWriter().flush();response.getWriter().close();}}}JsonLogoutSuccessHandler的具体实现可以在该类中响应json或自己实现重定向逻辑
package com.example.authorization.handler;import com.example.model.Result;
import com.example.util.JsonUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;import java.io.IOException;
import java.nio.charset.StandardCharsets;/*** 登出成功自定义处理** author vains*/
Slf4j
public class JsonLogoutSuccessHandler implements LogoutSuccessHandler {Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {log.info(onLogoutSuccessHandler);// 写回json数据ResultString result Result.success();response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.getWriter().write(JsonUtils.objectCovertToJson(result));response.getWriter().flush();// 或重定向至某个url// 重定向操作...略}}这样就可以在销毁当前Session的情况下撤销token了如图 未带token参数示例如图 现在就完成了销毁Session与撤销token的步骤。
Spring Authorization Server提供的/connect/logout
该接口是Openid Connect 1.0 协议中规定的一个接口。
访问方式
浏览器直接访问http://127.0.0.1:8080/connect/logout?id_token_hint{id_token}post_logout_redirect_uri{客户端信息中配置的post_logout_redirect_uri}(地址栏直接输入该地址后按回车)
参数说明
参数描述描述id_token_hintOAuth2授权登录以后获取的id token协议中是推荐传入但是在Authorization Server中必填post_logout_redirect_uri客户端信息中配置的post_logout_redirect_uri登出后重定向的地址不传会走默认登出处理
更多参数说明详见spec openid-connect-rpinitiated-1_0 RPLogout
其中127.0.0.1是认证服务所在服务器的ip8080是认证服务对外提供服务的端口。
分析 该接口是Spring Authorization Server基于Openid Connect 1.0协议提供的一个OIDC登出接口接口由OidcLogoutEndpointFilter拦截处理在拦截器中先调用OidcLogoutAuthenticationConverter获取请求入参并转成OidcLogoutAuthenticationToken返回之后过滤器再调用AuthenticationManager(ProviderManager)来做登出处理根据OidcLogoutAuthenticationToken最终由OidcLogoutAuthenticationProvider处理具体的登出逻辑在该provider中没有针对Session或者access token做什么操作先根据id_token_hint参数查询oauth2登录生成的认证信息之后针对认证信息与Session做一些校验限制了必须是登录时的Session才可以调用登出之后就是登出成功处理了上述所说的登出后跳转路径以及销毁当前Session都是在这个登出成功处理中操作的如果当前Session存在并且获取到表单登录生成的认证信息则调用SecurityContextLogoutHandler处理这个和上边的/logout登出使用的LogoutHandler一样逻辑是一样的都是销毁当前Session与认证信息接下来是跳转的操作如果请求/connect/logout时携带了post_logout_redirect_uri参数则会跳转到该url如果没有携带该参数则会通过SimpleUrlLogoutSuccessHandler跳转到根目录。
自定义 通过上述流程可以发现这个是与/oauth2/token和/oauth2/authorize类似的一个接口或者说Spring Authorization Server提供的内置端点都是这个流程支持自定义的地方基本就是Converter、Provider、成功响应和失败响应处理详见文档oidc-logout-endpoint
在端点的过滤器链配置中添加如下配置
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)// 开启OpenID Connect 1.0协议相关端点.oidc(oidc - oidc.logoutEndpoint(logoutEndpoint - logoutEndpoint.logoutResponseHandler((request, response, authorization) - {})))具体的实现就不写了大家知道入口以后根据自己的业务实现登出即可。
注意如果要使用这个接口并自定义logoutResponseHandler逻辑请注销Session因为默认的Converter和Provider都没有注销当前的Session
注意如果要使用这个接口并自定义logoutResponseHandler逻辑请注销Session因为默认的Converter和Provider都没有注销当前的Session
注意如果要使用这个接口并自定义logoutResponseHandler逻辑请注销Session因为默认的Converter和Provider都没有注销当前的Session
如果想在Provider中注销Session也可以也可以在里边撤销token(撤销token代码请参考上边的RevokeAccessTokenLogoutHandler)提供了很多配置入口自由度也是比较高的。
不带post_logout_redirect_uri参数访问效果会清除Session并跳转到根目录/没有放行直接跳转到登录页面了(代表着认证信息与Session都被清除了) 带post_logout_redirect_uri参数访问效果会清除Session并跳转到post_logout_redirect_uri参数的值(一个url)。 请注意这个url必须要在客户端信息配置中存在否则会提示错误 请注意这个url必须要在客户端信息配置中存在否则会提示错误 请注意这个url必须要在客户端信息配置中存在否则会提示错误 如果集成了十九章的内容将客户端信息存储在redis则改代码后重启即可如果是存储在数据库的请在重启后检查数据库中的数据是否被修改。
Spring Authorization Server提供的/oauth2/revoke
该接口是基于RFC 7009实现的一个接口。
访问方式
curl --location --globoff --request POST http://127.0.0.1:8080/oauth2/revoke?token{access token或者refresh token} \
--header Authorization: Basic bWVzc2FnaW5nLWNsaWVudDoxMjM0NTYtoken参数是想撤销的token(access token\refresh token)
其中127.0.0.1是认证服务所在服务器的ip8080是认证服务对外提供服务的端口。
访问时记得添加basic认证请求头username和password分别代表客户端的id和密钥
更多参数及参数的详细说明见RFC 7009 Revocation Request
分析 该接口是Spring Authorization Server基于OAuth2.1协议提供的一个token撤销接口接口由OAuth2TokenRevocationEndpointFilter拦截处理在过滤器内的逻辑与上边提到的OidcLogoutEndpointFilter过滤器的大致流程一致流程是通过Converter转换请求参数然后由Provider将token的状态改为无效状态最后是处理成功的handler根据源码可知是OAuth2TokenRevocationAuthenticationConverter来将撤销请求的参数转为OAuth2TokenRevocationAuthenticationToken然后ProviderManager将生成的OAuth2TokenRevocationAuthenticationToken交由OAuth2TokenRevocationAuthenticationProvider处理在该provider内将token的状态改为无效状态默认的成功处理只设置了http 200的状态码无任何响应。 这个接口在讲上边的登出接口时都有提过上边两个接口主要目的是清除认证状态与销毁当前Session但是oauth2登录生成access token/refresh token还可以继续使用所以需要调用当前接口来将token无效化。
自定义 跟上边的OidcLogoutEndpointFilter过滤器一样可以自定义Converter、Provider、成功响应和失败响应处理详见文档oauth2-token-revocation-endpoint
为避免贴大量代码我这边就只自定义一个成功响应处理在端点的过滤器链配置中添加如下代码
http.getConfigurer(OAuth2AuthorizationServerConfigurer.class)// 开启OpenID Connect 1.0协议相关端点.oidc(Customizer.withDefaults())// 自定义token撤销断点配置入口.tokenRevocationEndpoint(tokenRevocationEndpoint - tokenRevocationEndpoint// 撤销成功响应.revocationResponseHandler(new TokenRevocationSuccessHandler()));TokenRevocationSuccessHandler实现如下成功后响应json
package com.example.authorization.handler;import com.example.model.Result;
import com.example.util.JsonUtils;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.MediaType;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;import java.io.IOException;
import java.nio.charset.StandardCharsets;/*** token撤销成功处理** author vains*/
Slf4j
public class TokenRevocationSuccessHandler implements AuthenticationSuccessHandler {Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {log.info(Token revocation success);// 写回json数据ResultString result Result.success();response.setCharacterEncoding(StandardCharsets.UTF_8.name());response.setContentType(MediaType.APPLICATION_JSON_VALUE);response.getWriter().write(JsonUtils.objectCovertToJson(result));response.getWriter().flush();}
}携带access token访问测试 前后端分离中想用axios请求/logout和/connect/logout 这种方式仅适用于使用Spring Session将前后端的Session一同管理前后端共享Session以后才能使用这种方式并且初始化axios时需要添加withCredentials: true配置使其在跨域请求时带上cookie这样后端才能获取到登录时的Session并进行销毁。(未测试应该可行) 如果没有使用Spring Session将前后端的Session一同管理推荐使用文中的方式打开一个新标签页并访问登出接口因为在上边的分析中都讲过是获取当前Session并销毁如果使用axios访问那获取不到登录时的Session则会跳过销毁Session的步骤。
Jwt token为什么撤销了以后还能继续使用 因为jwt是无状态的资源服务器解析时不会从认证服务查询token的状态所以在解析时不能知道jwt token的状态如果想在撤销token以后使jwt token失效可以在redis中添加一个黑名单解析时查看jwt类型的access token是否在黑名单中以防止撤销后的token继续使用。
最后 到此就说明了两个用户登出和一个token撤销的接口里边的逻辑有些可能没有讲到但是处理请求的类以及具体逻辑的处理相关的类我基本上都贴出来大家可以熟悉一下源码然后看看怎么结合自己业务来实现登出相关的内容。
附录
新仓库地址
spec openid-connect-rpinitiated-1_0 RPLogout
oidc-logout-endpoint
RFC 7009
RFC 7009 Revocation Request
oauth2-token-revocation-endpoint