将 URL 参数传递给 Spring 安全自定义 LogoutSuccessHandler
Pass URL Parameters to Spring Security custom LogoutSuccessHandler
The server i am deploying is this
https://github.com/OpenConext/OpenConext-oidc , i am extending it's
logout capabilities , (Logout
etc).
现在我有一个请求,使用以下方式完成注销:
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");
总是returns空!为什么会这样?我赶上太晚了 URL 或者什么?
我总是返回 url `http:/www.example.com:8080/server app/saml/SingleLogout
在用户上-context.xml(注销是这样定义的)(在这里工作没有问题)。
...
<!-- 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) 创建了自定义 SAMLLogoutFilter (CustomSAMLLogoutFilter),我将原始请求的 url 传递给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) 在用户上-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
最后,如果您想查看所有用户-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>
The server i am deploying is this https://github.com/OpenConext/OpenConext-oidc , i am extending it's logout capabilities , (
Logout
etc).
现在我有一个请求,使用以下方式完成注销:
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");
总是returns空!为什么会这样?我赶上太晚了 URL 或者什么?
我总是返回 url `http:/www.example.com:8080/server app/saml/SingleLogout
在用户上-context.xml(注销是这样定义的)(在这里工作没有问题)。
...
<!-- 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) 创建了自定义 SAMLLogoutFilter (CustomSAMLLogoutFilter),我将原始请求的 url 传递给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) 在用户上-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
最后,如果您想查看所有用户-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>