微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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

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

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

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

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

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

这是我的安全 xml 文件在 oauth2 登录正常工作而注销不正常时的外观。抱歉,如果我包含了太多的 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,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"/>


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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。