将URL参数传递给Spring Security自定义LogoutSuccessHandler

时间:2017-08-21 13:21:58

标签: java spring spring-security saml spring-saml

  

我正在部署的服务器就是这个   https://github.com/OpenConext/OpenConext-oidc,我正在扩展它   注销功能,(Logout等)。

现在我有一个请求,注销是使用: http:/www.example.com:8080/server app/saml/logout

我想像这样在URL上添加参数。 http:/www.example.com:8080/server app/saml/logout?value=www.youtube.com,因此我可以重定向到用户的不同页面。

为此,我创建了一个自定义 CustomLogoutSuccessHandler

public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        if (authentication != null && authentication.getDetails() != null) {
            try {
                request.getSession().invalidate();
                System.out.println("User Successfully Logout");
                //you can add more codes here when the user successfully logs out,
                //such as updating the database for last active.
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //Set the Server Status
        httpServletResponse.setStatus(HttpServletResponse.SC_OK);

        //redirect to login
        String queryString = request.getParameter("value");

        //Check if no parameters have been passed
        if (queryString == null) {
            httpServletResponse.sendRedirect("http://www.youtube.com");
        } else {
            httpServletResponse.sendRedirect("http://www." + queryString + ".com");
        }
    }

}

问题是request.getParameter("value");总是返回null!为什么会这样?我抓到的网址太晚了或什么?

我总是回到网址`http:/www.example.com:8080 / server app / saml / SingleLogout

在user-context.xml上(Logout是这样定义的)(它在这里工作没有问题)。

...

<!-- Filters for processing of SAML messages -->
    <bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
        <security:filter-chain-map request-matcher="ant">
            <security:filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/>
            <security:filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/>
            <security:filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
            <security:filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
            <security:filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
            <security:filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
        </security:filter-chain-map>
    </bean>

!-- Handler for successful logout -->
    <bean id="successLogoutHandler" class="oidc.security.CustomLogoutSuccessHandler"></bean>

...

1 个答案:

答案 0 :(得分:0)

<强>解决方案

1)创建了自定义SAMLLogoutFilter( CustomSAMLLogoutFilter ),我将原始请求的网址传递给 CustomLogoutHandler

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package oidc.security;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.saml.SAMLLogoutFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

/**
 *
 * @author GOXR3PLUS
 */
public class CustomSAMLLogoutFilter extends SAMLLogoutFilter {

    private LogoutSuccessHandler logoutSuccessHandler;

    public CustomSAMLLogoutFilter(String successUrl, LogoutHandler[] localHandler, LogoutHandler[] globalHandlers) {
        super(successUrl, localHandler, globalHandlers);
    }

    public CustomSAMLLogoutFilter(LogoutSuccessHandler logoutSuccessHandler, LogoutHandler[] localHandler, LogoutHandler[] globalHandlers) {
        super(logoutSuccessHandler, localHandler, globalHandlers);
        this.logoutSuccessHandler = logoutSuccessHandler;
    }

    @Override
    public void processLogout(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
        super.processLogout(request, response, chain);

        //   System.out.println("Hello from [ CustomSAMLLogoutFilter ] ");
        //Lets print some information here
        System.out.println("FULL HttpServletRequest URL is : " + getFullURL(request));

        //Downcast and pass it as parameter
        ((CustomLogoutSuccessHandler) logoutSuccessHandler).setOriginalURL(getFullURL(request));
        //   System.out.println("Chao chao from [ CustomSAMLLogoutFilter ] \n");
    }

    /**
     * Returns the full URL of the HTTPServletRequest
     */
    public static String getFullURL(HttpServletRequest request) {
        StringBuffer requestURL = request.getRequestURL();
        String queryString = request.getQueryString();

        if (queryString == null) {
            return requestURL.toString();
        } else {
            return requestURL.append('?').append(queryString).toString();
        }
    }

}

2) CustomLogoutHandler 的代码:

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package oidc.security;

import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
//import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.regex.Pattern;
import org.springframework.util.MultiValueMap;
import org.springframework.web.util.UriComponentsBuilder;

public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

    /**
     * This variable is used in order to keep track of the original URL the user
     * passed before he logged out
     */
    private String originalURL;

    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        if (authentication != null && authentication.getDetails() != null) {
            try {
                request.getSession().invalidate();
                System.out.println("User Successfully Logout");
                //you can add more codes here when the user successfully logs out,
                //such as updating the database for last active.
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        //Set the Server Status
        response.setStatus(HttpServletResponse.SC_OK);

        //----------- Choose where to redirect------------------
        System.out.println("Hello from [ CustomLogoutSuccessHandler ] \n");
        System.out.println("Original URL is : " + originalURL);

        //Check if any parameters have been passed
        String redirectURL = null;
        if (originalURL != null) {

            try {
                //Get all the parameters from the url
                MultiValueMap<String, String> parameters
                        = UriComponentsBuilder.fromUriString(originalURL).build().getQueryParams();

                //--Get the parameter value 
                List<String> value = parameters.get("redirect");
                if (value.size() != 0) { //if it exists
                    redirectURL = value.get(0);
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        // Decide where to redirect
        System.out.println(" Redirect : " + redirectURL);
        if (redirectURL == null) {
            response.sendRedirect("/oidc/");
        } else {
            response.sendRedirect(redirectURL);
        }
    }

    public void setOriginalURL(String originalURL) {
        this.originalURL = originalURL;
    }

}

3)在user-context.xml上,我只更改了两件事

<!-- Handler for successful logout --> <bean id="successLogoutHandler" class="oidc.security.CustomLogoutSuccessHandler"></bean>

<!-- Override default logout processing filter with the one processing SAML messages --> <bean id="samlLogoutFilter" class="oidc.security.CustomSAMLLogoutFilter"> <constructor-arg index="0" ref="successLogoutHandler"/> <constructor-arg index="1" ref="logoutHandler"/> <constructor-arg index="2" ref="logoutHandler"/> </bean>

为了让他们看到新的自定义java文件java/..../security

最后,您希望在此处查看所有user-context.xml:

<?xml version="1.0" encoding="UTF-8"?>
<!--
  Copyright 2015 The MITRE Corporation 
    and the MIT Kerberos and Internet Trust Consortium

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
-->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:security="http://www.springframework.org/schema/security"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-3.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd"

       profile="!local">


    <context:property-placeholder location="classpath:application.oidc.properties"/>

    <!-- Enable auto-wiring -->
    <context:annotation-config/>

    <!-- Scan for auto-wiring classes in spring saml packages -->
    <context:component-scan base-package="org.springframework.security.saml"/>

    <!-- Unsecured pages -->
    <security:http security="none" pattern="/translate-sp-entity-id" create-session="never"/>

    <!-- Secured pages with SAML as entry point -->
    <security:http entry-point-ref="samlEntryPoint" use-expressions="true" create-session="never">
        <security:intercept-url pattern="/authorize" access="hasRole('ROLE_USER')" />
        <security:intercept-url pattern="/**" access="permitAll" />
        <security:custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
        <security:custom-filter before="PRE_AUTH_FILTER" ref="clientIdFilter"/>
        <security:custom-filter ref="authRequestFilter" after="SECURITY_CONTEXT_FILTER" />
        <security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
        <!--<security:anonymous />-->
        <security:headers>
            <security:frame-options policy="DENY" />
        </security:headers>
        <security:csrf request-matcher-ref="apiCsrfProtectionMatcher"/>
    </security:http>

    <!-- Logger for SAML messages and events -->
    <bean id="clientIdFilter" class="oidc.security.ClientIdFilter"/>

    <!-- Filters for processing of SAML messages -->
    <bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
        <security:filter-chain-map request-matcher="ant">
            <security:filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/>
            <security:filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/>
            <security:filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
            <security:filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
            <security:filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
            <security:filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
        </security:filter-chain-map>
    </bean>



    <!-- Handler deciding where to redirect user after successful login -->
    <bean id="successRedirectHandler"
          class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/"/>
    </bean>

    <!--
    Use the following for interpreting RelayState coming from unsolicited response as redirect URL:
    <bean id="successRedirectHandler" class="org.springframework.security.saml.SAMLRelayStateSuccessHandler">
       <property name="defaultTargetUrl" value="/" />
    </bean>
    -->

    <!-- Handler deciding where to redirect user after failed login -->
    <bean id="failureRedirectHandler"
          class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <property name="useForward" value="true"/>
        <property name="defaultFailureUrl" value="/error.jsp"/>
    </bean>

    <!-- Handler for successful logout -->
    <bean id="successLogoutHandler" class="oidc.security.CustomLogoutSuccessHandler"></bean>

    <!-- Override default logout processing filter with the one processing SAML messages -->
    <bean id="samlLogoutFilter" class="oidc.security.CustomSAMLLogoutFilter">
        <constructor-arg index="0" ref="successLogoutHandler"/>
        <constructor-arg index="1" ref="logoutHandler"/>
        <constructor-arg index="2" ref="logoutHandler"/>
    </bean>

    <!-- Filter processing incoming logout messages -->
    <!-- First argument determines URL user will be redirected to after successful global logout -->
    <bean id="samlLogoutProcessingFilter" class="org.springframework.security.saml.SAMLLogoutProcessingFilter">
        <constructor-arg index="0" ref="successLogoutHandler"/>
        <constructor-arg index="1" ref="logoutHandler"/>
    </bean>


    <security:authentication-manager alias="authenticationManager">
        <!-- Register authentication manager for SAML provider -->
        <security:authentication-provider ref="samlAuthenticationProvider"/>
    </security:authentication-manager>

    <!-- Logger for SAML messages and events -->
    <bean id="samlLogger" class="org.springframework.security.saml.log.SAMLDefaultLogger"/>

    <bean id="keyStoreLocator" class="oidc.saml.KeyStoreLocator"/>

    <!-- Central storage of cryptographic keys -->
    <bean id="keyManager" class="org.springframework.security.saml.key.JKSKeyManager">
        <constructor-arg>
            <bean factory-bean="keyStoreLocator"
                  factory-method="createKeyStore">
                <constructor-arg value="${idp.entity.id}"/>
                <constructor-arg value="${idp.public.certificate}"/>
                <constructor-arg value="${sp.entity.id}"/>
                <constructor-arg value="${sp.public.certificate}"/>
                <constructor-arg value="${sp.private.key}"/>
                <constructor-arg value="${sp.passphrase}"/>
            </bean>
        </constructor-arg>
        <constructor-arg>
            <map>
                <entry key="${sp.entity.id}" value="${sp.passphrase}"/>
            </map>
        </constructor-arg>
        <constructor-arg type="java.lang.String" value="${sp.entity.id}"/>
    </bean>

    <!-- Entry point to initialize authentication, default values taken from properties file -->
    <bean id="samlEntryPoint" class="oidc.saml.ProxySAMLEntryPoint"/>

    <!-- Filter automatically generates default SP metadata -->
    <bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
        <constructor-arg>
            <bean class="org.springframework.security.saml.metadata.MetadataGenerator">
                <property name="entityId" value="${sp.entity.id}"/>
                <property name="entityBaseURL" value="${sp.entity.base.url}"/>
                <property name="extendedMetadata">
                    <bean class="org.springframework.security.saml.metadata.ExtendedMetadata">
                        <property name="signMetadata" value="true"/>
                        <property name="idpDiscoveryEnabled" value="false"/>
                    </bean>
                </property>
            </bean>
        </constructor-arg>
    </bean>

    <!-- The filter is waiting for connections on URL suffixed with filterSuffix and presents SP metadata there -->
    <bean id="metadataDisplayFilter" class="org.springframework.security.saml.metadata.MetadataDisplayFilter"/>

    <bean id="metadataManager" class="org.springframework.security.saml.metadata.CachingMetadataManager">
        <constructor-arg>
            <list>
                <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
                    <constructor-arg>
                        <bean class="org.opensaml.saml2.metadata.provider.HTTPMetadataProvider">
                            <constructor-arg>
                                <value type="java.lang.String">${idp.metadata.url}</value>
                            </constructor-arg>
                            <constructor-arg>
                                <value type="int">30000</value>
                            </constructor-arg>
                            <property name="parserPool" ref="parserPool"/>
                            <property name="requireValidMetadata" value="false"/>
                        </bean>
                    </constructor-arg>
                </bean>
            </list>
        </constructor-arg>
    </bean>

    <!-- IDP Metadata configuration - paths to metadata of IDPs in circle of trust is here -->
    <bean class="org.springframework.security.saml.metadata.ExtendedMetadataDelegate">
        <constructor-arg ref="metadataManager"/>
    </bean>

    <bean id="defaultSAMLUserDetailsService" class="oidc.saml.DefaultSAMLUserDetailsService">
        <constructor-arg value="${sp.entity.id}"/>
    </bean>

    <!-- SAML Authentication Provider responsible for validating of received SAML messages -->
    <bean id="samlAuthenticationProvider" class="oidc.saml.FederatedSAMLAuthenticationProvider">
        <property name="userDetails" ref="defaultSAMLUserDetailsService"/>
    </bean>

    <!-- Provider of default SAML Context -->
    <bean id="contextProvider" class="org.springframework.security.saml.context.SAMLContextProviderImpl"/>

    <!-- Processing filter for WebSSO profile messages -->
    <bean id="samlWebSSOProcessingFilter" class="org.springframework.security.saml.SAMLProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
        <property name="authenticationFailureHandler" ref="failureRedirectHandler"/>
    </bean>

    <!-- Processing filter for WebSSO Holder-of-Key profile -->
    <bean id="samlWebSSOHoKProcessingFilter" class="org.springframework.security.saml.SAMLWebSSOHoKProcessingFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationSuccessHandler" ref="successRedirectHandler"/>
        <property name="authenticationFailureHandler" ref="failureRedirectHandler"/>
    </bean>

    <!-- Logout handler terminating local session -->
    <bean id="logoutHandler"
          class="org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler">
        <property name="invalidateHttpSession" value="false"/>
    </bean>

    <!-- Class loading incoming SAML messages from httpRequest stream -->
    <bean id="processor" class="org.springframework.security.saml.processor.SAMLProcessorImpl">
        <constructor-arg>
            <list>
                <ref bean="redirectBinding"/>
                <ref bean="postBinding"/>
                <ref bean="artifactBinding"/>
                <ref bean="soapBinding"/>
                <ref bean="paosBinding"/>
            </list>
        </constructor-arg>
    </bean>

    <!-- SAML 2.0 WebSSO Assertion Consumer -->
    <bean id="webSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerImpl">
        <property name="maxAuthenticationAge" value="43200"/>
    </bean>

    <!-- SAML 2.0 Holder-of-Key WebSSO Assertion Consumer -->
    <bean id="hokWebSSOprofileConsumer" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/>

    <!-- SAML 2.0 Web SSO profile -->
    <bean id="webSSOprofile" class="org.springframework.security.saml.websso.WebSSOProfileImpl"/>

    <!-- SAML 2.0 Holder-of-Key Web SSO profile -->
    <bean id="hokWebSSOProfile" class="org.springframework.security.saml.websso.WebSSOProfileConsumerHoKImpl"/>

    <!-- SAML 2.0 ECP profile -->
    <bean id="ecpprofile" class="org.springframework.security.saml.websso.WebSSOProfileECPImpl"/>

    <!-- SAML 2.0 Logout Profile -->
    <bean id="logoutprofile" class="org.springframework.security.saml.websso.SingleLogoutProfileImpl"/>

    <!-- Bindings, encoders and decoders used for creating and parsing messages -->
    <bean id="postBinding" class="org.springframework.security.saml.processor.HTTPPostBinding">
        <constructor-arg ref="parserPool"/>
        <constructor-arg ref="velocityEngine"/>
    </bean>

    <bean id="redirectBinding" class="org.springframework.security.saml.processor.HTTPRedirectDeflateBinding">
        <constructor-arg ref="parserPool"/>
    </bean>

    <bean id="artifactBinding" class="org.springframework.security.saml.processor.HTTPArtifactBinding">
        <constructor-arg ref="parserPool"/>
        <constructor-arg ref="velocityEngine"/>
        <constructor-arg>
            <bean class="org.springframework.security.saml.websso.ArtifactResolutionProfileImpl">
                <constructor-arg>
                    <bean class="org.apache.commons.httpclient.HttpClient">
                        <constructor-arg>
                            <bean class="org.apache.commons.httpclient.MultiThreadedHttpConnectionManager"/>
                        </constructor-arg>
                    </bean>
                </constructor-arg>
                <property name="processor">
                    <bean class="org.springframework.security.saml.processor.SAMLProcessorImpl">
                        <constructor-arg ref="soapBinding"/>
                    </bean>
                </property>
            </bean>
        </constructor-arg>
    </bean>

    <bean id="soapBinding" class="org.springframework.security.saml.processor.HTTPSOAP11Binding">
        <constructor-arg ref="parserPool"/>
    </bean>

    <bean id="paosBinding" class="org.springframework.security.saml.processor.HTTPPAOS11Binding">
        <constructor-arg ref="parserPool"/>
    </bean>

    <!-- Initialization of OpenSAML library-->
    <bean class="org.springframework.security.saml.SAMLBootstrap"/>

    <!-- Initialization of the velocity engine -->
    <bean id="velocityEngine" class="org.springframework.security.saml.util.VelocityFactory" factory-method="getEngine"/>

    <!-- XML parser pool needed for OpenSAML parsing -->
    <bean id="parserPool" class="org.opensaml.xml.parse.StaticBasicParserPool" init-method="initialize">
        <property name="builderFeatures">
            <map>
                <entry key="http://apache.org/xml/features/dom/defer-node-expansion" value="false"/>
            </map>
        </property>
    </bean>

    <bean id="parserPoolHolder" class="org.springframework.security.saml.parser.ParserPoolHolder"/>
</beans>