如何通过xml配置spring security 5.4.1以使oauth2注销正常工作?

问题描述 投票:0回答:2

我正在尝试将当前使用基本身份验证的 Web 应用程序转换为使用 oauth2。在准备这个项目时,我将 Spring 和 Spring Security 从 v3 升级到 v5,以便我们可以利用 Spring Security 5.4.1 中的 OAuth2 功能。我们的 Web 应用程序仍然通过 xml 配置 spring 和 spring security,并且不使用 spring boot。

我现在已经配置好,以便 oauth2 登录过程正常工作,或者至少看起来可以正常工作。但是,我在配置它时遇到困难,因此当用户注销时,会话也会在授权服务器中结束,并向用户显示来自授权服务器的登录页面。目前,当我注销时,我可以在日志中看到会话已失效并且 JSESSIONID cookie 已删除,但我没有看到它向授权服务器的“end-session-endpoint”发送请求以结束会话授权服务器。然后我看到,当 Spring Security 想要再次登录用户时,授权服务器响应说用户的会话仍然处于活动状态并跳过登录页面。

配置 oauth2 注销的正确方法似乎是将“logout”元素的“success-handler-ref”属性设置为“OidcClientInitiatedLogoutSuccessHandler”bean 的引用。但是,OidcClientInitiatedLogoutSuccessHandler 构造函数需要引用 ClientRegistrationRepository bean,并且配置 ClientRegistrationRepository“bean”的“client-registrations”元素似乎没有可以传递给 OidcClientInitiatedLogoutSuccessHandler 构造函数的“id”或“name”属性。

我确实尝试创建自己的 ClientRegistrationRepository bean 来替换“client-registrations”元素的使用,希望它允许我使用“id”属性配置该类。然而,当我这样做时,它破坏了登录过程,我不知道为什么。

这是当 oauth2 登录正常工作而注销不正常时我的安全 xml 文件的外观。抱歉,如果我包含了太多 xml 文件。我觉得越多越好。

    <security:http pattern="/resources/**" security="none"/>
    <security:http pattern="/mobile/startup" security="none"/>
    <security:http pattern="/mobile/debuginfo" security="none"/>
    <security:http auto-config="true" use-expressions="true" disable-url-rewriting="true">
        <headers disabled="true"/>
        <csrf disabled="true"/>
        <expression-handler ref="webExpressionHandler"/>
        <intercept-url pattern="/identity/login" access="permitAll"/>
        <intercept-url pattern="/mobilelogin" access="permitAll"/>
        ...
        <intercept-url pattern="/**" access="isAuthenticated()"/>   
        <access-denied-handler error-page="/error/access_denied"/>

        <oauth2-client>
            <authorization-code-grant/>
        </oauth2-client>

        <oauth2-login oidc-user-service-ref="hwOidcUserService"/>

        <logout logout-url="/logout"
                invalidate-session="true"
                delete-cookies="JSESSIONID"
                logout-success-url="/"/>
    </security:http>

    <security:client-registrations>
        <client-registration registration-id="idsrv"
                             client-id="homeofficecodeui"
                             provider-id="idsrv"
                             client-name="Home Office and Mobile"
                             client-secret="<SECRET>"
                             authorization-grant-type="authorization_code"
                             client-authentication-method="basic"
                             redirect-uri="<CLIENT_BASE_URL>/login/oauth2/code/idsrv"
                             scope="openid,offline_access,profile,email,homeofficeapi"/>

        <provider provider-id="idsrv"
                  issuer-uri="<AUTH_SERVER_BASE_URL>"
                  authorization-uri="<AUTH_SERVER_BASE_URL>/connect/authorize?sec_host=<CLIENT_BASE_URL>"/>
    </security:client-registrations>

    <beans:bean id="hwOidcUserService"
                class="com.example.service.HWOidcUserService"/>

    <beans:bean id="webExpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
        <beans:property name="permissionEvaluator" ref="permissionEvaluator"/>
    </beans:bean>
    <beans:bean id="requestContextFilter" class="org.springframework.web.filter.RequestContextFilter"/>

    <beans:bean id="customAuthenticationEntryPoint"
        class="com.example.security.CustomAuthenticationEntryPoint" />

    <beans:bean id="authenticationFailureHandler" class="com.example.security.CustomAuthenticationFailureHandler">
        <beans:property name="exceptionMappings">      
            <beans:props>
                <beans:prop key="org.springframework.security.authentication.BadCredentialsException">/login?error=BAD_CREDENTIALS</beans:prop>
                <beans:prop key="org.springframework.security.authentication.CredentialsExpiredException">/login?error=CREDENTIALS_EXPIRED</beans:prop>            
                <beans:prop key="org.springframework.security.authentication.LockedException">/login?error=ACCOUNT_LOCKED</beans:prop>   
                <beans:prop key="com.example.exception.AccountManuallyLockedException">/login?error=ACCOUNT_LOCKED_MANUALLY</beans:prop> 
                <beans:prop key="org.springframework.security.authentication.DisabledException">/login?error=AGENCY_DISABLED</beans:prop>       
                <beans:prop key="org.springframework.security.authentication.InternalAuthenticationServiceException">/login?error=UNKNOWN_ERROR</beans:prop>
            </beans:props>
        </beans:property>
    </beans:bean>

    <beans:bean id="passwordValidator" class="com.example.security.PasswordValidator">
    </beans:bean>

    <authentication-manager>
        <authentication-provider ref="alternateCustomAuthenticationProvider"/>
        <authentication-provider ref="alternate2CustomAuthenticationProvider"/>
        <authentication-provider ref="customAuthenticationProvider"/>
    </authentication-manager>
    
    <global-method-security pre-post-annotations="enabled">
       <expression-handler ref="expressionHandler"/>
    </global-method-security>
    
    <beans:bean id="expressionHandler" class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler">
       <beans:property name="permissionEvaluator" ref="permissionEvaluator"/>
    </beans:bean>
    
    <beans:bean id="permissionEvaluator" class="com.example.security.CustomPermissionEvaluator">
    </beans:bean>

这是我认为我需要进行的更改,但我无法弄清楚 clientRegistrationRepository 的引用是什么:

    <security:http ...
        ...

        <logout logout-url="/logout"
                invalidate-session="true"
                delete-cookies="JSESSIONID"
                success-handler-ref="logoutSuccessHandler"/>
    </security:http>

    <beans:bean id="logoutSuccessHandler"
                class="org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler">
        <beans:constructor-arg ref="clientRegistrationRepository"/> <-- Error. Can't figure out what the ref to the clientRegistrationRepository is.
        <beans:property name="postLogoutRedirectUri" value="/"/>
    </beans:bean>

我已经浏览了 Spring Security Reference,但不幸的是,有关 OidcClientInitiatedLogoutSuccessHandler 的部分不包含 xml 示例。

有谁知道我需要进行哪些更改才能使 oauth2 注销过程正常工作?

黑客解决方案更新

我想出了一个可能属于“黑客”类别的解决方案。我在过滤器链中添加了一个自定义注销过滤器,该过滤器调用授权服务器“end-session-endpoint”以在授权服务器中注销用户。它使用 OidcClientInitiatedLogoutSuccessHandler 类,并通过 ApplicationContext 获取 ClientRegistrationRepository 的句柄。

我将此新过滤器放在 LogoutFilter 之前,因为 LogoutFilter 取消了 SecurityContext 中的身份验证,并且 OidcClientInitiatedLogoutSuccessHandler 需要身份验证才能正常工作。

这是新的自定义注销过滤器:

package com.example.web.login;

import javax.annotation.PostConstruct;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;

public class OAuth2LogoutFilter extends GenericFilterBean implements ApplicationContextAware {
    private static final Logger logger = LoggerFactory.getLogger(OAuth2LogoutFilter.class);

    private ApplicationContext applicationContext;

    private ClientRegistrationRepository clientRegistrationRepository;

    private OidcClientInitiatedLogoutSuccessHandler logoutSuccessHandler;

    private RequestMatcher logoutRequestMatcher;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    @PostConstruct
    public void init() {
        if (logger.isDebugEnabled())
            logger.debug("in init()");

        Assert.notNull(applicationContext, "applicationContext can not be null");

        clientRegistrationRepository = applicationContext.getBean(InMemoryClientRegistrationRepository.class);
        Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository can not be null");

        logoutSuccessHandler = new OidcClientInitiatedLogoutSuccessHandler(clientRegistrationRepository);
        logoutSuccessHandler.setPostLogoutRedirectUri("<CLIENT_BASE_URL>/logoutAfterOAuth");

        logoutRequestMatcher = new AntPathRequestMatcher("/logout");

        if (logger.isDebugEnabled())
            logger.debug("end init()");
    }

    @Override
    public void doFilter(final ServletRequest request,
                         final ServletResponse response,
                         final FilterChain chain) throws IOException, ServletException {
        doFilter((HttpServletRequest) request, (HttpServletResponse) response, chain);
    }

    private void doFilter(final HttpServletRequest request,
                          final HttpServletResponse response,
                          final FilterChain chain) throws IOException, ServletException {
        if (logger.isDebugEnabled())
            logger.debug("in doFilter()" +
                    "\n\trequest: " + request +
                    "\n\tresponse: " + response +
                    "\n\tchain: " + chain);

        if (requiresLogout(request)) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (this.logger.isDebugEnabled())
                this.logger.debug("Logging out auth: " + auth);

            logoutSuccessHandler.onLogoutSuccess(request, response, auth);
            return;
        }

        chain.doFilter(request, response);

        if (logger.isDebugEnabled())
            logger.debug("end doFilter()");
    }

    protected boolean requiresLogout(final HttpServletRequest request) {
        if (this.logoutRequestMatcher.matches(request))
            return true;

        if (this.logger.isTraceEnabled())
            this.logger.trace("Did not match request to " + logoutRequestMatcher);

        return false;
    }
}

这是对安全 xml 文件的更改:

<security:http ...
    ...

    <custom-filter ref="oauth2LogoutFilter" before="LOGOUT_FILTER"/>

    ...

    <logout logout-url="/logoutAfterOAuth"
            invalidate-session="true"
            delete-cookies="JSESSIONID"
            logout-success-url="/"/>

</security:http>

<beans:bean id="oauth2LogoutFilter"
            class="com.example.web.login.OAuth2LogoutFilter"/>


我仍然希望有人能告诉我正确的非黑客解决方案。

xml spring-security oauth-2.0 openid-connect logout
2个回答
0
投票

OidcClientInitiatedLogoutSuccessHandler
bean 定义的 XML 中,我使用了:

autowire=“constructor”

这样我就可以让 spring 注入

ClientRegistrationRepository
,因为没有 ID 引用。


0
投票

您可以分享您完整的安全 XML 吗?实际上,我正在努力使用 XML 进行 oauth2-login。

我没有被重定向到 oauth2 服务器登录页面。

正如您提到的,您已重定向到身份验证服务器的登录页面,请分享您完整的 XML。会有帮助吗??

© www.soinside.com 2019 - 2024. All rights reserved.