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
Related
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
After sending a logon message using quickfix/J I want to receive the raw incoming message and do my own thing with the message as far as decoding it goes. Using Single Binary Encoding (SBE)
https://github.com/real-logic/simple-binary-encoding
For example:
I send logon message 8=FIX.4.4^9=95^35=A^34=1^49=FROMComp^52=20151009-18:22:35.968^56=HistReplay^98=0^108=30^141=Y^553=ABC^554=ABC^10=238^ in the FIX format as per the target hosts instructions
http://www.cmegroup.com/confluence/display/EPICSANDBOX/MDP+3.0+-+TCP+Replay+Messages
Then the target computer sends back a heartbeat message in SBE format. The message from the target computer sends the message back in SBE format and using Quickfix/J message and messagecracker the raw data is not recognized or I just simply do not know a way to receive the raw data using fromApp
I want to intercept the raw data coming in so that i can send it to my own SBE decoder instead of using quickfix/J message and messagecracker. Anybody know how?
Application class
package tcpconnectiontest;
import java.util.logging.Level;
import java.util.logging.Logger;
import quickfix.Application;
import quickfix.DoNotSend;
import quickfix.FieldNotFound;
import quickfix.IncorrectDataFormat;
import quickfix.IncorrectTagValue;
import quickfix.Message;
import quickfix.MessageCracker;
import quickfix.RejectLogon;
import quickfix.SessionID;
import quickfix.UnsupportedMessageType;
import quickfix.field.MsgType;
/*
* 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.
*/
/**
*
* #author Administrator
*/
public class TestApplicationImpl extends MessageCracker implements Application {
#Override
public void fromAdmin(Message arg0, SessionID arg1) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, RejectLogon {
System.out.println("Successfully called fromAdmin for sessionId : "
+ arg0);
}
#Override
public void fromApp(Message arg0, SessionID arg1) throws FieldNotFound,
IncorrectDataFormat, IncorrectTagValue, UnsupportedMessageType {
System.out.println("Successfully called fromApp for sessionId : "
+ arg0);
crack(arg0, arg1);
System.out.println(arg0);
}
#Override
public void onCreate(SessionID arg0) {
System.out.println("Successfully called onCreate for sessionId : "
+ arg0);
}
#Override
public void onLogon(SessionID arg0) {
System.out.println("Successfully logged on for sessionId : " + arg0);
}
#Override
public void onLogout(SessionID arg0) {
System.out.println("Successfully logged out for sessionId : " + arg0);
}
#Override
public void toAdmin(Message message, SessionID sessionId) {
try {
System.out.println("Inside toAdmin");
final String msgType = message.getHeader().getString(MsgType.FIELD);
if(MsgType.LOGON.compareTo(msgType) == 0)
{
message.setString(quickfix.field.Username.FIELD, "MGE");
message.setString(quickfix.field.Password.FIELD, "MGE");
}
} catch (FieldNotFound ex) {
Logger.getLogger(TestApplicationImpl.class.getName()).log(Level.SEVERE, null, ex);
}
}
#Override
public void toApp(Message arg0, SessionID arg1) throws DoNotSend {
System.out.println("Message : " + arg0 + " for sessionid : " + arg1);
}
#Override
public void onMessage(Message message, SessionID sessionID)
throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue {
System.out.println("Inside Logon Message");
super.onMessage(message, sessionID);
System.out.println(message.toString());
}
}
main class
package tcpconnectiontest;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import quickfix.Application;
import quickfix.ConfigError;
import quickfix.DefaultMessageFactory;
import quickfix.FileLogFactory;
import quickfix.FileStoreFactory;
import quickfix.MessageFactory;
import quickfix.Session;
import quickfix.SessionID;
import quickfix.SessionNotFound;
import quickfix.SessionSettings;
import quickfix.SocketInitiator;
/**
*
* #author Administrator
*/
public class TCPConnectionTest {
private static WriteFIXMLMessage fixml = new WriteFIXMLMessage();
/**
* #param args the command line arguments
*/
public static void main(String[] args) throws IOException {
SocketInitiator socketInitiator = null;
try {
SessionSettings sessionSettings = new SessionSettings("C:\\ProgramData\\MDR\\sessionSettings.txt");
Application application = new TestApplicationImpl();
FileStoreFactory fileStoreFactory = new FileStoreFactory(sessionSettings);
FileLogFactory logFactory = new FileLogFactory(sessionSettings);
MessageFactory messageFactory = new DefaultMessageFactory();
socketInitiator = new SocketInitiator(application,
fileStoreFactory, sessionSettings, logFactory,
messageFactory);
socketInitiator.start();
//Thread.sleep(5000);
SessionID sessionId = socketInitiator.getSessions().get(0);
//sendLogonRequest(sessionId);
Thread.sleep(5000);
socketInitiator.getManagedSessions();
int i = 0;
do {
try {
if(socketInitiator.isLoggedOn())
{
sendMDRRequest(sessionId);
}
else
{
System.out.println(socketInitiator.isLoggedOn());
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
} while ((!socketInitiator.isLoggedOn()) && (i < 30));
} catch (ConfigError e) {
e.printStackTrace();
} catch (SessionNotFound e) {
e.printStackTrace();
} catch (Exception exp) {
exp.printStackTrace();
} finally {
if (socketInitiator != null) {
socketInitiator.stop(true);
}
}
}
private static void sendLogonRequest(SessionID sessionId)
throws SessionNotFound {
boolean sent = Session.sendToTarget(fixml.loginXML(), sessionId);
System.out.println("Logon Message Sent : " + sent);
}
private static void sendMDRRequest(SessionID sessionId)
throws SessionNotFound {
boolean sentnext = Session.sendToTarget(fixml.requestXML(), sessionId);
System.out.println("MDR Message Sent : " + sentnext);
}
}
Does anyone know how to configure a persistent token store for Mule OAuth Provider module?
Adding a normal object store does not support the org.mule.modules.oauth2.provider.token.TokenStore interface.
EDIT
I want to persist to file - disk.
EDIT 2
Flow with OAuth provider setup:
<mule xmlns:objectstore="http://www.mulesoft.org/schema/mule/objectstore" xmlns:context="http://www.springframework.org/schema/context"
xmlns:https="http://www.mulesoft.org/schema/mule/https" xmlns:tracking="http://www.mulesoft.org/schema/mule/ee/tracking" xmlns:json="http://www.mulesoft.org/schema/mule/json"
xmlns:mulexml="http://www.mulesoft.org/schema/mule/xml"
xmlns:scripting="http://www.mulesoft.org/schema/mule/scripting" xmlns:http="http://www.mulesoft.org/schema/mule/http" xmlns="http://www.mulesoft.org/schema/mule/core"
xmlns:doc="http://www.mulesoft.org/schema/mule/documentation"
xmlns:spring="http://www.springframework.org/schema/beans" version="EE-3.5.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:ss="http://www.springframework.org/schema/security" xmlns:mule-ss="http://www.mulesoft.org/schema/mule/spring-security"
xmlns:oauth2-provider="http://www.mulesoft.org/schema/mule/oauth2-provider"
xsi:schemaLocation="http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-current.xsd
http://www.mulesoft.org/schema/mule/json http://www.mulesoft.org/schema/mule/json/current/mule-json.xsd
http://www.mulesoft.org/schema/mule/xml http://www.mulesoft.org/schema/mule/xml/current/mule-xml.xsd
http://www.mulesoft.org/schema/mule/http http://www.mulesoft.org/schema/mule/http/current/mule-http.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-current.xsd
http://www.mulesoft.org/schema/mule/core http://www.mulesoft.org/schema/mule/core/current/mule.xsd
http://www.mulesoft.org/schema/mule/scripting http://www.mulesoft.org/schema/mule/scripting/current/mule-scripting.xsd
http://www.mulesoft.org/schema/mule/ee/tracking http://www.mulesoft.org/schema/mule/ee/tracking/current/mule-tracking-ee.xsd
http://www.mulesoft.org/schema/mule/https http://www.mulesoft.org/schema/mule/https/current/mule-https.xsd
http://www.mulesoft.org/schema/mule/oauth2-provider http://www.mulesoft.org/schema/mule/oauth2-provider/current/mule-oauth2-provider.xsd
http://www.mulesoft.org/schema/mule/spring-security http://www.mulesoft.org/schema/mule/spring-security/current/mule-spring-security.xsd
http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd
http://www.mulesoft.org/schema/mule/objectstore http://www.mulesoft.org/schema/mule/objectstore/current/mule-objectstore.xsd">
<spring:beans>
<spring:bean id="oauthTokenStore" name="oauthTokenStore" class="org.mule.util.store.TextFileObjectStore"/>
</spring:beans>
<spring:beans>
<ss:authentication-manager id="resourceOwnerAuthenticationManager">
<ss:authentication-provider>
<ss:user-service id="resourceOwnerUserService">
<ss:user name="${username}" password="${password}" authorities="RESOURCE_OWNER" />
</ss:user-service>
</ss:authentication-provider>
</ss:authentication-manager>
</spring:beans>
<mule-ss:security-manager>
<mule-ss:delegate-security-provider name="resourceOwnerSecurityProvider" delegate-ref="resourceOwnerAuthenticationManager" />
</mule-ss:security-manager>
<oauth2-provider:config name="blazeOauth2Provider"
providerName="Blaze" host="0.0.0.0" port="${blaze.esb.port.https}"
authorizationEndpointPath="api/1.0/authorize" accessTokenEndpointPath="api/1.0/token"
resourceOwnerSecurityProvider-ref="resourceOwnerSecurityProvider"
scopes="BLAH" doc:name="OAuth provider module"
tokenTtlSeconds="${blaze.security.token.lifespan}" connector-ref="httpsServerConnector" supportedGrantTypes="AUTHORIZATION_CODE IMPLICIT" enableRefreshToken="true" tokenStore-ref="oauthTokenStore" >
<oauth2-provider:clients>
<oauth2-provider:client clientId="${blaze.client.id}" secret="${blaze.client.secret}" type="CONFIDENTIAL" clientName="Client" description="Service Front-End">
<oauth2-provider:redirect-uris>
<oauth2-provider:redirect-uri>http://localhost*</oauth2-provider:redirect-uri>
</oauth2-provider:redirect-uris>
<oauth2-provider:authorized-grant-types>
<oauth2-provider:authorized-grant-type>AUTHORIZATION_CODE</oauth2-provider:authorized-grant-type>
<oauth2-provider:authorized-grant-type>TOKEN</oauth2-provider:authorized-grant-type>
</oauth2-provider:authorized-grant-types>
<oauth2-provider:scopes>
<oauth2-provider:scope>BLAH</oauth2-provider:scope>
</oauth2-provider:scopes>
</oauth2-provider:client>
</oauth2-provider:clients>
</oauth2-provider:config>
</mule>
ok, after performing a simple test, I recommend developing your own FileObjectStore for more control.
Create public class, for example:
public class MyFileObjectStore extends AbstractObjectStore { ..}
Use a properties file to store the token, key=value
Implement the methods: doStore, doRetrieve, doRemove, basically to update on a properties files.
Change in you flow:
<spring:bean id="accessTokenStore" class="test.MyFileObjectStore"/>
<spring: bean name="tokenStore" class="org.mule.modules.oauth2.provider.token.ObjectStoreTokenStore">
<spring:property name="accessTokenObjectStore" ref="accessTokenStore" />
There are several ways to set the tokenStore for oauth. You can use for example (the most common):
org.mule.util.store.PartitionedPersistentObjectStore or
org.mule.transport.jdbc.store.JdbcObjectStore
For your requirement, you can use:
org.mule.util.store.TextFileObjectStore
I hope to help;
Based on #Julio answer:
Added a class, that implements a map <String, AccessTokenStoreHolder>:
package xxx;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import org.mule.api.lifecycle.InitialisationException;
import org.mule.api.store.ObjectDoesNotExistException;
import org.mule.api.store.ObjectStoreException;
import org.mule.config.i18n.CoreMessages;
import org.mule.util.FileUtils;
import org.mule.util.IOUtils;
import org.mule.util.StringUtils;
import org.mule.util.store.InMemoryObjectStore;
import org.mule.modules.oauth2.provider.token.AccessTokenStoreHolder;
public class PersistantOAuthObjectStore extends InMemoryObjectStore<AccessTokenStoreHolder> {
protected File fileStore;
protected String directory;
private Map<String, AccessTokenStoreHolder> tokenStore;
private FileOutputStream output;
public PersistantOAuthObjectStore() {
super();
}
private Map<String, AccessTokenStoreHolder> getTokenStore() {
if (tokenStore == null)
tokenStore = new HashMap<>();
return tokenStore;
}
#Override
public void initialise() throws InitialisationException
{
super.initialise();
if (directory == null)
directory = context.getConfiguration().getWorkingDirectory() + "/objectstore";
try
{
File dir = FileUtils.openDirectory(directory);
fileStore = new File(dir, name + ".dat");
if (fileStore.exists())
loadFromStore();
}
catch (Exception e)
{
throw new InitialisationException(e, this);
}
}
#SuppressWarnings("unchecked")
protected synchronized void loadFromStore() throws Exception
{
ObjectInputStream stream = new ObjectInputStream(new FileInputStream(fileStore));
Object result = stream.readObject();
tokenStore = (Map<String, AccessTokenStoreHolder>)result;
for (Map.Entry<String, AccessTokenStoreHolder> entry : getTokenStore().entrySet())
super.store(entry.getKey().toString(), entry.getValue());
stream.close();
}
#Override
public void store(Serializable id, AccessTokenStoreHolder item) throws ObjectStoreException
{
super.store(id, item);
try
{
synchronized(getTokenStore()) {
getTokenStore().put(id.toString(), item);
saveMap();
}
}
catch (IOException e)
{
throw new ObjectStoreException(e);
}
}
private void saveMap() throws IOException {
if (output == null)
output = new FileOutputStream(fileStore, false);
ObjectOutputStream stream = new ObjectOutputStream(output);
stream.writeObject(getTokenStore());
}
#Override
public AccessTokenStoreHolder remove(Serializable key) throws ObjectStoreException
{
super.retrieve(key);
try
{
synchronized (getTokenStore())
{
if (getTokenStore().containsKey(key)) {
AccessTokenStoreHolder val = getTokenStore().get(key);
getTokenStore().remove(key);
saveMap();
return val;
}
}
throw new ObjectDoesNotExistException(CoreMessages.objectNotFound(key));
}
catch (IOException e)
{
throw new ObjectStoreException(e);
}
}
#Override
public void clear() throws ObjectStoreException
{
super.clear();
try
{
synchronized (getTokenStore()) {
getTokenStore().clear();
saveMap();
}
}
catch (IOException e)
{
throw new ObjectStoreException(e);
}
}
public String getDirectory()
{
return directory;
}
public void setDirectory(String directory)
{
this.directory = directory;
}
#Override
public boolean isPersistent() {
return true;
}
}
Then add 2 spring beans to xml:
<spring:bean id="oauthTokenStore" name="oauthTokenStore" class="org.mule.modules.oauth2.provider.token.ObjectStoreTokenStore">
<spring:property name="accessTokenObjectStore" ref="oauthObjectStore"/>
</spring:bean>
<spring:bean id="oauthObjectStore" class="com.vatit.blaze.esb.utils.objectStore.BlazePersistantObjectStore" init-method="initialise" destroy-method="dispose" name="oauthObjectStore">
<spring:property name="name" value="oauthObjectStore"/>
</spring:bean>
Then ref the tokenStore in your OAuth 2 provider config:
tokenStore-ref="oauthTokenStore"
I think the above answers are correct but not ideal. ObjectStoreTokenStore can be composed of multiple persistent object stores. I am not sure if you even need to write any Java code to have this done.
<spring:bean name="tokenStore" class="org.mule.modules.oauth2.provider.token.ObjectStoreTokenStore">
<spring:property name="accessTokenObjectStore" ref="accessTokenFileObjectStore"/>
<spring:property name="refreshTokenObjectStore" ref="refreshTokenFileObjectStore"/>
</spring:bean>
Here accessTokenFileObjectStore and refreshTokenFileObjectStore can be spring beans created out from TextFileObjectStore
I wrote a small service class which return a list with 1000 strings.
I am using Spring Httpinvoker to get the service and read the list. If the number of the elements in the list is 100 all is going well when
I try 1000 it freeze utill there is a connection reset
The client side is JUnit 4 class with Spring runner on the same machine
by the way the same is happening with Hessian protocol using the Spring Remoting classes.
They are both HTML based but this is the only connection I can see RMI and JMS RMI (thorugh Spring remoting) is working fine with the same service
The service code
public class DateServiceImpl implements DateService {
/* (non-Javadoc)
* #see com.successcharging.rmiexample.DateService#getDate()
*/
#Override
public Date getDate() {
return new Date();
}
#Override
public List<String> getBigList() {
List<String> listData = new ArrayList<String>();
for (int i = 0 ; i < 100;i++) {
listData.add(Math.random()+"");
}
return listData;
}
}
The mapping server side
<!-- The service to use this is the server side -->
<bean id="dateServiceServer" class="com.successcharging.rmiexample.server.DateServiceImpl" />
<!-- the http invoker protocol -->
<bean name="/DateServiceHttpInvoker"
class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter">
<property name="service" ref="dateServiceServer" />
<property name="serviceInterface"
value="com.successcharging.rmiexample.server.DateService" />
</bean>
The client mapping
The junit code
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class DateServiceTest extends BaseTest {
#Resource
private DateService dateServiceHttpInvokerClient;
#Test
public void testDateServiceHttpInvoker() {
List<String> data = dateServiceHttpInvokerClient.getBigList(); //Here it is get stuck
data.add("My test");
System.out.println("HttpInvoker:"
+ data.size());
}
public DateService getDateServiceHttpInvokerClient() {
return dateServiceHttpInvokerClient;
}
public void setDateServiceHttpInvokerClient(
DateService dateServiceHttpInvoker) {
this.dateServiceHttpInvokerClient = dateServiceHttpInvoker;
}
}
Any ideas ?
Can they work together?
Some project sample would be great.
I have a web-app on Spring3. And i need to implement NTLM. Spring stopped NTLM support in 3rd version. Is there any possibilities to implement it?
Looking for a sample project.
They can be used together. Essentially what you want to do is hook into the SPNEGO protocol and detect when you receive an NTLM packet from the client. A good description of the protocol can be found here:
http://www.innovation.ch/personal/ronald/ntlm.html
http://blogs.technet.com/b/tristank/archive/2006/08/02/negotiate-this.aspx
Another great resource for NTLM is this:
http://davenport.sourceforge.net/ntlm.html
But you asked for a sample so here goes. To detect an NTLM packet you need to base64
decode the packet and inspect for a starting string:
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("Authorization");
if ((header != null) && header.startsWith("Negotiate ")) {
if (logger.isDebugEnabled()) {
logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
}
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] decodedToken = Base64.decode(base64Token);
if (isNTLMMessage(decodedToken)) {
authenticationRequest = new NTLMServiceRequestToken(decodedToken);
}
...
}
public static boolean isNTLMMessage(byte[] token) {
for (int i = 0; i < 8; i++) {
if (token[i] != NTLMSSP_SIGNATURE[i]) {
return false;
}
}
return true;
}
public static final byte[] NTLMSSP_SIGNATURE = new byte[]{
(byte) 'N', (byte) 'T', (byte) 'L', (byte) 'M',
(byte) 'S', (byte) 'S', (byte) 'P', (byte) 0
};
You'll need to make an authentication provider that can handle that type of authenticationRequest:
import jcifs.Config;
import jcifs.UniAddress;
import jcifs.ntlmssp.NtlmMessage;
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import jcifs.ntlmssp.Type3Message;
import jcifs.smb.NtlmPasswordAuthentication;
import jcifs.smb.SmbSession;
import jcifs.util.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AccountStatusUserDetailsChecker;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import javax.annotation.PostConstruct;
import java.io.IOException;
/**
* User: gcermak
* Date: 3/15/11
* <p/>
*/
public class ActiveDirectoryNTLMAuthenticationProvider implements AuthenticationProvider, InitializingBean {
protected String defaultDomain;
protected String domainController;
protected UserDetailsChecker userDetailsChecker = new AccountStatusUserDetailsChecker();
public ActiveDirectoryNTLMAuthenticationProvider(){
Config.setProperty( "jcifs.smb.client.soTimeout", "1800000" );
Config.setProperty( "jcifs.netbios.cachePolicy", "1200" );
Config.setProperty( "jcifs.smb.lmCompatibility", "0" );
Config.setProperty( "jcifs.smb.client.useExtendedSecurity", "false" );
}
#Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
NTLMServiceRequestToken auth = (NTLMServiceRequestToken) authentication;
byte[] token = auth.getToken();
String name = null;
String password = null;
NtlmMessage message = constructNTLMMessage(token);
if (message instanceof Type1Message) {
Type2Message type2msg = null;
try {
type2msg = new Type2Message(new Type1Message(token), getChallenge(), null);
throw new NtlmType2MessageException(Base64.encode(type2msg.toByteArray()));
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
}
if (message instanceof Type3Message) {
final Type3Message type3msg;
try {
type3msg = new Type3Message(token);
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
final byte[] lmResponse = (type3msg.getLMResponse() != null) ? type3msg.getLMResponse() : new byte[0];
final byte[] ntResponse = (type3msg.getNTResponse() != null) ? type3msg.getNTResponse() : new byte[0];
NtlmPasswordAuthentication ntlmPasswordAuthentication = new NtlmPasswordAuthentication(type3msg.getDomain(), type3msg.getUser(), getChallenge(), lmResponse, ntResponse);
String username = ntlmPasswordAuthentication.getUsername();
String domain = ntlmPasswordAuthentication.getDomain();
String workstation = type3msg.getWorkstation();
name = ntlmPasswordAuthentication.getName();
password = ntlmPasswordAuthentication.getPassword();
}
// do custom logic here to find the user ...
userDetailsChecker.check(user);
return new UsernamePasswordAuthenticationToken(user, password, user.getAuthorities());
}
// The Client will only ever send a Type1 or Type3 message ... try 'em both
protected static NtlmMessage constructNTLMMessage(byte[] token) {
NtlmMessage message = null;
try {
message = new Type1Message(token);
return message;
} catch (IOException e) {
if ("Not an NTLMSSP message.".equals(e.getMessage())) {
return null;
}
}
try {
message = new Type3Message(token);
return message;
} catch (IOException e) {
if ("Not an NTLMSSP message.".equals(e.getMessage())) {
return null;
}
}
return message;
}
protected byte[] getChallenge() {
UniAddress dcAddress = null;
try {
dcAddress = UniAddress.getByName(domainController, true);
return SmbSession.getChallenge(dcAddress);
} catch (IOException e) {
throw new NtlmAuthenticationFailure(e.getMessage());
}
}
#Override
public boolean supports(Class<? extends Object> auth) {
return NTLMServiceRequestToken.class.isAssignableFrom(auth);
}
#Override
public void afterPropertiesSet() throws Exception {
// do nothing
}
public void setSmbClientUsername(String smbClientUsername) {
Config.setProperty("jcifs.smb.client.username", smbClientUsername);
}
public void setSmbClientPassword(String smbClientPassword) {
Config.setProperty("jcifs.smb.client.password", smbClientPassword);
}
public void setDefaultDomain(String defaultDomain) {
this.defaultDomain = defaultDomain;
Config.setProperty("jcifs.smb.client.domain", defaultDomain);
}
/**
* 0: Nothing
* 1: Critical [default]
* 2: Basic info. (Can be logged under load)
* 3: Detailed info. (Highest recommended level for production use)
* 4: Individual smb messages
* 6: Hex dumps
* #param logLevel the desired logging level
*/
public void setDebugLevel(int logLevel) throws Exception {
switch(logLevel) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 6:
Config.setProperty("jcifs.util.loglevel", Integer.toString(logLevel));
break;
default:
throw new Exception("Invalid Log Level specified");
}
}
/**
*
* #param winsList a comma separates list of wins addresses (ex. 10.169.10.77,10.169.10.66)
*/
public void setNetBiosWins(String winsList) {
Config.setProperty("jcifs.netbios.wins", winsList);
}
public void setDomainController(String domainController) {
this.domainController = domainController;
}
}
And finally you need to tie it all together in your spring_security.xml file:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:jdbc="http://www.springframework.org/schema/jdbc"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jdbc
http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.1.xsd">
<http auto-config="true" use-expressions="true" disable-url-rewriting="true">
<form-login login-page="/auth/login"
login-processing-url="/auth/j_security_check"/>
<remember-me services-ref="rememberMeServices"/>
<logout invalidate-session="true" logout-success-url="/auth/logoutMessage" logout-url="/auth/logout"/>
<access-denied-handler error-page="/error/accessDenied"/>
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="myUsernamePasswordUserDetailsService">
<password-encoder ref="passwordEncoder">
<salt-source ref="saltSource"/>
</password-encoder>
</authentication-provider>
<authentication-provider ref="NTLMAuthenticationProvider"/>
</authentication-manager>
</beans:beans>
Lastly you need to know how to tie it all together. The protocol as described in the first set of links shows that there are a couple round trips that you need to make between the client and server. Thus in your filter you need a bit more logic:
import jcifs.ntlmssp.Type1Message;
import jcifs.ntlmssp.Type2Message;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.codec.Base64;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.extensions.kerberos.KerberosServiceRequestToken;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.util.Assert;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* User: gcermak
* Date: 12/5/11
*/
public class SpnegoAuthenticationProcessingFilter extends GenericFilterBean {
private AuthenticationManager authenticationManager;
private AuthenticationSuccessHandler successHandler;
private AuthenticationFailureHandler failureHandler;
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
String header = request.getHeader("Authorization");
if ((header != null) && header.startsWith("Negotiate ")) {
if (logger.isDebugEnabled()) {
logger.debug("Received Negotiate Header for request " + request.getRequestURL() + ": " + header);
}
byte[] base64Token = header.substring(10).getBytes("UTF-8");
byte[] decodedToken = Base64.decode(base64Token);
// older versions of ie will sometimes do this
// logic cribbed from jcifs filter implementation jcifs.http.NtlmHttpFilter
if (request.getMethod().equalsIgnoreCase("POST")) {
if (decodedToken[8] == 1) {
logger.debug("NTLM Authorization header contains type-1 message. Sending fake response just to pass this stage...");
Type1Message type1 = new Type1Message(decodedToken);
// respond with a type 2 message, where the challenge is null since we don't
// care about the server response (type-3 message) since we're already authenticated
// (This is just a by-pass - see method javadoc)
Type2Message type2 = new Type2Message(type1, new byte[8], null);
String msg = jcifs.util.Base64.encode(type2.toByteArray());
response.setHeader("WWW-Authenticate", "Negotiate " + msg);
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
return;
}
}
Authentication authenticationRequest = null;
if (isNTLMMessage(decodedToken)) {
authenticationRequest = new NTLMServiceRequestToken(decodedToken);
}
Authentication authentication;
try {
authentication = authenticationManager.authenticate(authenticationRequest);
} catch (NtlmBaseException e) {
// this happens during the normal course of action of an NTLM authentication
// a type 2 message is the proper response to a type 1 message from the client
// see: http://www.innovation.ch/personal/ronald/ntlm.html
response.setHeader("WWW-Authenticate", e.getMessage());
response.setHeader("Connection", "Keep-Alive");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.setContentLength(0);
response.flushBuffer();
return;
} catch (AuthenticationException e) {
// That shouldn't happen, as it is most likely a wrong configuration on the server side
logger.warn("Negotiate Header was invalid: " + header, e);
SecurityContextHolder.clearContext();
if (failureHandler != null) {
failureHandler.onAuthenticationFailure(request, response, e);
} else {
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.flushBuffer();
}
return;
}
if (successHandler != null) {
successHandler.onAuthenticationSuccess(request, response, authentication);
}
SecurityContextHolder.getContext().setAuthentication(authentication);
}
chain.doFilter(request, response);
}
public void setAuthenticationManager(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
public void setSuccessHandler(AuthenticationSuccessHandler successHandler) {
this.successHandler = successHandler;
}
public void setFailureHandler(AuthenticationFailureHandler failureHandler) {
this.failureHandler = failureHandler;
}
#Override
public void afterPropertiesSet() throws ServletException {
super.afterPropertiesSet();
Assert.notNull(this.authenticationManager, "authenticationManager must be specified");
}
}
You'll see that in the exception we use "Negotiate" rather than NTLM:
/**
* User: gcermak
* Date: 12/5/11
*/
public class NtlmType2MessageException extends NtlmBaseException {
private static final long serialVersionUID = 1L;
public NtlmType2MessageException(final String type2Msg) {
super("Negotiate " + type2Msg);
}
}
The spring filter (above) was largely patterned on jcifs.http.NtlmHttpFilter which you can find in the source for jcifs here:
http://jcifs.samba.org/
This isn't a whole, downloadable project as you requested but if there is interest from the community I could add this NTLM code to my github project:
http://git.springsource.org/~grantcermak/spring-security/activedirectory-se-security
Hope this helps!
Grant