本文主要是介绍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) &¶meters.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) &¶meters.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) &¶meters.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客户端发起登出源码分析的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!