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 Stream 并行流简介、使用与注意事项小结

《JavaStream并行流简介、使用与注意事项小结》Java8并行流基于StreamAPI,利用多核CPU提升计算密集型任务效率,但需注意线程安全、顺序不确定及线程池管理,可通过自定义线程池与C... 目录1. 并行流简介​特点:​2. 并行流的简单使用​示例:并行流的基本使用​3. 配合自定义线程池​示

从原理到实战解析Java Stream 的并行流性能优化

《从原理到实战解析JavaStream的并行流性能优化》本文给大家介绍JavaStream的并行流性能优化:从原理到实战的全攻略,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的... 目录一、并行流的核心原理与适用场景二、性能优化的核心策略1. 合理设置并行度:打破默认阈值2. 避免装箱

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

Java Kafka消费者实现过程

《JavaKafka消费者实现过程》Kafka消费者通过KafkaConsumer类实现,核心机制包括偏移量管理、消费者组协调、批量拉取消息及多线程处理,手动提交offset确保数据可靠性,自动提交... 目录基础KafkaConsumer类分析关键代码与核心算法2.1 订阅与分区分配2.2 拉取消息2.3

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

Python 基于http.server模块实现简单http服务的代码举例

《Python基于http.server模块实现简单http服务的代码举例》Pythonhttp.server模块通过继承BaseHTTPRequestHandler处理HTTP请求,使用Threa... 目录测试环境代码实现相关介绍模块简介类及相关函数简介参考链接测试环境win11专业版python

Java中HashMap的用法详细介绍

《Java中HashMap的用法详细介绍》JavaHashMap是一种高效的数据结构,用于存储键值对,它是基于哈希表实现的,提供快速的插入、删除和查找操作,:本文主要介绍Java中HashMap... 目录一.HashMap1.基本概念2.底层数据结构:3.HashCode和equals方法为什么重写Has

Java 正则表达式的使用实战案例

《Java正则表达式的使用实战案例》本文详细介绍了Java正则表达式的使用方法,涵盖语法细节、核心类方法、高级特性及实战案例,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录一、正则表达式语法详解1. 基础字符匹配2. 字符类([]定义)3. 量词(控制匹配次数)4. 边

Java Scanner类解析与实战教程

《JavaScanner类解析与实战教程》JavaScanner类(java.util包)是文本输入解析工具,支持基本类型和字符串读取,基于Readable接口与正则分隔符实现,适用于控制台、文件输... 目录一、核心设计与工作原理1.底层依赖2.解析机制A.核心逻辑基于分隔符(delimiter)和模式匹

Java中的stream流分组示例详解

《Java中的stream流分组示例详解》Java8StreamAPI以函数式风格处理集合数据,支持分组、统计等操作,可按单/多字段分组,使用String、Map.Entry或Java16record... 目录什么是stream流1、根据某个字段分组2、按多个字段分组(组合分组)1、方法一:使用 Stri