i have written a web service which takes input parameters (json) from other application. i am just authenticating those values and returning the "http status codes and response" (using cfheader). For that I am trying the "RESTClient" in firefox extension. My problem is - It is running the "GET" method successfully but not the POST method.I guess the request is not even going to the server in POST method. See the screen shot below of both the request:
1. GET request
POST request
This is my CFC:
<cfcomponent rest="true" restpath="/AimsWeb"> <!--- REST Service--->
<cffunction name="AuthenticateUser" access="remote" httpmethod="POST" returntype="void">
<!---- Defining Arguments--->
<cfargument name="Username" type="string" required="Yes">
<cfargument name="Password" type="string" required="Yes">
<cfargument name="CustomerID" type="numeric" required="Yes">
<!---- Setting the Form Values (which we will get from AW+) and setting it to arguments passed--->
<cfset Form.CustomerID = arguments.CustomerID>
<cfset Form.Username = arguments.Username>
<cfset Form.Password = arguments.Password>
<!--- Take input json, parse it and set in in a variable --->
<cfscript>
record=deserializeJSON(
'{
"customerId": #Form.CustomerID#,
"userName": "#Form.userName#",
"password": "#Form.Password#"
}'
);
this.customerid = record.customerId;
this.userName = record.userName;
this.password = record.password;
</cfscript>
<cfquery name="AllUsers" datasource="#Application.GomDatasource#">
SELECT u.UserTypeID, u.UserID, u.CustomerID, u.UserName, u.Password, u.active, u.locked
FROM tblUsers u
WHERE u.CustomerID = <cfqueryparam cfsqltype="cf_sql_integer" value="#this.customerid#">
AND u.username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#this.username#">
AND u.password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#this.password#">
</cfquery>
<!--- This is to check whether provided parameters are valid by checking the same in the database--->
<cfset local.StatusStruct = StructNew()>
<cfif AllUsers.RecordCount AND (AllUsers.Active EQ 0 OR AllUsers.locked EQ 1)>
<cfheader statuscode="401" statustext="User Account is locked">
<cfelse>
<cfif this.customerid EQ "" OR this.username EQ "" OR this.password EQ "">
<cfheader statuscode="400" statustext="Insufficient Input. Please provide Customer ID, UserName and Password">
<cfelseif AllUsers.RecordCount AND this.CustomerId EQ AllUsers.CustomerID AND this.username EQ AllUsers.UserName AND this.password EQ AllUsers.Password>
<cfheader statuscode="200" statustext="Success">
<cfelseif AllUsers.CustomerID NEQ this.CustomerID>
<cfheader statuscode="400" statustext="Customer Id doesn't exist">
<cfelseif AllUsers.UserName NEQ this.UserName>
<cfheader statuscode="400" statustext="User not found">
<cfelseif AllUsers.Password NEQ this.password>
<cfheader statuscode="400" statustext="Invalid Password">
</cfif>
</cfif>
</cffunction>
</cfcomponent>
FYI a GET request has no request entity body because it is a GET request and the web server will generally ignore any body included in the request. I am surprised that ColdFusion provides a response since I don't see a GET method in your CFC.
The POST method is probably failing because the CFC is expecting a MIME type of application/form-url-encoded (your CFARGUMENTs) and you are posting application/json
Try breaking up into 2 functions, one which accepts application/json, one application/form-url-encoded
use the 'consumes' attribute in the CFFUNCTION to distinguish. They can both call a common function after this.
Related
I have created a REST service in ColdFusion with a POST. It takes input json from other application, checks them in my DB and returns the http status code. Simple.
Calling it from RESTClient-FireFox Plus from POSTMAN-Chrome, my application says it's success/failure depending upon authentication, but RESTClient hangs continuing to show the progress bar and the button Abort.
The GET works perfectly fine though. Can anyone please help?
GET - Works fine.
POST - Does not work.
Here is my webservice(cfc):
<cfcomponent rest="true" restpath="/AimsWeb"> <!--- REST Service--->
<cffunction name="AuthenticateUser" access="remote" httpmethod="POST" returntype="void">
<!---- Defining Arguments--->
<cfargument name="Username" type="string" required="Yes">
<cfargument name="Password" type="string" required="Yes">
<cfargument name="CustomerID" type="numeric" required="Yes">
<!---- Setting the Form Values (which we will get from AW+) and setting it to arguments passed--->
<cfset Form.CustomerID = arguments.CustomerID>
<cfset Form.Username = arguments.Username>
<cfset Form.Password = arguments.Password>
<!--- Take input json, parse it and set in in a variable --->
<cfscript>
record=deserializeJSON(
'{
"customerId": "#Form.CustomerID#",
"userName": "#Form.userName#",
"password": "#Form.Password#"
}'
);
this.customerid = record.customerId;
this.userName = record.userName;
this.password = record.password;
</cfscript>
<cfdump var="#record#">
<cfquery name="AllUsers" datasource="#Application.GomDatasource#">
SELECT u.UserTypeID, u.UserID, u.CustomerID, u.UserName, u.Password, u.active, u.locked
FROM tblUsers u
WHERE
u.username = 'swapnil'
</cfquery>
<!--- This is to check whether provided parameters are valid by checking the same in the database --->
<cfset local.StatusStruct = StructNew()>
<cflog text="in struct" type="information">
<cfif AllUsers.RecordCount AND (AllUsers.Active EQ 0 OR AllUsers.locked EQ 1)>
<cfheader statuscode="401" statustext="User Account is locked">
<cflog text="account locked" type="information">
<cfelse>
<cfif this.customerid EQ "" OR this.username EQ "" OR this.password EQ "">
<cfheader statuscode="400" statustext="Insufficient Input. Please provide Customer ID, UserName and Password">
<cflog text="insufficient input" type="information">
<cfelseif AllUsers.RecordCount AND (this.CustomerId EQ AllUsers.CustomerID AND this.username EQ AllUsers.UserName AND this.password EQ AllUsers.Password)>
<cfheader statuscode="200" statustext="Success">
<cflog text="success" type="information">
<cfelseif AllUsers.CustomerID NEQ this.CustomerID>
<cfheader statuscode="400" statustext="Customer Id doesn't exist">
<cflog text="customer id not exist" type="information">
<cfelseif AllUsers.UserName NEQ this.UserName>
<cfheader statuscode="400" statustext="User not found">
<cflog text="user name not found" type="information">
<cfoutput>
user name not found
</cfoutput>
<cfelseif AllUsers.Password NEQ this.password>
<cfheader statuscode="400" statustext="Invalid Password">
<cflog text="invalid password" type="information">
</cfif>
</cfif>
</cffunction>
</cfcomponent>
Here is the JSON I am passing as a Body:
{
"customerId": 100,
"userName": "test",
"password": "xxxxx"
}
I have written a web-service in ColdFusion which returns message (success/failure) by checking the input values in the database.
To run the cfc, I am directly providing the arguments in the URL, like this:
http://localhost/AimsWeb/Authenticate2.cfc?method=AuthenticateUser&returnformat=json&CustomerID=1&username=xxx&password=xxxx
But when I run this page, it ends with an error like below:
This is my CFC:
<cfcomponent rest="true" restpath="/AimsWeb"> <!--- REST Service--->
<cffunction name="AuthenticateUser" access="remote" httpmethod="POST" returnFormat="JSON" returntype="json">
<!---- Defining Arguments--->
<cfargument name="Username" type="string" required="Yes">
<cfargument name="Password" type="string" required="Yes">
<cfargument name="CustomerID" type="string" required="Yes">
<!---- Setting the Form Values (which we will get from AW+) and setting it to arguments passed--->
<cfset Form.CustomerID = arguments.CustomerID>
<cfset Form.Username = arguments.Username>
<cfset Form.Password = Hash(arguments.Password)>
<cfif StructKeyExists (form, 'CustomerID') and StructKeyExists(form, 'UserName') and StructKeyExists (form, 'password')>
<cfquery name="AllUsers" datasource="#Application.GomDatasource#">
SELECT u.UserTypeID, u.UserID, u.CustomerID, u.UserName, u.Password
FROM tblUsers u
WHERE u.CustomerID = <cfqueryparam cfsqltype="cf_sql_integer" value="#Form.CustomerID#">
</cfquery>
<!--- This is to check whether provided parameters are valid by checking the same in the database--->
<cfset local.StatusStruct = StructNew()>
<cfif form.customerid EQ "" OR form.username EQ "" OR form.password EQ "">
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = "Insufficient Input.">
<cfelseif AllUsers.RecordCount AND form.CustomerId EQ AllUsers.CustomerID AND form.username EQ AllUsers.UserName AND form.password EQ AllUsers.Password>
<cfset local.StatusStruct['errorCode'] = 200>
<cfset local.StatusStruct['errorMessage'] = "Success">
<cfelseif AllUsers.CustomerID NEQ form.CustomerID>
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = "Customer Id doesn't exist">
<cfelseif AllUsers.UserName NEQ form.UserName>
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = "User not found">
<cfelseif AllUsers.Password NEQ form.password>
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = "Invalid Password">
</cfif>
<!--- Returning the status in JSON form--->
</cfif>
<cfreturn local.StatusStruct>
</cffunction>
</cfcomponent>
Can anyone help me please?
it worked. The returntype=json was not valid. I removed that line and it worked.
<cffunction name="AuthenticateUser" access="remote" httpmethod="GET" returnFormat="JSON">
Thanks ALL for your help.
I have a web service (cfc) which will catch/receive JSON data from an external application that would be posting information.
The input request will be in JSON similar to below format:
So I will be receiving CustomerId, UserName and Password and I just need to validate this fields in my database and return the success/failure message.
My question is - How to receive the json data in my cfc? I have written Form scope as I believe it would solve my purpose, but I am not sure.
Also how to parse that json values?
Below is my CFC:
<cfcomponent rest="true" restpath="/AimsWeb"> <!--- REST Service--->
<cffunction name="AuthenticateUser" access="remote" httpmethod="GET" returntype="any">
<!---- Defining Arguments--->
<cfargument name="Username" type="string" required="Yes">
<cfargument name="Password" type="string" required="Yes">
<cfargument name="CustomerID" type="string" required="Yes">
<!---- Setting the Form Values (which we will get from AW+) and setting it to arguments passed--->
<cfset Form.CustomerID = arguments.CustomerID>
<cfset Form.Username = arguments.Username>
<cfset Form.Password = arguments.Password>
<cfif StructKeyExists (form, 'CustomerID') and StructKeyExists(form, 'UserName') and StructKeyExists (form, 'password')>
<cfquery name="AllUsers" datasource="#Application.GomDatasource#">
SELECT u.UserTypeID, u.UserID, u.CustomerID, u.UserName, u.Password, u.active, u.locked
FROM tblUsers u
WHERE u.username = 'vasu'
AND u.CustomerID = <cfqueryparam cfsqltype="cf_sql_varchar" value="#Form.CustomerId#">
<!--- OR u.username = <cfqueryparam cfsqltype="cf_sql_varchar" value="#Form.userName#"> --->
<!--- OR u.password = <cfqueryparam cfsqltype="cf_sql_varchar" value="#Form.password#"> --->
</cfquery>
<!--- This is to check whether provided parameters are valid by checking the same in the database--->
<cfset local.StatusStruct = StructNew()>
<!--- <cfdump var="#AllUsers#"> --->
<cfif AllUsers.RecordCount AND (AllUsers.Active EQ 0 OR AllUsers.locked EQ 1)>
<cfset local.StatusStruct['errorCode'] = 401>
<cfset local.StatusStruct['errorMessage'] = " User Account is locked">
<cfelse>
<cfif form.customerid EQ "" OR form.username EQ "" OR form.password EQ "">
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = " Insufficient Input. Please provide Customer ID, UserName and Password">
<cfelseif AllUsers.RecordCount AND form.CustomerId EQ AllUsers.CustomerID AND form.username EQ AllUsers.UserName AND form.password EQ AllUsers.Password>
<cfset local.StatusStruct['errorCode'] = 200>
<cfset local.StatusStruct['errorMessage'] = " Success">
<cfelseif AllUsers.CustomerID NEQ form.CustomerID>
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = " Customer Id doesn't exist">
<cfelseif AllUsers.UserName NEQ form.UserName>
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = " User not found">
<cfelseif AllUsers.Password NEQ form.password>
<cfset local.StatusStruct['errorCode'] = 400>
<cfset local.StatusStruct['errorMessage'] = " Invalid Password">
</cfif>
</cfif>
</cfif>
<cfreturn local.StatusStruct> <!--- Returning the status in JSON form--->
</cffunction>
</cfcomponent>
I am trying to convert some pages from my app to use cfc's, and one page uses a stored procedure to retrieve a couple sets of data.
Now when I access the results, they act just like a if I used a <cfquery> tag, and all of the functionality that gives. So now I am trying to use this same stored procedure in a cfc that I am building, and I would like to be able access the results in the same manner, and there in lies my problem. I'm not sure how to return multiple queries from the function, without creating an array, which I have started. By the way, the function is incomplete. I was just trying to get something to work. In the below setup I get an array of query objects, but I feel there is a better way to do it.
Here is the <cffuntion>:
<cffunction name="getProfileData"
access="public"
output="false"
returntype="string">
<cfargument name="cusip" type="string" required="true">
<cfargument name="report_date" type="date" required="true">
<cfset var errorMessage = "everything is good">
<cftry>
<cfstoredproc datasource="#dsn#" procedure="prc_asset_profile_retrieve">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" value="#cusip#" dbvarname="#cusip">
<cfprocparam type="in" cfsqltype="cf_sql_varchar" value="#report_date#" dbvarname="#reportDate">
<cfprocresult name="profile_head" resultset="1">
<cfprocresult name="attribution" resultset="2">
<cfprocresult name="characteristics" resultset="3">
<cfprocresult name="exposure" resultset="4">
<cfprocresult name="weights" resultset="5">
<cfprocresult name="holdings" resultset="6">
</cfstoredproc>
<cfset var profileArray = []>
<cfset #ArrayAppend(profileArray,profile_head)#>
<cfcatch type="any">
<cfset errorMessage = "something happened">
</cfcatch>
</cftry>
<cfreturn profileArray>
</cffunction>
When I output some test data, it matches up
<cfset count = fund_profile.getProfileData("#cusip#","#report_date#")>
<cfdump var="#count[1]#">
<cfoutput>
From cfc (##count[1].recordCount##): #count[1].recordCount#<br>
From stored proc (##profile_head.recordCount##): #profile_head.recordCount#
</cfoutput>
I get:
From cfc (#count[1].recordCount#): 1
From stored proc (#profile_head.recordCount#): 1
But the second way looks so much cleaner.
-----------------------------WORKING SOLUTION------------------------------
So after working with the answer from #leigh, I came up with this.
Here is the full cfc:
<cfcomponent displayname="Fund Profile" hint="This is the cfc that will do the processing of all fund profile information" output="false">
<cfproperty name = "result1"> <!--- PROFILE HEAD --->
<cfproperty name = "result2"> <!--- ATTRIBUTION --->
<cfproperty name = "result3"> <!--- CHARACTERISTICS --->
<cfproperty name = "result4"> <!--- EXPOSURE --->
<cfproperty name = "result5"> <!--- WEIGHTS --->
<cfproperty name = "result6"> <!--- HOLDINGS --->
<cffunction name="init"
displayname="init"
hint="This will initialize the object"
access="public"
output="false"
returnType="Any">
<cfargument name="dsn" type="string" required="true" />
<cfargument name="cusip" type="string" required="true" />
<cfargument name="report_date" type="date" required="true" />
<cfset variables.dsn = #arguments.dsn#>
<cfset variables.cusip = #arguments.cusip#>
<cfset variables.report_date = #arguments.report_date#>
<cfscript>
getProfiledata(cusip,report_date);
</cfscript>
<cfreturn this>
</cffunction>
<cffunction name="getProfileData"
access="private"
output="false"
returntype="void">
<cfargument name="cusip" type="string" required="true">
<cfargument name="report_date" type="date" required="true">
<cfstoredproc datasource="#dsn#" procedure="prc_asset_profile_retrieve">
<!--- STORED PROCEDURE HASN'T CHANGED. SEE ABOVE FOR CODE --->
</cfstoredproc>
<cfscript>
setProfilehead(profile_head);
setAttribution(attribution);
setCharacteristics(characteristics);
setExposure(exposure);
setWeights(weights);
setHoldings(holdings);
</cfscript>
<cfreturn>
</cffunction>
<!--- NOT GOING TO INCLUDE ALL SETTERS AND GETTERS, --->
<!--- BECAUSE THEY ARE ALL THE SAME OTHER THAN THE NAMES --->
<cffunction name="setProfileHead" access="private">
<cfargument name="ProfileHead">
<cfset variables.result1 = arguments.ProfileHead>
</cffunction>
<cffunction name="getProfileHead" access="public" returntype="query">
<cfreturn variables.result1>
</cffunction>
</cfcomponent>
Here is the code from the calling page:
<cfset fund_profile = CreateObject("component", "CFCs.fund_profile").init("#dsn#","#cusip#","#report_date#")>
<cfset profile_head = fund_profile.getProfileHead()>
Sorry for all the code, but I wanted to make the code available. So does anyone see any problems with what I came up with?
A function can only return a single value. If you wish to return multiple values, you will need to use some type of complex object (an array, structure, ...) If arrays are not intuitive enough, you could place the queries in a structure and return that instead. Then the calling page could access the queries by name, rather than index.
(Side note, be sure to properly var scope/localize all function variables.)
<cfset var data = {}>
...
<!--- store query results in structure --->
<cfset data.profile_head = profile_head>
<cfset data.attribution = attribution>
...
<cfset data.holdings = holdings>
<!--- return structure --->
<cfreturn data>
I would create other methods in the CFC that would each be responsible for returning a result from the stored proc. In the main method , call setters
setProfileHead(profilehead:profileHead)
<cffunction name=ProfileHead>
<cfarguments name=ProfileHead />
<cfset variables.profilehead = arguments.profilehead>
</cffunction>
Then...
<cffunction name=GetProfileHead>
<cfreturn variables.profileHead />
</cffuction>
Is there a recommended (and preferably free) way in ColdFusion to access a remote file that is protected by NTLM authentication? The cfhttp tag appears to only support Basic authentication.
This CFX Tag - CFX_HTTP5 - should do what you need. It does cost $50, but perhaps it's worth the cost? Seems like a small price to pay.
Here is some code I found in:
http://www.bpurcell.org/downloads/presentations/securing_cfapps_examples.zip
There are also examples for ldap, webservices, and more.. I'll paste 2 files here so you can have an idea, code looks like it should still work.
<cfapplication name="example2" sessionmanagement="Yes" loginStorage="Session">
<!-- Application.cfm -->
<!-- CFMX will check for authentication with each page request. -->
<cfset Request.myDomain="allaire">
<cfif isdefined("url.logout")>
<CFLOGOUT>
</cfif>
<cflogin>
<cfif not IsDefined("cflogin")>
<cfinclude template="loginform.cfm">
<cfabort>
<cfelse>
<!--Invoke NTSecurity CFC -->
<cfinvoke component = "NTSecurity" method = "authenticateAndGetGroups"
returnVariable = "userRoles" domain = "#Request.myDomain#"
userid = "#cflogin.name#" passwd = "#cflogin.password#">
<cfif userRoles NEQ "">
<cfloginuser name = "#cflogin.name#" password = "#cflogin.password#" roles="#stripSpacesfromList(userRoles)#">
<cfset session.displayroles=stripSpacesfromList(userRoles)><!--- for displaying roles only --->
<cfelse>
<cfset loginmessage="Invalid Login">
<cfinclude template="loginform.cfm">
<cfabort>
</cfif>
</cfif>
</cflogin>
<!-- strips leading & trailing spaces from the list of roles that was returned -->
<cffunction name="stripSpacesfromList">
<cfargument name="myList">
<cfset myArray=listtoarray(arguments.myList)>
<cfloop index="i" from="1" to="#arraylen(myArray)#" step="1">
<!--- <cfset myArray[i]=replace(trim(myArray[i]), " ", "_")>
out<br>--->
<cfset myArray[i]=trim(myArray[i])>
</cfloop>
<cfset newList=arrayToList(myArray)>
<cfreturn newList>
</cffunction>
This is the cfc that might be of interest to you:
<!---
This component implements methods for use for NT Authentication and Authorization.
$Log: NTSecurity.cfc,v $
Revision 1.1 2002/03/08 22:40:41 jking
Revision 1.2 2002/06/26 22:46 Brandon Purcell
component for authentication and authorization
--->
<cfcomponent name="NTSecurity" >
<!--- Authenticates the user and outputs true on success and false on failure. --->
<cffunction name="authenticateUser" access="REMOTE" output="no" static="yes" hint="Authenticates the user." returntype="boolean">
<cfargument name="userid" type="string" required="true" />
<cfargument name="passwd" type="string" required="true" />
<cfargument name="domain" type="string" required="true" />
<cftry>
<cfscript>
ntauth = createObject("java", "jrun.security.NTAuth");
ntauth.init(arguments.domain);
// authenticateUser throws an exception if it fails,
ntauth.authenticateUser(arguments.userid, arguments.passwd);
</cfscript>
<cfreturn true>
<cfcatch>
<cfreturn false>
</cfcatch>
</cftry>
</cffunction>
<!---
Authenticates the user and outputs true on success and false on failure.
--->
<cffunction access="remote" name="getUserGroups" output="false" returntype="string" hint="Gets user groups." static="yes">
<cfargument name="userid" type="string" required="true" />
<cfargument name="domain" type="string" required="true" />
<cftry>
<cfscript>
ntauth = createObject("java", "jrun.security.NTAuth");
ntauth.init(arguments.domain);
groups = ntauth.GetUserGroups(arguments.userid);
// note that groups is a java.util.list, which should be
// equiv to a CF array, but it's not right now???
groups = trim(groups.toString());
groups = mid(groups,2,len(groups)-2);
</cfscript>
<cfreturn groups>
<cfcatch>
<cflog text="Error in ntsecurity.cfc method getUserGroups - Error: #cfcatch.message#" type="Error" log="authentication" file="authentication" thread="yes" date="yes" time="yes" application="no">
<cfreturn "">
</cfcatch>
</cftry>
</cffunction>
<!---
This method combines the functionality of authenticateUser and getUserGroups.
--->
<cffunction access="remote" name="authenticateAndGetGroups" output="false" returntype="string" hint="Authenticates the user and gets user groups if it returns nothing the user is not authticated" static="yes">
<cfargument name="userid" type="string" required="true" />
<cfargument name="passwd" type="string" required="true" />
<cfargument name="domain" type="string" required="true" />
<cftry>
<cfscript>
ntauth = createObject("java", "jrun.security.NTAuth");
ntauth.init(arguments.domain);
// authenticateUser throws an exception if it fails,
// so we don't have anything specific here
ntauth.authenticateUser(arguments.userid, arguments.passwd);
groups = ntauth.GetUserGroups(arguments.userid);
// note that groups is a java.util.list, which should be
// equiv to a CF array, but it's not right now
groups = trim(groups.toString());
groups = mid(groups,2,len(groups)-2);
</cfscript>
<cfreturn groups>
<cfcatch>
<cfreturn "">
</cfcatch>
</cftry>
</cffunction>
</cfcomponent>
If the code from Brandon Purcell that uses the jrun.security.NTauth class doesn't work for you in cf9 (it didn't for me) the fix is to use the coldfusion.security.NTAuthentication class instead. Everything worked fine for me.
You could try following the guidance here: http://cfsilence.com/blog/client/index.cfm/2008/3/17/ColdFusionSharepoint-Integration--Part-1--Authenticating
Here is what it boils down to you doing:
edit the client-config.wsdd
Change
<transport
name="http"
pivot="java:org.apache.axis.transport.http.HTTPSender">
</transport>
to
<transport
name="http"
pivot="java:org.apache.axis.transport.http.CommonsHTTPSender">
</transport>
In my case I fixed this problem using 'NTLM Authorization Proxy Server'
http://www.tldp.org/HOWTO/Web-Browsing-Behind-ISA-Server-HOWTO-4.html
work fine for me :)