spring authorization server oidc客户端发起登出源码分析

本文主要是介绍spring authorization server oidc客户端发起登出源码分析,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

版本

spring-security-oauth2-authorization-server:1.2.1

场景

spring authorization server OIDC协议,支持处理依赖方(客户端)发起的登出请求,注销授权服务器端的会话

流程:
客户端登出成功->跳转到授权服务端OIDC登出端点->授权服务端注销会话->跳转回客户端(可选)

源码

  • OIDC 登出端点配置器
    org.springframework.security.oauth2.server.authorization.config.annotation.web.configurers.OidcLogoutEndpointConfigurer

  • OIDC 登出请求端点过滤器
    org.springframework.security.oauth2.server.authorization.oidc.web.OidcLogoutEndpointFilter

public final class OidcLogoutEndpointFilter extends OncePerRequestFilter {// 默认的端点地址,用于处理OIDC依赖方发起的登出请求private static final String DEFAULT_OIDC_LOGOUT_ENDPOINT_URI = "/connect/logout";...// 登出处理器,实现为SecurityContextLogoutHandlerprivate final LogoutHandler logoutHandler;...// 认证处请求转换器,默认实现为OidcLogoutAuthenticationConverter	private AuthenticationConverter authenticationConverter;// 登出请求成功处理器private AuthenticationSuccessHandler authenticationSuccessHandler = this::performLogout;// 登出请求失败处理器private AuthenticationFailureHandler authenticationFailureHandler = this::sendErrorResponse;...@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)throws ServletException, IOException {if (!this.logoutEndpointMatcher.matches(request)) {filterChain.doFilter(request, response);return;}try {// 获取OIDC登出请求信息,OidcLogoutAuthenticationTokenAuthentication oidcLogoutAuthentication = this.authenticationConverter.convert(request);// 对请求信息进行认证处理Authentication oidcLogoutAuthenticationResult =this.authenticationManager.authenticate(oidcLogoutAuthentication);// 进行登出处理this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, oidcLogoutAuthenticationResult);} catch (OAuth2AuthenticationException ex) {if (this.logger.isTraceEnabled()) {this.logger.trace(LogMessage.format("Logout request failed: %s", ex.getError()), ex);}this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);} catch (Exception ex) {...// 发送失败响应this.authenticationFailureHandler.onAuthenticationFailure(request, response,new OAuth2AuthenticationException(error));}}...// 执行登出private void performLogout(HttpServletRequest request, HttpServletResponse response,Authentication authentication) throws IOException, ServletException {OidcLogoutAuthenticationToken oidcLogoutAuthentication = (OidcLogoutAuthenticationToken) authentication;// 检查激活的用户会话if (oidcLogoutAuthentication.isPrincipalAuthenticated() &&StringUtils.hasText(oidcLogoutAuthentication.getSessionId())) {// 执行登出 (清除安全上下文,废弃当前会话)this.logoutHandler.logout(request, response,(Authentication) oidcLogoutAuthentication.getPrincipal());}// 处理请求的登出后跳转地址if (oidcLogoutAuthentication.isAuthenticated() &&StringUtils.hasText(oidcLogoutAuthentication.getPostLogoutRedirectUri())) {UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(oidcLogoutAuthentication.getPostLogoutRedirectUri());String redirectUri;if (StringUtils.hasText(oidcLogoutAuthentication.getState())) {uriBuilder.queryParam(OAuth2ParameterNames.STATE,UriUtils.encode(oidcLogoutAuthentication.getState(), StandardCharsets.UTF_8));}redirectUri = uriBuilder.build(true).toUriString();		// build(true) -> Components are explicitly encodedthis.redirectStrategy.sendRedirect(request, response, redirectUri);} else {// 执行默认跳转this.logoutSuccessHandler.onLogoutSuccess(request, response,(Authentication) oidcLogoutAuthentication.getPrincipal());}}...
}
  • OIDC登出请求转换器
    org.springframework.security.oauth2.server.authorization.oidc.web.authentication.OidcLogoutAuthenticationConverter
public final class OidcLogoutAuthenticationConverter implements AuthenticationConverter {...@Overridepublic Authentication convert(HttpServletRequest request) {// 如果是GET请求获取url参数,否则获取表单参数MultiValueMap<String, String> parameters ="GET".equals(request.getMethod()) ?OAuth2EndpointUtils.getQueryParameters(request) :OAuth2EndpointUtils.getFormParameters(request);// 必要参数id_token_hint (OIDC TOKEN)String idTokenHint = parameters.getFirst("id_token_hint");if (!StringUtils.hasText(idTokenHint) ||parameters.get("id_token_hint").size() != 1) {throwError(OAuth2ErrorCodes.INVALID_REQUEST, "id_token_hint");}// 获取当前会话用户,如果当前会话没有认证信息,则使用匿名用户认证信息Authentication principal = SecurityContextHolder.getContext().getAuthentication();if (principal == null) {principal = ANONYMOUS_AUTHENTICATION;}// 获取当前会话String sessionId = null;HttpSession session = request.getSession(false);if (session != null) {sessionId = session.getId();}// 可选参数client_id (客户端ID)String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);if (StringUtils.hasText(clientId) &&parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);}// 可选参数post_logout_redirect_uri (登出授权服务器后跳转的地址,可用于跳转回客户端站点)String postLogoutRedirectUri = parameters.getFirst("post_logout_redirect_uri");if (StringUtils.hasText(postLogoutRedirectUri) &&parameters.get("post_logout_redirect_uri").size() != 1) {throwError(OAuth2ErrorCodes.INVALID_REQUEST, "post_logout_redirect_uri");}// 可选参数state (状态码)String state = parameters.getFirst(OAuth2ParameterNames.STATE);if (StringUtils.hasText(state) &&parameters.get(OAuth2ParameterNames.STATE).size() != 1) {throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);}return new OidcLogoutAuthenticationToken(idTokenHint, principal,sessionId, clientId, postLogoutRedirectUri, state);}...
}
  • OIDC 登出请求认证提供者
    org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcLogoutAuthenticationProvider
public final class OidcLogoutAuthenticationProvider implements AuthenticationProvider {...@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {OidcLogoutAuthenticationToken oidcLogoutAuthentication =(OidcLogoutAuthenticationToken) authentication;// 根据参数提交的ID TOKEN查询已存在的OAuth2认证记录OAuth2Authorization authorization = this.authorizationService.findByToken(oidcLogoutAuthentication.getIdTokenHint(), ID_TOKEN_TOKEN_TYPE);...// 获取ID TOKEN授权记录OAuth2Authorization.Token<OidcIdToken> authorizedIdToken = authorization.getToken(OidcIdToken.class);// 根据认证记录获取注册客户端信息RegisteredClient registeredClient = this.registeredClientRepository.findById(authorization.getRegisteredClientId());// 获取ID TOKENOidcIdToken idToken = authorizedIdToken.getToken();		// 校验客户端ID,是否包含在ID TOKEN订阅者清单中List<String> audClaim = idToken.getAudience();if (CollectionUtils.isEmpty(audClaim) ||!audClaim.contains(registeredClient.getClientId())) {throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.AUD);}// 如果请求中携带了客户端ID,校验是否与ID TOKEN对应客户端注册信息的ID一致if (StringUtils.hasText(oidcLogoutAuthentication.getClientId()) &&!oidcLogoutAuthentication.getClientId().equals(registeredClient.getClientId())) {throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);}// 如果请求中携带了登出后跳转地址,校验是否包含在客户端注册信息的登出后跳转地址清单中if (StringUtils.hasText(oidcLogoutAuthentication.getPostLogoutRedirectUri()) &&!registeredClient.getPostLogoutRedirectUris().contains(oidcLogoutAuthentication.getPostLogoutRedirectUri())) {throwError(OAuth2ErrorCodes.INVALID_REQUEST, "post_logout_redirect_uri");}...// 如果当前会话用户不是匿名用户,则进行用户校验if (oidcLogoutAuthentication.isPrincipalAuthenticated()) {// 校验ID TOKEN是否包含用户信息,当前用户是否与授权用户一致Authentication currentUserPrincipal = (Authentication) oidcLogoutAuthentication.getPrincipal();Authentication authorizedUserPrincipal = authorization.getAttribute(Principal.class.getName());if (!StringUtils.hasText(idToken.getSubject()) ||!currentUserPrincipal.getName().equals(authorizedUserPrincipal.getName())) {throwError(OAuth2ErrorCodes.INVALID_TOKEN, IdTokenClaimNames.SUB);}			// 校验ID TOKEN的 sid 是否与请求的会话ID一致if (StringUtils.hasText(oidcLogoutAuthentication.getSessionId())) {SessionInformation sessionInformation = findSessionInformation(currentUserPrincipal, oidcLogoutAuthentication.getSessionId());if (sessionInformation != null) {String sessionIdHash;try {sessionIdHash = createHash(sessionInformation.getSessionId());} catch (NoSuchAlgorithmException ex) {OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,"Failed to compute hash for Session ID.", null);throw new OAuth2AuthenticationException(error);}String sidClaim = idToken.getClaim("sid");if (!StringUtils.hasText(sidClaim) ||!sidClaim.equals(sessionIdHash)) {throwError(OAuth2ErrorCodes.INVALID_TOKEN, "sid");}}}}...return new OidcLogoutAuthenticationToken(idToken, (Authentication) oidcLogoutAuthentication.getPrincipal(),oidcLogoutAuthentication.getSessionId(), oidcLogoutAuthentication.getClientId(),oidcLogoutAuthentication.getPostLogoutRedirectUri(), oidcLogoutAuthentication.getState());}// 用于OidcLogoutAuthenticationToken对象的认证处理@Overridepublic boolean supports(Class<?> authentication) {return OidcLogoutAuthenticationToken.class.isAssignableFrom(authentication);}...
}

这篇关于spring authorization server oidc客户端发起登出源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/801368

相关文章

Java对象和JSON字符串之间的转换方法(全网最清晰)

《Java对象和JSON字符串之间的转换方法(全网最清晰)》:本文主要介绍如何在Java中使用Jackson库将对象转换为JSON字符串,并提供了一个简单的工具类示例,该工具类支持基本的转换功能,... 目录前言1. 引入 Jackson 依赖2. 创建 jsON 工具类3. 使用示例转换 Java 对象为

SpringBoot快速接入OpenAI大模型的方法(JDK8)

《SpringBoot快速接入OpenAI大模型的方法(JDK8)》本文介绍了如何使用AI4J快速接入OpenAI大模型,并展示了如何实现流式与非流式的输出,以及对函数调用的使用,AI4J支持JDK8... 目录使用AI4J快速接入OpenAI大模型介绍AI4J-github快速使用创建SpringBoot

Java中的Cursor使用详解

《Java中的Cursor使用详解》本文介绍了Java中的Cursor接口及其在大数据集处理中的优势,包括逐行读取、分页处理、流控制、动态改变查询、并发控制和减少网络流量等,感兴趣的朋友一起看看吧... 最近看代码,有一段代码涉及到Cursor,感觉写法挺有意思的。注意是Cursor,而不是Consumer

解决java.lang.NullPointerException问题(空指针异常)

《解决java.lang.NullPointerException问题(空指针异常)》本文详细介绍了Java中的NullPointerException异常及其常见原因,包括对象引用为null、数组元... 目录Java.lang.NullPointerException(空指针异常)NullPointer

javaScript在表单提交时获取表单数据的示例代码

《javaScript在表单提交时获取表单数据的示例代码》本文介绍了五种在JavaScript中获取表单数据的方法:使用FormData对象、手动提取表单数据、使用querySelector获取单个字... 方法 1:使用 FormData 对象FormData 是一个方便的内置对象,用于获取表单中的键值

前端知识点之Javascript选择输入框confirm用法

《前端知识点之Javascript选择输入框confirm用法》:本文主要介绍JavaScript中的confirm方法的基本用法、功能特点、注意事项及常见用途,文中通过代码介绍的非常详细,对大家... 目录1. 基本用法2. 功能特点①阻塞行为:confirm 对话框会阻塞脚本的执行,直到用户作出选择。②

SpringBoot项目注入 traceId 追踪整个请求的日志链路(过程详解)

《SpringBoot项目注入traceId追踪整个请求的日志链路(过程详解)》本文介绍了如何在单体SpringBoot项目中通过手动实现过滤器或拦截器来注入traceId,以追踪整个请求的日志链... SpringBoot项目注入 traceId 来追踪整个请求的日志链路,有了 traceId, 我们在排

Java实战之利用POI生成Excel图表

《Java实战之利用POI生成Excel图表》ApachePOI是Java生态中处理Office文档的核心工具,这篇文章主要为大家详细介绍了如何在Excel中创建折线图,柱状图,饼图等常见图表,需要的... 目录一、环境配置与依赖管理二、数据源准备与工作表构建三、图表生成核心步骤1. 折线图(Line Ch

Spring Boot 3 整合 Spring Cloud Gateway实践过程

《SpringBoot3整合SpringCloudGateway实践过程》本文介绍了如何使用SpringCloudAlibaba2023.0.0.0版本构建一个微服务网关,包括统一路由、限... 目录引子为什么需要微服务网关实践1.统一路由2.限流防刷3.登录鉴权小结引子当前微服务架构已成为中大型系统的标

Java集合中的List超详细讲解

《Java集合中的List超详细讲解》本文详细介绍了Java集合框架中的List接口,包括其在集合中的位置、继承体系、常用操作和代码示例,以及不同实现类(如ArrayList、LinkedList和V... 目录一,List的继承体系二,List的常用操作及代码示例1,创建List实例2,增加元素3,访问元