This blog explains how to write a custom grant handler to WSO2 API Manager to support NTLM grant type.
If you are going to try this please note you should be in a Windows environment. If you are trying this on a single machine (Client and server user accounts on a same computer), you would be able to try this without any issues (I have tried on a Windows 7/8 environments). But if you need to try an actual production environment (clients and server are different computers) , first of all you need to setup a Active Directory Domain. You can follow link [2] to setup that.
In this process we need to deal with Windows Platform related APIs. For that we can use Java Native Access (JNA) [3] library which is a simple way of native access without requiring to write JNI (Java Native Interface) code. There is another library which is built on top of JNA which is Waffle [4] which encapsulates all functionality you need to implement user authentication.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<dependency> | |
<groupId>net.java.dev.jna</groupId> | |
<artifactId>jna</artifactId> | |
<version>4.2.1</version> | |
</dependency> | |
<dependency> | |
<groupId>com.github.dblock.waffle</groupId> | |
<artifactId>waffle-jna</artifactId> | |
<version>1.8.0</version> | |
</dependency> |
First step of the NTLM handshake is to create the Type1 token by the client.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
String securityPackage = "Negotiate"; | |
WindowsSecurityContextImpl clientContext = null; | |
IWindowsCredentialsHandle clientCredentials = null; | |
// client credentials handle | |
clientCredentials = WindowsCredentialsHandleImpl.getCurrent(securityPackage); | |
clientCredentials.initialize(); | |
// initial client security context | |
clientContext = new WindowsSecurityContextImpl(); | |
clientContext.setPrincipalName(WindowsAccountImpl.getCurrentUsername()); | |
System.out.println("username :" + WindowsAccountImpl.getCurrentUsername()); | |
clientContext.setCredentialsHandle(clientCredentials.getHandle()); | |
clientContext.setSecurityPackage(securityPackage); | |
clientContext.initialize(null, null, "localhost"); | |
String clientTokenType1 = Base64.encode(clientContext.getToken()); | |
System.out.println("Generated NTLM token Type1:" + clientTokenType1); |
Server (APIM) recieves the token from the request header. Then it validates the Type 1 token and generates a Type 2 token. It is sent back to the client. Then client should generate a Type 3 token based on Type 2 token. It is sent to the server as a Header.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* client receives a Token 2 as a Header in following format | |
WWW-Authenticate: NTLM <token2> | |
*/ | |
Header tokenHeader = response.getFirstHeader("WWW-Authenticate"); | |
String clientTokenType2 = tokenHeader.getValue().trim().split(" ")[1]; | |
System.out.println("Recieved NTLM token Type2 From Server:" + clientTokenType2); | |
byte[] decodedToken2 = Base64.decode(clientTokenType2); | |
Sspi.SecBufferDesc continueToken = new Sspi.SecBufferDesc(Sspi.SECBUFFER_TOKEN, decodedToken2); | |
clientContext.initialize(clientContext.getHandle(), continueToken, "localhost"); | |
byte[] clientTokenType3 = clientContext.getToken(); | |
String encodedToken3 = Base64.encode(clientTokenType3); | |
System.out.println("Generated NTLM token Type3:" + encodedToken3); | |
//send the token 3 to server | |
response = invokeNTLMTokenEndpoint(encodedToken3); | |
HttpEntity entity = response.getEntity(); | |
String responseString = EntityUtils.toString(entity, "UTF-8"); | |
System.out.println(responseString); |
Following function can be used to determine the type of the token.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public int getNLTMMessageType(byte[] decodedNLTMMessage) throws IdentityOAuth2Exception { | |
int messageType; | |
if (decodedNLTMMessage.length > 8) { | |
messageType = decodedNLTMMessage[8]; | |
} else { | |
throw new IdentityOAuth2Exception( | |
"Cannot extract message type from NLTM Token. Decoded token length is less than 8."); | |
} | |
//NLTM token type must be one of 1,2 or 3 | |
if (messageType < 1 || messageType > 3) { | |
throw new IdentityOAuth2Exception( | |
"Invalid NLTM message type:" + messageType + ". Should be one of 1,2 or 3."); | |
} | |
return messageType; | |
} |
To write a custom grant handler we can basically follow the doc [5]. That includes a sample [6] code too. When writing our handler we need to basically extend AbstractAuthorizationGrantHandler when writing our class.
Following is the grant handler code.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package org.wso2.carbon.identity.oauth.custom.ntlm; | |
import org.apache.commons.logging.Log; | |
import org.apache.commons.logging.LogFactory; | |
import org.wso2.carbon.identity.oauth2.IdentityOAuth2Exception; | |
import org.wso2.carbon.identity.oauth2.ResponseHeader; | |
import org.wso2.carbon.identity.oauth2.token.OAuthTokenReqMessageContext; | |
import org.wso2.carbon.identity.oauth2.token.handlers.grant.AbstractAuthorizationGrantHandler; | |
import waffle.util.Base64; | |
import waffle.windows.auth.IWindowsSecurityContext; | |
import waffle.windows.auth.impl.WindowsAuthProviderImpl; | |
public class NTLMAuthenticationGrantHandlerWithHandshake extends AbstractAuthorizationGrantHandler { | |
private static Log log = LogFactory.getLog(NTLMAuthenticationGrantHandlerWithHandshake.class); | |
private static WindowsAuthProviderImpl provider = new WindowsAuthProviderImpl(); | |
private String securityPackage = "Negotiate"; | |
public int getNLTMMessageType(byte[] decodedNLTMMessage) throws IdentityOAuth2Exception { | |
int messageType; | |
if (decodedNLTMMessage.length > 8) { | |
messageType = decodedNLTMMessage[8]; | |
} else { | |
throw new IdentityOAuth2Exception( | |
"Cannot extract message type from NLTM Token. Decoded token length is less than 8."); | |
} | |
//NLTM token type must be one of 1,2 or 3 | |
if (messageType < 1 || messageType > 3) { | |
throw new IdentityOAuth2Exception( | |
"Invalid NLTM message type:" + messageType + ". Should be one of 1,2 or 3."); | |
} | |
return messageType; | |
} | |
@Override | |
public boolean validateGrant(OAuthTokenReqMessageContext tokReqMsgCtx) throws IdentityOAuth2Exception { | |
boolean validGrant = super.validateGrant(tokReqMsgCtx); | |
if (!validGrant) { | |
return false; | |
} | |
String token = tokReqMsgCtx.getOauth2AccessTokenReqDTO().getWindowsToken(); | |
IWindowsSecurityContext serverContext = null; | |
if (token != null) { | |
byte[] bytesToken = Base64.decode(token); | |
int tokenType = getNLTMMessageType(bytesToken); | |
if (log.isDebugEnabled()) { | |
log.debug("Received NTLM token Type " + tokenType + ":" + token); | |
} | |
if (tokenType == 1) { | |
serverContext = provider.acceptSecurityToken("server-connection", bytesToken, securityPackage); | |
String type2Token = Base64.encode(serverContext.getToken()); | |
if (log.isDebugEnabled()) { | |
log.debug("Sent NTLM token Type 2:" + type2Token); | |
} | |
ResponseHeader[] responseHeaders = new ResponseHeader[1]; | |
responseHeaders[0] = new ResponseHeader(); | |
responseHeaders[0].setKey("WWW-Authenticate"); | |
responseHeaders[0].setValue("NTLM " + type2Token); | |
tokReqMsgCtx.addProperty("RESPONSE_HEADERS_PROPERTY", responseHeaders); | |
return false; | |
} else if (tokenType == 3) { | |
serverContext = provider.acceptSecurityToken("server-connection", bytesToken, securityPackage); | |
String resourceOwnerUserNameWithDomain = serverContext.getIdentity().getFqn(); | |
String resourceOwnerUserName = resourceOwnerUserNameWithDomain.split("\\\\")[1]; | |
tokReqMsgCtx.setAuthorizedUser(resourceOwnerUserName); | |
return true; | |
} else { | |
log.error("Unknown NTLM token, Type " + tokenType + ":" + token); | |
return false; | |
} | |
} else { | |
if (log.isDebugEnabled()) { | |
log.debug("NTLM token is null"); | |
} | |
throw new IdentityOAuth2Exception("NTLM token is null"); | |
} | |
} | |
} |
Once you completed writing the handler code, you can do the following configuration to enable the new custom grant type.
There is a section called "SupportedGrantTypes" in identity.xml in
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<SupportedGrantType> | |
<GrantTypeName>iwa:ntlmhandshake</GrantTypeName> | |
<GrantTypeHandlerImplClass>org.wso2.carbon.identity.oauth2.token.handlers.grant.iwa.ntlm.NTLMAuthenticationGrantHandler</GrantTypeHandlerImplClass> | |
</SupportedGrantType> |
After setting this you can start the server. You can use the provided sample client at [7] to test the new NTLM grant handler. You can follow the Readme.txt file which is added there.
[1] NTLM Authentication Basics : http://malinthaprasan.blogspot.com/2015/10/ntlm-authentication-basics.html
[2] Setting up an Active Directory Domain: http://roshanwijesena.blogspot.com/2015/05/ntlm-grant-type-support-with-wso2-api.html
[3] Java Native Access (JNA) Library: https://github.com/java-native-access/jna
[4] Waffle : https://github.com/dblock/waffle
[5] Writing a custom grant type : https://docs.wso2.com/display/IS500/Writing+a+Custom+OAuth+2.0+Grant+Type
[6] Custom grant type sample code : https://svn.wso2.org/repos/wso2/people/asela/oauth/custom-grant/
[7] Sample NTLM grant handler with handshake support and a sample client code: https://drive.google.com/open?id=0B1jOwGWdNiSNMEw3UFJ1R3Z4WFU
No comments:
Post a Comment