I am trying to implement Single sign on using spring security. The application is hosted in IBM Websphere Application Server 8.5 (IBM JDK 7).
I've gone through http://docs.spring.io/autorepo/docs/spring-security-kerberos/1.0.2.BUILD-SNAPSHOT/reference/htmlsingle/
My security configuration file looks like this :
<sec:http entry-point-ref="spnegoEntryPoint" use-expressions="true">
<sec:intercept-url pattern="/contents/**" access="permitAll"/>
<sec:intercept-url pattern="/favicon.ico" access="permitAll"/>
<sec:intercept-url pattern="/WEB-INF/tags/**" access="permitAll"/>
<sec:intercept-url pattern="/systemuser/login**" access="permitAll"/>
<sec:intercept-url pattern="/systemuser/authentication/failure" access="permitAll"/>
<sec:intercept-url pattern="/systemuser/logout**" access="permitAll"/>
<sec:intercept-url pattern="/systemuser/405" access="permitAll"/>
<sec:intercept-url pattern="/systemuser/noscript" access="permitAll"/>
<sec:intercept-url pattern="/systemuser/**" access="isAuthenticated()"/>
<sec:form-login login-page="/systemuser/login" authentication-failure-url="/systemuser/login?error=true"
authentication-success-handler-ref="systemUserAuthenticationSuccessHandler"/>
<sec:logout logout-url="/systemuser/logout" success-handler-ref="systemUserLogoutSuccessHandler"/>
<sec:access-denied-handler error-page="/403"/>
<!-- enable csrf protection -->
<sec:csrf/>
<sec:headers/>
<sec:custom-filter ref="spnegoAuthenticationProcessingFilter"
before="BASIC_AUTH_FILTER"/>
</sec:http>
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="kerberosAuthenticationProvider"/>
<sec:authentication-provider ref="kerberosServiceAuthenticationProvider"/>
</sec:authentication-manager>
<bean id="kerberosAuthenticationProvider"
class="org.springframework.security.kerberos.authentication.KerberosAuthenticationProvider">
<property name="userDetailsService" ref="systemUserDetailsService"/>
<property name="kerberosClient">
<bean class="com.fdiapp.authentication.systemuser.service.IbmJaasKerberosClient">
<property name="debug" value="true"/>
</bean>
</property>
</bean>
<bean id="spnegoEntryPoint"
class="org.springframework.security.kerberos.web.authentication.SpnegoEntryPoint">
<constructor-arg value="/systemuser/login"/>
</bean>
<bean id="spnegoAuthenticationProcessingFilter"
class="org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="successHandler" ref="systemUserAuthenticationSuccessHandler"/>
<property name="failureHandler" ref="systemUserAuthenticationFailureHandler"/>
</bean>
<bean id="kerberosServiceAuthenticationProvider"
class="org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider">
<property name="ticketValidator">
<bean
class="com.fdiapp.authentication.systemuser.service.IbmJaasKerberosTicketValidator">
<property name="servicePrincipal" value="HTTP/MYDOMAIN"/>
<property name="keyTabLocation" value="/WEB-INF/keyfile/hbdap-hkkdd001.keytab"/>
<property name="debug" value="true"/>
</bean>
</property>
<property name="userDetailsService" ref="systemUserDetailsService"/>
</bean>
<bean id="systemUserDetailsService"
class="com.fdiapp.authentication.systemuser.service.SystemUserDetailsService"/>
<bean id="systemUserAuthenticationSuccessHandler"
class="com.fdiapp.authentication.handler.SystemUserAuthenticationSuccessHandler"/>
<bean id="systemUserLogoutSuccessHandler"
class="com.fdiapp.authentication.handler.SystemUserLogoutSuccessHandler"/>
<bean id="systemUserAuthenticationFailureHandler"
class="com.fdiapp.authentication.handler.SystemUserAuthenticationFailureHandler"/>
Kerberos client :
public class IbmJaasKerberosClient implements KerberosClient {
private boolean debug = true;
private static final Log LOG = LogFactory.getLog(IbmJaasKerberosClient.class);
#Override
public String login(String username, String password) {
LOG.debug("Trying to authenticate " + username + " with Kerberos");
String validatedUsername;
try {
LoginContext loginContext = new LoginContext("", null, new KerberosClientCallbackHandler(username, password),
new LoginConfig(this.debug));
loginContext.login();
if (LOG.isDebugEnabled()) {
LOG.debug("Kerberos authenticated user: "+loginContext.getSubject());
}
validatedUsername = loginContext.getSubject().getPrincipals().iterator().next().toString();
loginContext.logout();
} catch (LoginException e) {
if(LOG.isDebugEnabled()){
LOG.error(e.getMessage());
}
throw new BadCredentialsException("Kerberos authentication failed", e);
}
return validatedUsername;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
private static class LoginConfig extends Configuration {
private boolean debug;
public LoginConfig(boolean debug) {
super();
this.debug = debug;
}
#Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
HashMap<String, String> options = new HashMap<String, String>();
options.put("credsType", "acceptor");
if (debug) {
options.put("debug", "true");
}
return new AppConfigurationEntry[] { new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), };
}
}
private static class KerberosClientCallbackHandler implements CallbackHandler {
private String username;
private String password;
public KerberosClientCallbackHandler(String username, String password) {
this.username = username;
this.password = password;
}
#Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback ncb = (NameCallback) callback;
ncb.setName(username);
} else if (callback instanceof PasswordCallback) {
PasswordCallback pwcb = (PasswordCallback) callback;
pwcb.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callback, "We got a " + callback.getClass().getCanonicalName()
+ ", but only NameCallback and PasswordCallback is supported");
}
}
}
}
}
Kereros Ticket Validator :
public class IbmJaasKerberosTicketValidator implements KerberosTicketValidator, InitializingBean {
private String servicePrincipal;
private Resource keyTabLocation;
private Subject serviceSubject;
private boolean holdOnToGSSContext;
private boolean debug = true;
private static final Log LOG = LogFactory.getLog(IbmJaasKerberosTicketValidator.class);
#Override
public KerberosTicketValidation validateTicket(byte[] token) {
try {
return Subject.doAs(this.serviceSubject, new KerberosValidateAction(token));
}
catch (PrivilegedActionException e) {
throw new BadCredentialsException("Kerberos validation not successful", e);
}
}
#Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.servicePrincipal, "servicePrincipal must be specified");
Assert.notNull(this.keyTabLocation, "keyTab must be specified");
if (keyTabLocation instanceof ClassPathResource) {
LOG.warn("Your keytab is in the classpath. This file needs special protection and shouldn't be in the classpath. JAAS may also not be able to load this file from classpath.");
}
String keyTabLocationAsString = this.keyTabLocation.getURL().toExternalForm();
// We need to remove the file prefix (if there is one), as it is not supported in Java 7 anymore.
// As Java 6 accepts it with and without the prefix, we don't need to check for Java 7
if (keyTabLocationAsString.startsWith("file:"))
{
keyTabLocationAsString = keyTabLocationAsString.substring(5);
}
LoginConfig loginConfig = new LoginConfig(keyTabLocationAsString, this.servicePrincipal,
this.debug);
Set<Principal> princ = new HashSet<Principal>(1);
princ.add(new KerberosPrincipal(this.servicePrincipal));
Subject sub = new Subject(false, princ, new HashSet<Object>(), new HashSet<Object>());
LoginContext lc = new LoginContext("", sub, null, loginConfig);
lc.login();
this.serviceSubject = lc.getSubject();
}
/**
* The service principal of the application.
* For web apps this is <code>HTTP/full-qualified-domain-name#DOMAIN</code>.
* The keytab must contain the key for this principal.
*
* #param servicePrincipal service principal to use
* #see #setKeyTabLocation(Resource)
*/
public void setServicePrincipal(String servicePrincipal) {
this.servicePrincipal = servicePrincipal;
}
/**
* <p>The location of the keytab. You can use the normale Spring Resource
* prefixes like <code>file:</code> or <code>classpath:</code>, but as the
* file is later on read by JAAS, we cannot guarantee that <code>classpath</code>
* works in every environment, esp. not in Java EE application servers. You
* should use <code>file:</code> there.
*
* This file also needs special protection, which is another reason to
* not include it in the classpath but rather use <code>file:/etc/http.keytab</code>
* for example.
*
* #param keyTabLocation The location where the keytab resides
*/
public void setKeyTabLocation(Resource keyTabLocation) {
this.keyTabLocation = keyTabLocation;
}
/**
* Enables the debug mode of the JAAS Kerberos login module.
*
* #param debug default is false
*/
public void setDebug(boolean debug) {
this.debug = debug;
}
/**
* Determines whether to hold on to the {#link GSSContext GSS security context} or
* otherwise {#link GSSContext#dispose() dispose} of it immediately (the default behaviour).
* <p>Holding on to the GSS context allows decrypt and encrypt operations for subsequent
* interactions with the principal.
*
* #param holdOnToGSSContext true if should hold on to context
*/
public void setHoldOnToGSSContext(boolean holdOnToGSSContext) {
this.holdOnToGSSContext = holdOnToGSSContext;
}
/**
* This class is needed, because the validation must run with previously generated JAAS subject
* which belongs to the service principal and was loaded out of the keytab during startup.
*/
private class KerberosValidateAction implements PrivilegedExceptionAction<KerberosTicketValidation> {
byte[] kerberosTicket;
public KerberosValidateAction(byte[] kerberosTicket) {
this.kerberosTicket = kerberosTicket;
}
#Override
public KerberosTicketValidation run() throws Exception {
byte[] responseToken = new byte[0];
GSSName gssName = null;
GSSContext context = GSSManager.getInstance().createContext((GSSCredential) null);
boolean first = true;
while (!context.isEstablished()) {
if (first) {
kerberosTicket = tweakJdkRegression(kerberosTicket);
}
responseToken = context.acceptSecContext(kerberosTicket, 0, kerberosTicket.length);
gssName = context.getSrcName();
if (gssName == null) {
throw new BadCredentialsException("GSSContext name of the context initiator is null");
}
first = false;
}
if (!holdOnToGSSContext) {
context.dispose();
}
return new KerberosTicketValidation(gssName.toString(), servicePrincipal, responseToken, context);
}
}
/**
* Normally you need a JAAS config file in order to use the JAAS Kerberos Login Module,
* with this class it is not needed and you can have different configurations in one JVM.
*/
private static class LoginConfig extends Configuration {
private String keyTabLocation;
private String servicePrincipalName;
private boolean debug;
public LoginConfig(String keyTabLocation, String servicePrincipalName, boolean debug) {
this.keyTabLocation = keyTabLocation;
this.servicePrincipalName = servicePrincipalName;
this.debug = debug;
}
#Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
HashMap<String, String> options = new HashMap<String, String>();
options.put("useKeytab", this.keyTabLocation);
options.put("principal", this.servicePrincipalName);
options.put("credsType", "acceptor");
if (this.debug) {
options.put("debug", "true");
}
return new AppConfigurationEntry[] { new AppConfigurationEntry("com.ibm.security.auth.module.Krb5LoginModule",
AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options), };
}
}
private static byte[] tweakJdkRegression(byte[] token) throws GSSException {
if (token == null || token.length < 48) {
return token;
}
int[] toCheck = new int[] { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x82, 0xF7, 0x12, 0x01, 0x02, 0x02, 0x06, 0x09, 0x2A,
0x86, 0x48, 0x86, 0xF7, 0x12, 0x01, 0x02, 0x02 };
for (int i = 0; i < 22; i++) {
if ((byte) toCheck[i] != token[i + 24]) {
return token;
}
}
byte[] nt = new byte[token.length];
System.arraycopy(token, 0, nt, 0, 24);
System.arraycopy(token, 35, nt, 24, 11);
System.arraycopy(token, 24, nt, 35, 11);
System.arraycopy(token, 46, nt, 46, token.length - 24 - 11 - 11);
return nt;
}
}
I am getting the following error message :
[8/11/16 11:33:59:201 HKT] 000000c4 SpnegoAuthent W org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter doFilter Negotiate Header was invalid: Negotiate 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
org.springframework.security.authentication.BadCredentialsException: Kerberos validation not successful
at com.fdiapp.authentication.systemuser.service.IbmJaasKerberosTicketValidator.validateTicket(IbmJaasKerberosTicketValidator.java:50)
at org.springframework.security.kerberos.authentication.KerberosServiceAuthenticationProvider.authenticate(KerberosServiceAuthenticationProvider.java:64)
at org.springframework.security.authentication.ProviderManager.authenticate(ProviderManager.java:156)
at org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter.doFilter(SpnegoAuthenticationProcessingFilter.java:145)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:199)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:110)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.csrf.CsrfFilter.doFilterInternal(CsrfFilter.java:85)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:57)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:50)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:87)
at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:342)
at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:192)
at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:160)
at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:344)
at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:261)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:195)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:91)
at org.springframework.web.multipart.support.MultipartFilter.doFilterInternal(MultipartFilter.java:118)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
at com.ibm.ws.webcontainer.filter.FilterInstanceWrapper.doFilter(FilterInstanceWrapper.java:195)
at com.ibm.ws.webcontainer.filter.WebAppFilterChain.doFilter(WebAppFilterChain.java:91)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.doFilter(WebAppFilterManager.java:967)
at com.ibm.ws.webcontainer.filter.WebAppFilterManager.invokeFilters(WebAppFilterManager.java:1107)
at com.ibm.ws.webcontainer.servlet.CacheServletWrapper.handleRequest(CacheServletWrapper.java:87)
at com.ibm.ws.webcontainer.WebContainer.handleRequest(WebContainer.java:940)
at com.ibm.ws.webcontainer.WSWebContainer.handleRequest(WSWebContainer.java:1817)
at com.ibm.ws.webcontainer.channel.WCChannelLink.ready(WCChannelLink.java:200)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleDiscrimination(HttpInboundLink.java:463)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.handleNewRequest(HttpInboundLink.java:530)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.processRequest(HttpInboundLink.java:316)
at com.ibm.ws.http.channel.inbound.impl.HttpInboundLink.ready(HttpInboundLink.java:287)
at com.ibm.io.async.AsyncChannelFuture$1.run(AsyncChannelFuture.java:205)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1881)
Caused by: java.security.PrivilegedActionException: org.ietf.jgss.GSSException, major code: 13, minor code: 0
major string: Invalid credentials
minor string: Cannot obtain mechanism credential for mechanism 1.3.6.1.5.5.2
at java.security.AccessController.doPrivileged(AccessController.java:458)
at javax.security.auth.Subject.doAs(Subject.java:572)
atcom.fdiapp.authentication.systemuser.service.IbmJaasKerberosTicketValidator.validateTicket(IbmJaasKerberosTicketValidator.java:47)
Could anyone suggest me how can I make the above configuration workable?
Or
How can I retrieve user from the negotiate header using spn and ketab file so that I can authenticate.
Maybe this might help:
Oid krb5MechOid = new Oid("1.2.840.113554.1.2.2");
Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2");
System.setProperty("javax.security.auth.useSubjectCredsOnly", "true");
GSSManager manager = GSSManager.getInstance();
GSSName gssUserName = manager.createName(userName, GSSName.NT_USER_NAME, krb5MechOid);
clientGssCreds = manager.createCredential(gssUserName.canonicalize(krb5MechOid),
GSSCredential.INDEFINITE_LIFETIME,
krb5MechOid,
GSSCredential.INITIATE_ONLY);
clientGssCreds.add (gssUserName,
GSSCredential.INDEFINITE_LIFETIME,
GSSCredential.INDEFINITE_LIFETIME,
spnegoMechOid,
GSSCredential.INITIATE_ONLY);
http://www.ibm.com/support/knowledgecenter/en/SS7K4U_8.5.5/com.ibm.websphere.zseries.doc/ae/tsec_SPNEGO_token.html
Related
I have a similar question here
Guice with multiple concretes......picking one of them
with a solution for Guice.
But I have a different project using spring di (beans), but with the same kind of issue.
I have an interface with N number of concretes. (3 here)
public interface OrderProcessorInterface {
void ProcessOrder(String preferredShipperAbbreviation, Order ord);
}
public class FedExShipper implements ShipperInterface {
private Log logger;
public FedExShipper(Log lgr) {
if (null == lgr) {
throw new IllegalArgumentException("Log is null");
}
this.logger = lgr;
}
public void ShipOrder(Order ord) {
this.logger.info("I'm shipping the Order with FexEx");
}
}
public class UpsShipper implements ShipperInterface {
private Log logger;
public UpsShipper(Log lgr) {
if (null == lgr) {
throw new IllegalArgumentException("Log is null");
}
this.logger = lgr;
}
public void ShipOrder(Order ord) {
this.logger.info("I'm shipping the Order with Ups");
}
}
public class UspsShipper implements ShipperInterface {
private Log logger;
public UspsShipper(Log lgr) {
if (null == lgr) {
throw new IllegalArgumentException("Log is null");
}
this.logger = lgr;
}
public void ShipOrder(Order ord) {
this.logger.info("I'm shipping the Order with Usps");
}
}
........
Then I have a class that needs to know about ALL THREE concretes.
import java.util.Collection;
import java.util.Set;
import org.apache.commons.logging.Log;
public class OrderProcessorImpl implements OrderProcessorInterface {
private Log logger;
private java.util.Map<String, javax.inject.Provider<ShipperInterface>> shipperProviderMap;
public OrderProcessorImpl(Log lgr, java.util.Map<String, javax.inject.Provider<ShipperInterface>> spMap) {
if (null == lgr) {
throw new IllegalArgumentException("Log is null");
}
if (null == spMap) {
throw new IllegalArgumentException("Provider<ShipperInterface> is null");
}
this.logger = lgr;
this.shipperProviderMap = spMap;
}
public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {
this.logger.info(String.format("About to ship. (%1s)", preferredShipperAbbreviation));
ShipperInterface foundShipperInterface = this.FindShipperInterface(preferredShipperAbbreviation);
foundShipperInterface.ShipOrder(ord);
}
private ShipperInterface FindShipperInterface(String preferredShipperAbbreviation) {
ShipperInterface foundShipperInterface = this.shipperProviderMap.get(preferredShipperAbbreviation).get();
if (null == foundShipperInterface) {
throw new NullPointerException(
String.format("ShipperInterface not found in shipperProviderMap. ('%1s')", preferredShipperAbbreviation));
}
return foundShipperInterface;
}
}
=============
Basically, I want to call the method, pass in a string argument, and have it choose the concrete for me. (if my real code, this is via a database value, but for the demo code, this is good enough)
Order ord = new Order();
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
BeanFactory factory = context;
OrderProcessorInterface opi = context.getBean(OrderProcessorImpl.class);
opi.ProcessOrder("myFedExName", ord); /* friendlyName would be nice, but fully qualified concrete name also assceptable */
My Spring Configuration is via xml:
<bean id="theLoggerBean"
class="org.apache.commons.logging.impl.Log4JLogger">
<constructor-arg value="log" />
</bean>
<bean id="fedExBean"
class="com.me.FedExShipper">
<constructor-arg ref="theLoggerBean"></constructor-arg>
</bean>
<bean id="uspsExBean"
class="com.me.FedExShipper">
<constructor-arg ref="theLoggerBean"></constructor-arg>
</bean>
<bean id="upsExBean"
class="com.me.FedExShipper">
<constructor-arg ref="theLoggerBean"></constructor-arg>
</bean>
..........
================================
<bean id="OrderProcessorImplBean"
class="com.me.OrderProcessorImpl">
<constructor-arg ref="theLoggerBean"></constructor-arg>
<constructor-arg ref="How do I do N Number of ShipperInterfaces Here ??"></constructor-arg>
</bean>
So I want to xml configure the 3 concretes.
And then inject them into the class.
But where I have "How do I do N Number of ShipperInterfaces Here ??", I have no idea what to do.
JSR 330 implementation preferred, but will take anything.
THANKS
Note, in the other question (the Guice one), this was also a possiblity for the constructor of the OrderProcessor:
public class OrderProcessorImpl implements OrderProcessorInterface {
private Log logger;
Set<ShipperInterface> shippers;
public OrderProcessorImpl(Log lgr, Set<ShipperInterface> shprs) {
if (null == lgr) {
throw new IllegalArgumentException("Log is null");
}
if (null == shprs) {
throw new IllegalArgumentException("ShipperInterface(s) is null");
}
this.logger = lgr;
this.shippers = shprs;
}
public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {
this.logger.info(String.format("About to ship. (%1s)", preferredShipperAbbreviation));
for (ShipperInterface sh : shippers) {
this.logger.info(String.format("ShipperInterface . (%1s)", sh.getClass().getSimpleName()));
}
}
}
Something like this should work. This uses #Autowired and not xml configuration:
#org.springframework.stereotype.Service
public class OrderProcessorImpl implements OrderProcessorInterface {
private List<ShipperInterface> shipperProviders;
private Map<String, ShipperInterface> shipperProvidersMap = new HashMap<>();
#Autowired
public void setShipperProviders(List<ShipperInterface> shipperProviders) {
this.shipperProviders= shipperProviders;
this.shipperProviders.stream().forEach(p->shipperProvidersMap .put(/* your code for getting the key */, p));
}
Gradle dependency hint:
compile group: 'org.springframework', name: 'spring-context', version: '5.1.9.RELEASE'
I think I have something that works:
beans.xml (note the "util" extras in the namespace declares)
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/util
http://www.springframework.org/schema/util/spring-util-2.5.xsd">
<bean id="theLoggerBean"
class="org.apache.commons.logging.impl.Log4JLogger">
<constructor-arg value="log" />
</bean>
<bean id="fedExShipperBean"
class="com.me.shipping.FedExShipper">
<constructor-arg ref="theLoggerBean"></constructor-arg>
</bean>
<bean id="upsShipperBean"
class="com.me.shipping.UpsShipper">
<constructor-arg ref="theLoggerBean"></constructor-arg>
</bean>
<bean id="uspsShipperBean"
class="com.me.shipping.UspsShipper">
<constructor-arg ref="theLoggerBean"></constructor-arg>
</bean>
<util:map id="shipperInterfaceMap" key-type="java.lang.String"
value-type="com.me.shipping.interfaces.ShipperInterface">
<entry key="fedexFriendlyName" value-ref="fedExShipperBean" />
<entry key="upsFriendlyName" value-ref="upsShipperBean" />
<entry key="uspsFriendlyName" value-ref="uspsShipperBean" />
</util:map>
<bean id="orderProcessorImplBean"
class="com.me.shipping.OrderProcessorImpl">
<constructor-arg ref="theLoggerBean"></constructor-arg>
<constructor-arg ref="shipperInterfaceMap"></constructor-arg>
</bean>
</beans>
and java
package com.me.shipping;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import com.me.shipping.interfaces.OrderProcessorInterface;
import com.me.shipping.interfaces.ShipperInterface;
import com.me.Models.Order;
public class OrderProcessorImpl implements OrderProcessorInterface {
private Log logger;
private java.util.Map<String, ShipperInterface> shipperInterfaceMap;
public OrderProcessorImpl(Log lgr, java.util.Map<String, ShipperInterface> siMap) {
if (null == lgr) {
throw new IllegalArgumentException("Log is null");
}
if (null == siMap) {
throw new IllegalArgumentException("Map<String, ShipperInterface> is null");
}
this.logger = lgr;
this.shipperInterfaceMap = siMap;
}
public void ProcessOrder(String preferredShipperAbbreviation, Order ord) {
this.logger.info(String.format("About to ship. (%1s)", preferredShipperAbbreviation));
ShipperInterface foundShipperInterface = this.FindShipperInterface(preferredShipperAbbreviation);
foundShipperInterface.ShipOrder(ord);
}
private ShipperInterface FindShipperInterface(String friendlyName)
{
ShipperInterface returnItem = null;
if (null != this.shipperInterfaceMap)
{
returnItem = this.shipperInterfaceMap.entrySet().stream()
.filter(e -> e.getKey().equalsIgnoreCase(friendlyName))
.map(Map.Entry::getValue)
.findFirst()
.orElse(null);
}
if (null == returnItem)
{
throw new NullPointerException(String.format("shipperProviderMap did not contain expected item. (Key='%s')", friendlyName));
}
return returnItem;
}
}
and "main" method
ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
BeanFactory factory = context;
Order ord = new Order();
OrderProcessorInterface opi = context.getBean(OrderProcessorImpl.class);
opi.ProcessOrder("fedexFriendlyName", ord);
Hi i have created this class to handle all spring security exception :
import org.apache.cxf.binding.soap.SoapFault;
import org.apache.cxf.binding.soap.SoapMessage;
import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
/**
* The Class SoapAuthenticationInterceptor.
*/
public class SoapAuthenticationInterceptor extends AbstractPhaseInterceptor<SoapMessage> implements InitializingBean {
/** The authentication manager. */
private AuthenticationManager authenticationManager;
/** The authentication required. */
private boolean authenticationRequired = true;
/**
* Instantiates a new soap authentication interceptor.
*/
public SoapAuthenticationInterceptor() {
super(Phase.RECEIVE);
}
/**
* Sets the authentication manager.
*
* #param authenticationManager
* the new authentication manager
*/
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* Sets the authentication required.
*
* #param authenticationRequired
* the new authentication required
*/
public void setAuthenticationRequired(boolean authenticationRequired) {
this.authenticationRequired = authenticationRequired;
}
/*
* (non-Javadoc)
*
* #see
* org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
public void afterPropertiesSet() throws Exception {
if (authenticationManager == null) {
throw new IllegalStateException("No authentication manager has been configured");
}
}
/*
* (non-Javadoc)
*
* #see org.apache.cxf.interceptor.Interceptor#handleMessage(org.apache.cxf.
* message.Message)
*/
public void handleMessage(SoapMessage message) throws Fault {
Authentication authentication = message.getExchange().get(Authentication.class);
if (authentication != null) {
try {
authentication = authenticationManager.authenticate(authentication);
message.getExchange().put(Authentication.class, authentication);
} catch (AuthenticationException ex) {
throw new SoapFault("Bad credentials", message.getVersion().getSender());
}
} else if (authenticationRequired) {
throw new SoapFault("Authentication required", message.getVersion().getSender());
}
}
}
then in my applicationContext.xml i have configured this intercepter like this:
<bean id="soapAuthenticationInterceptor" class="com.test.cxf.interceptors.SoapAuthenticationInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
</bean>
<cxf:bus>
<cxf:features>
<cxf:logging />
</cxf:features>
<cxf:inInterceptors>
<ref bean="soapAuthenticationInterceptor" />
</cxf:inInterceptors>
<cxf:outFaultInterceptors>
<ref bean="soapAuthenticationInterceptor" />
</cxf:outFaultInterceptors>
</cxf:bus>
my probleme is when i send a soapui enveloppe with a bad login/password the Interceptor is not called ?
can you please help me ?
Th solution is to override DigestAuthenticationEntryPoint.for the code class is in this url
I'm having difficulty figuring out just how exactly one would access CAS released attributes in a servlet using Spring Security and Spring MVC. Traditionally, in a Spring-less implementation, I'd do something like this
public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
// Gets the user ID from CAS
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();
final Map<String, Object> attributes = principal.getAttributes();
String userId = (String) attributes.get("userid");
// ...
}
When creating a servlet using Spring MVC, but without Spring Security, there seemed to be basically no difference in accessing the attributes:
#RequestMapping("/")
public String welcome(HttpServletRequest request)
{
// Get the user ID from CAS
AttributePrincipal principal = (AttributePrincipal) request.getUserPrincipal();;
final Map<String, Object> attributes = principal.getAttributes();
userId = (String) attributes.get("userid");
// ...
}
However, after implementing Spring Security, request.getUserPrincipal() returns a CasAuthenticationToken rather than an AttributePrincipal. From what I noticed, none of the retrievable objects and data from this contained any of the CAS released attributes.
After a bit of looking around, I did notice something with mentioning the GrantedAuthorityFromAssertionAttributesUserDetailsService class, so I changed my security context .xml from
<security:user-service id="userService">
<security:user name="user" password="user" authorities="ROLE_ADMIN,ROLE_USER" />
</security:user-service>
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<property name="authenticationUserDetailsService">
<bean class="org.springframework.security.core.userdetails.UserDetailsByNameServiceWrapper">
<constructor-arg ref="userService" />
</bean>
</property>
<property name="serviceProperties" ref="serviceProperties" />
<property name="ticketValidator">
<bean class="org.jasig.cas.client.validation.Saml11TicketValidator">
<constructor-arg value="https://localhost:8443/cas" />
</bean>
</property>
<property name="key" value="casAuthProviderKey" />
</bean>
to
<bean id="casAuthenticationProvider" class="org.springframework.security.cas.authentication.CasAuthenticationProvider">
<property name="authenticationUserDetailsService">
<bean class="org.springframework.security.cas.userdetails.GrantedAuthorityFromAssertionAttributesUserDetailsService">
<constructor-arg>
<list>
<value>userid</value>
</list>
</constructor-arg>
</bean>
</property>
<property name="serviceProperties" ref="serviceProperties" />
<property name="ticketValidator">
<bean class="org.jasig.cas.client.validation.Saml11TicketValidator">
<constructor-arg value="https://localhost:8443/cas" />
</bean>
</property>
<property name="key" value="casAuthProviderKey" />
</bean>
Then, through a considerably more roundabout method, I could access the userid attribute by doing something like this:
#RequestMapping("/")
public String welcome(HttpServletRequest request)
{
CasAuthenticationToken principal = (CasAuthenticationToken) request.getUserPrincipal();
UserDetails userDetails = principal.getUserDetails();
Collection<SimpleGrantedAuthority> authorities = (Collection<SimpleGrantedAuthority>) userDetails.getAuthorities();
Iterator<SimpleGrantedAuthority> it = authorities.iterator();
String userid = it.next().getAuthority();
// ...
}
However, besides being a little more lengthy than previous implementations, it doesn't seem possible to support map multiple attributes from CAS (say, if CAS were also releasing firstName and lastName attributes).
Is there a better way of setting up the security context .xml to allow easier access of these attributes, especially if there are multiples that I want to use in a web app?
I think I figured it out. Outside of setting the attributes as authorities, which may be useful if you're using those to determine permission (i.e. hasAuthority('username')), it seems like the only other way is to construct your own UserDetails and UserDetailsService classes.
For example, MyUser:
package my.custom.springframework.security.userdetails;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
public class MyUser extends User
{
private static final long serialVersionUID = 1L;
private String id;
private String lastName;
private String firstName;
public MyUser(
String username,
String password,
String id,
String lastName,
String firstName,
Collection<? extends GrantedAuthority> authorities)
{
super(username, password, authorities);
this.id = id;
this.lastName = lastName;
this.firstName = firstName;
}
public String getId()
{
return id;
}
public String getLastName()
{
return lastName;
}
public String getFirstName()
{
return firstName;
}
}
Then, borrowing some of the structure of GrantedAuthorityFromAssertionAttributesUserDetailsService and JdbcDaoImpl, I created a MyUserDetailsService:
package my.custom.springframework.security.userdetails;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
import org.jasig.cas.client.authentication.AttributePrincipal;
import org.jasig.cas.client.validation.Assertion;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.security.cas.userdetails.AbstractCasAssertionUserDetailsService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
public final class MyUserDetailsService extends AbstractCasAssertionUserDetailsService
{
public static final String DEF_USERS_BY_ID_QUERY = "select ?, id, last_name, first_name " +
"from users " + "where id = ?";
public static final String DEF_AUTHORITIES_BY_ID_QUERY = "select role " +
"from roles join users on users.username = roles.username " +
"where users.id = ?";
private static final String NON_EXISTENT_PASSWORD_VALUE = "NO_PASSWORD";
private JdbcTemplate jdbcTemplate;
private String usersByIdQuery;
private String authoritiesByIdQuery;
public MyUserDetailsService(DataSource dataSource)
{
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.usersByIdQuery = DEF_USERS_BY_ID_QUERY;
this.authoritiesByIdQuery = DEF_AUTHORITIES_BY_ID_QUERY;
}
protected MyUser loadUserDetails(Assertion assertion)
{
AttributePrincipal attributePrincipal = assertion.getPrincipal();
String username = attributePrincipal.getName();
String id = (String) attributePrincipal.getAttributes().get("userid");
MyUser user = loadUser(username, id);
Set<GrantedAuthority> dbAuthsSet = new HashSet<GrantedAuthority>();
dbAuthsSet.addAll(loadUserAuthorities(id));
List<GrantedAuthority> dbAuths = new ArrayList<GrantedAuthority>(dbAuthsSet);
return createMyUser(username, user, dbAuths);
}
protected MyUser loadUser(String username, String id)
{
return jdbcTemplate.queryForObject(usersByIdQuery, new String[] { username, id },
new RowMapper<MyUser>()
{
public MyUser mapRow(ResultSet rs, int rowNum) throws SQLException
{
String username = rs.getString(1);
String id = rs.getString(2);
String lastName = rs.getString(3);
String firstName = rs.getString(4);
return new MyUser(username, NON_EXISTENT_PASSWORD_VALUE, id, lastName, firstName,
AuthorityUtils.NO_AUTHORITIES);
}
});
}
protected List<GrantedAuthority> loadUserAuthorities(String id)
{
return jdbcTemplate.query(authoritiesByIdQuery, new String[] { id },
new RowMapper<GrantedAuthority>()
{
public GrantedAuthority mapRow(ResultSet rs, int rowNum) throws SQLException
{
// TODO Replace with rolePrefix variable
String roleName = "ROLE_" + rs.getString(1);
return new SimpleGrantedAuthority(roleName);
}
});
}
protected MyUser createMyUser(String username,
MyUser userFromUserQuery, List<GrantedAuthority> combinedAuthorities)
{
return new MyUser(username, userFromUserQuery.getPassword(),
userFromUserQuery.getId(), userFromUserQuery.getLastName(), userFromUserQuery.getFirstName(),
combinedAuthorities);
}
}
Finally, I set the authenticationUserDetailsService in my casAuthenticationProvider to use this class, passing in a global datasource from my container (Tomcat 6 in this case):
...
<property name="authenticationUserDetailsService">
<bean class="my.custom.springframework.security.userdetails.MyUserDetailsService">
<constructor-arg>
<jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/my/conn"/>
</constructor-arg>
</bean>
</property>
...
i tried with ....
<sec:logout invalidate-session="true" logout-success-url="/logoutsuccess" logouturl="/logout/>
but it is not working properly....
i want to clear everything like refresh token and access token session , cookies when user logout....
my security-servlet.xml looks like this
<!-- Protected resources -->
<sec:http create-session="never" entry-point-ref="oauthAuthenticationEntryPoint"
access-decision-manager-ref="accessDecisionManager"
xmlns="http://www.springframework.org/schema/security">
<sec:anonymous enabled="false" />
<sec:intercept-url pattern="/data/user/*"
access="IS_AUTHENTICATED_FULLY" />
<sec:logout delete-cookies="JSESSIONID" invalidate-session="true" />
<sec:custom-filter ref="resourceServerFilter"
before="PRE_AUTH_FILTER" />
<sec:access-denied-handler ref="oauthAccessDeniedHandler" />
</sec:http>
In Spring-boot application I will:
1. get OAuth2AccessToken
2. using it will delete OAuth2RefreshToken
3. and then delete itself
#Component
public class CustomLogoutSuccessHandler
extends AbstractAuthenticationTargetUrlRequestHandler
implements LogoutSuccessHandler {
private static final String BEARER_AUTHENTICATION = "Bearer ";
private static final String HEADER_AUTHORIZATION = "authorization";
#Autowired
private TokenStore tokenStore;
#Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse,
Authentication authentication) throws IOException, ServletException {
String token = httpServletRequest.getHeader(HEADER_AUTHORIZATION);
if (token != null && token.startsWith(BEARER_AUTHENTICATION)) {
String accessTokenValue = token.split(" ")[1];
OAuth2AccessToken oAuth2AccessToken = tokenStore.readAccessToken(accessTokenValue);
if (oAuth2AccessToken != null) {
OAuth2RefreshToken oAuth2RefreshToken = oAuth2AccessToken.getRefreshToken();
if (oAuth2RefreshToken != null)
tokenStore.removeRefreshToken(oAuth2RefreshToken);
tokenStore.removeAccessToken(oAuth2AccessToken);
}
}
httpServletResponse.setStatus(HttpServletResponse.SC_OK);
}
}
you can do these things into sessionDestroyedListener...almost look like this..
In this code i am updating lastLogout date ..you can do what you want
#Component("sessionDestroyedEventListener")
public class SessionDestroyedEventListener implements ApplicationListener<SessionDestroyedEvent>{
// private static Logger logger = BaseLogger.getLogger(AuthenticationEventListener.class);
#Autowired
private AuthenticationService authenticationService;
public void setAuthenticationService(AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
/**
* Capture sessionDestroyed event and update lastLogout date after session destroyed of particular user.
*/
#Override
public void onApplicationEvent(SessionDestroyedEvent appEvent) {
SessionDestroyedEvent event = (SessionDestroyedEvent) appEvent;
Object obj = null;
UserInfo userInfo = null;
ArrayList<SecurityContext> sc = (ArrayList<SecurityContext>) event.getSecurityContexts();
Iterator<SecurityContext> itr = sc.iterator();
while (itr.hasNext()) {
obj = itr.next().getAuthentication().getPrincipal();
if (obj instanceof UserInfo) {
userInfo = (UserInfo) obj;
} else {
String userCode = (String) obj;
if (userCode == null || "".equals(userCode)) {
userCode = "UnDefinedUser";
}
userInfo = new UserInfo(userCode);
}
//authenticationService.updateLastLogoutDate(userInfo.getUsername());
}
}
}
I have a pretty standard implementation of spring security saml into my application in addition to other authentication mechanisms. Out of the box SAML will not be configured but can be configured through a form, so by default SAML should be disabled. I'd like to easily be able to toggle SAML on / off but am not sure what the best way to do this would be.
It seems like one approach would be to do a custom FilterChainProxy where if I check if saml is enabled and if so to ignore the samlFilter chain(How to delete one filter from default filter stack in Spring Security?) and also do a similar implementation for the Metadata Generator Filter.
Any advice would be great.
Here is my config:
<http auto-config="false" use-expressions="true"
access-decision-manager-ref="webAccessDecisionManager"
disable-url-rewriting="false"
create-session="never"
authentication-manager-ref="authenticationManager">
<custom-filter before="FIRST" ref="metadataGeneratorFilter"/>
<custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter"/>
</http>
Metadata Generator Filter:
<beans:bean id="metadataGeneratorFilter" class="org.springframework.security.saml.metadata.MetadataGeneratorFilter">
<beans:constructor-arg>
<beans:bean class="org.springframework.security.saml.metadata.MetadataGenerator">
<beans:property name="entityId" value="${saml.entityId}"/>
<beans:property name="signMetadata" value="${saml.signMetadata}"/>
</beans:bean>
</beans:constructor-arg>
</beans:bean>
Saml Filter:
<beans:bean id="samlFilter" class="org.springframework.security.web.FilterChainProxy">
<filter-chain-map request-matcher="ant">
<filter-chain pattern="/saml/login/**" filters="samlEntryPoint"/>
<filter-chain pattern="/saml/logout/**" filters="samlLogoutFilter"/>
<filter-chain pattern="/saml/metadata/**" filters="metadataDisplayFilter"/>
<filter-chain pattern="/saml/SSO/**" filters="samlWebSSOProcessingFilter"/>
<filter-chain pattern="/saml/SSOHoK/**" filters="samlWebSSOHoKProcessingFilter"/>
<filter-chain pattern="/saml/SingleLogout/**" filters="samlLogoutProcessingFilter"/>
<filter-chain pattern="/saml/discovery/**" filters="samlIDPDiscovery"/>
</filter-chain-map>
</beans:bean>
EDIT: Here is my implementation, it is a bit hackish and relies on a deprecated method but it works
The below snippet disables MetadataGeneratorFilter:
public class MyMetadataGeneratorFilter extends MetadataGeneratorFilter {
private boolean isActive = false;
public MyMetadataGeneratorFilter(MetadataGenerator generator) {
super(generator);
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
if (isActive) {
processMetadataInitialization((HttpServletRequest) request);
}
chain.doFilter(request, response);
}
public void setActive(boolean active) {
isActive = active;
}
}
There is also the samlFilter / FilterChainMap which is autowired. If saml is enabled, I leave this chain as is, if it is disabled, I set the chain to an empty map in my service which enables / disables saml.
Upon initialization, I get the filterchainmap values:
private Map<RequestMatcher, List<Filter>> map;
#Override
public void init() throws ServiceException, MetadataProviderException {
SamlConfig samlConfig = getConfig();
map = samlFilter.getFilterChainMap();
applySamlConfig(samlConfig);
}
In the below method, I set the filter chain map to either the original map provided in the spring xml(if enabled) or an empty map (if disabled).
public void applySamlConfig(SamlConfig samlConfig) throws ServiceException, MetadataProviderException {
if (!samlConfig.isEnabled()) {
Map<RequestMatcher, List<Filter>> emptyMap = samlFilter.getFilterChainMap();
emptyMap.clear();
samlFilter.setFilterChainMap(emptyMap);
return;
}
samlFilter.setFilterChainMap(map);
i added a custom filter in the entry-point-ref definition. This filter skips all following filters if the feature is not enabled.
<security:http entry-point-ref="samlEntryPoint">
<security:intercept-url pattern="/**" access="IS_AUTHENTICATED_FULLY" />
<!-- This filter checks if the SSO-Feature is enabled - otherwise all following security filters will be skipped -->
<security:custom-filter before="BASIC_AUTH_FILTER" ref="ssoEnabledFilter"/>
<security:custom-filter before="FIRST" ref="metadataGeneratorFilter" />
<security:custom-filter after="BASIC_AUTH_FILTER" ref="samlFilter" />
The ssoEnabledFilter:
public class SsoEnabledFilter extends GenericFilterBean {
#Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain filterChain) throws IOException, ServletException {
boolean ssoEnabled = isSsoEnabled();
if (ssoEnabled) {
filterChain.doFilter(request, response);
} else {
request.getRequestDispatcher(((HttpServletRequest) request).getServletPath()).forward(request, response);
}
}
}
So far I've been implementing this using a custom Spring namespace which includes or skips certain beans based on the backend configuration and reloading of the Spring context in case the backend configuration changes.
Edit : fixed error signaled by TheTurkish
If you want to be able to switch the use of SAML on a running application, the simpler would be to use a wrapper around samlFilter. For example
public class FilterWrapper extends GenericFilterBean {
private Filter inner;
private boolean active;
private boolean targetFilterLifeCycle = false;
public Filter getInner() {
return inner;
}
public void setInner(Filter inner) {
this.inner = inner;
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
#Override
public void doFilter(ServletRequest sr, ServletResponse sr1, FilterChain fc) throws IOException, ServletException {
if (active) {
inner.doFilter(sr, sr1, fc);
}
else {
fc.doFilter(str,sr1);
}
}
#Override
protected void initFilterBean() throws ServletException {
super.initFilterBean();
if (inner == null) {
throw new ServletException("Inner cannot be null");
}
if (targetFilterLifeCycle) {
inner.init(getFilterConfig());
}
}
#Override
public void destroy() {
super.destroy();
if (inner != null && targetFilterLifeCycle) {
inner.destroy();
}
}
}
You can use it that way :
<bean id="samlFilter" class="...FilterWrapper" p:active="false">
<property name=inner>
<!-- the real samlFilter bean -->
<bean class="org.springframework.security.web.FilterChainProxy">
...
</bean>
</property>
</bean>
As it is a bean, you inject it where you want to activate/deactivate Saml and simple call :
samlFilter.setActive(active);