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

相关文章

SpringBoot结合Docker进行容器化处理指南

《SpringBoot结合Docker进行容器化处理指南》在当今快速发展的软件工程领域,SpringBoot和Docker已经成为现代Java开发者的必备工具,本文将深入讲解如何将一个SpringBo... 目录前言一、为什么选择 Spring Bootjavascript + docker1. 快速部署与

MySQL中的LENGTH()函数用法详解与实例分析

《MySQL中的LENGTH()函数用法详解与实例分析》MySQLLENGTH()函数用于计算字符串的字节长度,区别于CHAR_LENGTH()的字符长度,适用于多字节字符集(如UTF-8)的数据验证... 目录1. LENGTH()函数的基本语法2. LENGTH()函数的返回值2.1 示例1:计算字符串

Spring Boot spring-boot-maven-plugin 参数配置详解(最新推荐)

《SpringBootspring-boot-maven-plugin参数配置详解(最新推荐)》文章介绍了SpringBootMaven插件的5个核心目标(repackage、run、start... 目录一 spring-boot-maven-plugin 插件的5个Goals二 应用场景1 重新打包应用

SpringBoot+EasyExcel实现自定义复杂样式导入导出

《SpringBoot+EasyExcel实现自定义复杂样式导入导出》这篇文章主要为大家详细介绍了SpringBoot如何结果EasyExcel实现自定义复杂样式导入导出功能,文中的示例代码讲解详细,... 目录安装处理自定义导出复杂场景1、列不固定,动态列2、动态下拉3、自定义锁定行/列,添加密码4、合并

Spring Boot集成Druid实现数据源管理与监控的详细步骤

《SpringBoot集成Druid实现数据源管理与监控的详细步骤》本文介绍如何在SpringBoot项目中集成Druid数据库连接池,包括环境搭建、Maven依赖配置、SpringBoot配置文件... 目录1. 引言1.1 环境准备1.2 Druid介绍2. 配置Druid连接池3. 查看Druid监控

Java中读取YAML文件配置信息常见问题及解决方法

《Java中读取YAML文件配置信息常见问题及解决方法》:本文主要介绍Java中读取YAML文件配置信息常见问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录1 使用Spring Boot的@ConfigurationProperties2. 使用@Valu

创建Java keystore文件的完整指南及详细步骤

《创建Javakeystore文件的完整指南及详细步骤》本文详解Java中keystore的创建与配置,涵盖私钥管理、自签名与CA证书生成、SSL/TLS应用,强调安全存储及验证机制,确保通信加密和... 目录1. 秘密键(私钥)的理解与管理私钥的定义与重要性私钥的管理策略私钥的生成与存储2. 证书的创建与

浅析Spring如何控制Bean的加载顺序

《浅析Spring如何控制Bean的加载顺序》在大多数情况下,我们不需要手动控制Bean的加载顺序,因为Spring的IoC容器足够智能,但在某些特殊场景下,这种隐式的依赖关系可能不存在,下面我们就来... 目录核心原则:依赖驱动加载手动控制 Bean 加载顺序的方法方法 1:使用@DependsOn(最直

SpringBoot中如何使用Assert进行断言校验

《SpringBoot中如何使用Assert进行断言校验》Java提供了内置的assert机制,而Spring框架也提供了更强大的Assert工具类来帮助开发者进行参数校验和状态检查,下... 目录前言一、Java 原生assert简介1.1 使用方式1.2 示例代码1.3 优缺点分析二、Spring Fr

Android kotlin中 Channel 和 Flow 的区别和选择使用场景分析

《Androidkotlin中Channel和Flow的区别和选择使用场景分析》Kotlin协程中,Flow是冷数据流,按需触发,适合响应式数据处理;Channel是热数据流,持续发送,支持... 目录一、基本概念界定FlowChannel二、核心特性对比数据生产触发条件生产与消费的关系背压处理机制生命周期