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中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置

Java 虚拟线程的创建与使用深度解析

《Java虚拟线程的创建与使用深度解析》虚拟线程是Java19中以预览特性形式引入,Java21起正式发布的轻量级线程,本文给大家介绍Java虚拟线程的创建与使用,感兴趣的朋友一起看看吧... 目录一、虚拟线程简介1.1 什么是虚拟线程?1.2 为什么需要虚拟线程?二、虚拟线程与平台线程对比代码对比示例:三

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Java中的.close()举例详解

《Java中的.close()举例详解》.close()方法只适用于通过window.open()打开的弹出窗口,对于浏览器的主窗口,如果没有得到用户允许是不能关闭的,:本文主要介绍Java中的.... 目录当你遇到以下三种情况时,一定要记得使用 .close():用法作用举例如何判断代码中的 input