We have MobileFirst adapter with a wl_unprotected security test to use it from backend process.
We applied following solution to protect it calling through normal URL
Security Team Restricted URL to be invoked from Outside client application.
Is there any better solution which could be applied to secure this adapter?
There is a very good article in the IBM MobileFirst Platform Developers Center Blog about how to do just that. Protecting adapter procedures for backend access https://developer.ibm.com/mobilefirstplatform/2015/02/04/protect-adapter-backend/
Please go to the article for more details, but here is a summary of the article.
You could use Basic HTTP Authentication to protect that adapter. Update your authenticationConfig.xml file with the securityTest, realm and loginModule as shown below:
authenticationConfig.xml
<securityTests>
<!-- your other security tests -->
<customSecurityTest name="BackendAccessSecurity">
<test realm="BackendAccessRealm" isInternalUserID="true"/>
</customSecurityTest>
</securityTests>
<realms>
<!-- your other realms -->
<realm name="BackendAccessRealm" loginModule="BackendAccessLogin">
<className>com.worklight.core.auth.ext.BasicAuthenticator</className>
<parameter name="basic-realm-name" value="Private"/>
</realm>
</realms>
<loginModules>
<!-- your other login modules -->
<loginModule name="BackendAccessLogin">
<className>com.sample.auth.ConfiguredIdentityLoginModule</className>
<parameter name="username-property" value="backend.username"/>
<parameter name="password-property" value="backend.password"/>
</loginModule>
</loginModules>
worklight.properties
##
# Backend access credentials
##
backend.username=user
backend.password=password
ConfiguredIdentityLoginModule.java
#Override
public void init(Map<String, String> options) throws MissingConfigurationOptionException {
String usernameProperty = options.remove(USERNAME_PROPERTY_CONF);
if (usernameProperty == null) throw new MissingConfigurationOptionException(USERNAME_PROPERTY_CONF);
String passwordProperty = options.remove(PASSWORD_PROPERTY_CONF);
if (passwordProperty == null) throw new MissingConfigurationOptionException(PASSWORD_PROPERTY_CONF);
super.init(options);
WorklightConfiguration conf = WorklightConfiguration.getInstance();
configuredUsername = conf.getStringProperty(usernameProperty);
configuredPassword = conf.getStringProperty(passwordProperty);
if (configuredUsername == null || configuredUsername.length() == 0) {
throw new IllegalStateException("ConfiguredIdentityLoginModule cannot resolve property " + usernameProperty + ". Please check project configuration properties.");
}
if (configuredPassword == null || configuredPassword.length() == 0) {
throw new IllegalStateException("ConfiguredIdentityLoginModule cannot resolve property " + usernameProperty + ". Please check project configuration properties.");
}
}
#Override
public boolean login(Map<String, Object> authenticationData) {
populateCache(authenticationData);
return configuredUsername.equals(username) && configuredPassword.equals(password);
}
Finally, protect your adapter with the BackendAccessSecurity.
Related
I am attempting to get a modified sample project working with the HeaderLoginModule and HeaderAuthenticator to protect an adapter and then call an adapter function from the sample project by setting the headers and using the WLResourceRequest JavaScript API. I believe that based on my configuration of the loginModule, where I am setting a user-name-header value, and setting this in the header of the WLResourceRequest and then calling send(), that this should provide the user object and then the adapter should be accessible. For some reason though I still get 500 and the log shows 401/unauthorized.
Here are the steps I used to set up this sample:
1) git clone the Cordova sample project at https://github.com/MobileFirst-Platform-Developer-Center/Cordova
2) Added the following sections to authenticationConfig.xml (within the appropriate sections)
<loginModule name="HeaderLoginModule" audit="true">
<className>com.worklight.core.auth.ext.HeaderLoginModule</className>
<parameter name="user-name-header" value="plentyid"/>
<parameter name="display-name-header" value="customername"/>
</loginModule>
<realm name="MyRealm" loginModule="HeaderLoginModule">
<className>com.worklight.core.auth.ext.HeaderAuthenticator</className>
</realm>
<mobileSecurityTest name="MyMobileSecurityTest">
<testUser realm="MyRealm" />
<testDeviceId provisioningType="none" />
</mobileSecurityTest>
3) Secured the adapter with the security test by changing this line in the adapter XML file
<procedure name="getFeed" securityTest="MyMobileSecurityTest"/>
4) Changed the getRSSFeed function as follows
getRSSFeed: function(){
var resourceRequest = new WLResourceRequest(
"/adapters/RSSAdapter/getFeed",
WLResourceRequest.GET);
resourceRequest.addHeader("plentyid","1234");
resourceRequest.addHeader("customername","John Smith");
resourceRequest.setHeader("plentyid","1234");
resourceRequest.setHeader("customername","John Smith");
WL.Logger.info(resourceRequest.getHeaders());
resourceRequest.send().then(app.getRSSFeedSuccess,app.getRSSFeedError);
}
** I will say on the above I could not tell whether to addHeader or setHeader from the documentation. I tried both separately, then both together. They seem to be set from looking at the call to getHeaders().
Thanks for any help with figuring out why this is still 401/Unauthorized when clicking the Adapter button in the app after I mfp push both the RSSAdapter project (MFP) and the Cordova project (app).
Im following and using the sample code from Custom Authenticator and Login Module and UserAdapter from Java SQL Adapter.
I want to get the user list after authenticated.
My configuring the authenticationConfig.xml file
<realms>
<realm loginModule="CustomLoginModule" name="CustomAuthenticatorRealm">
<className>com.mypackage.MyCustomAuthenticator</className>
</realm>
</realms>
<loginModules>
<loginModule name="CustomLoginModule">
<className>com.mypackage.MyCustomLoginModule</className>
</loginModule>
</loginModules>
My configuring the Java adapter, UserAdapterResource.java file
#GET
#Produces("application/json")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response getAllUsers() throws SQLException{
JSONArray results = new JSONArray();
Connection con = ds.getConnection();
PreparedStatement getAllUsers = con.prepareStatement("SELECT * FROM users");
ResultSet data = getAllUsers.executeQuery();
while(data.next()){
JSONObject item = new JSONObject();
item.put("userId", data.getString("userId"));
item.put("firstName", data.getString("firstName"));
item.put("lastName", data.getString("lastName"));
item.put("password", data.getString("password"));
results.add(item);
}
getAllUsers.close();
con.close();
return Response.ok(results).build();
}
But when I invoke the procedure above on client-side, it still return a response without authentication require, while it have to show a login module
From your code you only have a challenge handler for the CustomAuthenticatorRealm realm. Why not updated your adapter and protect it with that same realm instead of using myRealm.
Updated UserAdapterResource.java skeleton
#Path("/")
public class UserAdapterResource {
// ...
#POST
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response createUser(#FormParam("userId") String userId,
#FormParam("firstName") String firstName,
#FormParam("lastName") String lastName,
#FormParam("password") String password)
throws SQLException{
// ...
}
#GET
#Produces("application/json")
#Path("/{userId}")
public Response getUser(#PathParam("userId") String userId) throws SQLException{
// ...
}
#GET
#Produces("application/json")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response getAllUsers() throws SQLException{
// ...
}
// it's a good practice to protect this operation
#PUT
#Path("/{userId}")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response updateUser(#PathParam("userId") String userId,
#FormParam("firstName") String firstName,
#FormParam("lastName") String lastName,
#FormParam("password") String password)
throws SQLException{
// ...
}
// it's a good practice to protect this operation
#DELETE
#Path("/{userId}")
#OAuthSecurity(scope="CustomAuthenticatorRealm")
public Response deleteUser(#PathParam("userId") String userId) throws SQLException {
// ...
}
}
With these changes, when the application launches it will show the login form to authenticate before showing the list of users.
UPDATE:
The Java Adapter protection is using OAuth and so the MobileFirst server issues a token for authentication. This token has a lifespan with an expiration. Logging out of a realm doesn't affect the token.
One way to implement this based on your needs is to decrease the TTL (time to live) of your token to something like 10 or 15 seconds (or whatever you want). You can do this by setting the expirationInSeconds attribute in your login module inside authenticationConfig.xml.
authenticationConfig.xml
<!-- token will expire 10 seconds after being issued -->
<loginModule name="CustomLoginModule" expirationInSeconds="10">
<className>com.mypackage.MyCustomLoginModule</className>
</loginModule>
If 10 seconds have passed since the app connected to the server via adapter invocation or any other method then the user will need to reauthenticate.
We are trying to develop session management on IBMWorklight server. Following are our needs from session management system-
Session should be created once every user logs in using his credentials (username/password).
This session should be able to store session data of that particular user i.e. say session should save his credentials so that he need not to pass those again for accessing other services.
Session timeout should happen after certain time.
Our progress
Created a realm in authenticationConfig:
<realm name="SimpleAuthRealm" loginModule="SimpleAuthLoginModule">
<className>com.worklight.integration.auth.AdapterAuthenticator</className>
<parameter name="login-function" value="SimpleAuthAdapter.onAuthRequired" />
<parameter name="logout-function" value="SimpleAuthAdapter.onLogout" />
</realm>
Created Login module in authenticationConfig:
<loginModule name="SimpleAuthLoginModule">
<className>com.worklight.core.auth.ext.NonValidatingLoginModule</className>
</loginModule>
Created security test:
<customSecurityTest name="SimpleAuthAdapterTest">
<test realm="SimpleAuthRealm" isInternalUserID="true"/>
</customSecurityTest>
We have created a adapter with two procedure in it. Following is adapter.xml file
<procedure name="requestForData" securityTest="SimpleAuthAdapterTest" />
<procedure name="requestForOtherData" securityTest="SimpleAuthAdapterTest" />
Following is adapter implementation file:
function onAuthRequired(headers, errorMessage) {
WL.Logger.info("onAuthRequired(headers, errorMessage)----> START");
WL.Logger.info("headers: " + JSON.stringify(headers));
WL.Logger.info("errorMessage: " + errorMessage);
errorMessage = errorMessage ? errorMessage : null;
WL.Logger.info("onAuthRequired(headers, errorMessage)----> STOP");
return {
authRequired : true,
errorMessage : errorMessage
};
}
function submitAuthentication(username, password) {
WL.Logger.info("submitAuthentication(username, password)----> START");
WL.Logger.info("username: " + username);
WL.Logger.info("password: " + password);
if (username === "worklight" && password === "worklight") {
WL.Logger.info("Login successfull");
var userIdentity = {
userId : username,
displayName : username,
};
WL.Server.setActiveUser("SimpleAuthRealm", userIdentity);
var response = {
authRequired : false,
errorMessage : ""
};
WL.Logger.info("submitAuthentication(username, password)----> STOP");
WL.Logger.info("response: " + JSON.stringify(response));
return response;
}
var response = {
authRequired : true,
errorMessage : "Invalid login credentials"
};
WL.Logger.info("submitAuthentication(username, password)----> STOP");
WL.Logger.info("response: " + JSON.stringify(response));
return response;
}
function onLogout() {
WL.Logger.info("onLogout()---->START");
WL.Server.setActiveUser("SimpleAuthRealm", null);
//WL.Client.logout('SimpleAuthRealm');
WL.Logger.info("onLogout()---->STOP");
}
We have created dummy UI which includes login page and home page. Login button click will call submitAuthentication() service and migrates to home page. Home page consist of two buttons one to call requestForData() service and other for requestForOtherData() service.
Issues we are facing:
This flow demands first to call protected service e.g. requestForData and in response worklight server will throw challenge which we will clear by providing user's credential. We require other way round, we want to provide the user's credentials and start the session so that all the services protected by that realm(security test) should be accessible.
Once we clear challenge for first service we are able to call other service without providing user credentials, but while calling next service we are not passing any identification of calling client, this makes us believe that the session which is established in first service call challenge is for all/ any the user not not user specific. We need very very user specific session.
Please comment on if this is a good idea to maintain session on worklight middleware server as we are working on banking mobile application. Please suggest solutions on above...
For #1, consider setting a security test on all the app environments (in the application descriptor) and Calling WL.Client.connect when the app starts (you should be doing this anyway) This will trigger authentication when the app initially contacts the Worklight server. Once this is complete, you will be able to access adapters protected by security tests in the same realm without additional challenge.
For #2 when you establish the connection to the Worklight server, you create a session that the server tracks as yours, so even though you are not providing credentials again, the Worklight server knows which authenticated user makes each adapter call.
I'm trying to use and understand the use of LTPA security in worklight and the propagation of the LTPA cookie.
I'm able to authenticate agains the WAS and using a sniffer I can see that worklight returns me the LtpaToken2 cookie but when I invoke the HTTP Adapter, that invokes a service in other WAS in the same machine as the Worklight server, that adapter does not propagate the cookies.
I think I have set the right configuration. (At the end)
Is it possible to configure worklight server for automatically propagate the LTPA token from the app to the adapters and from the adapters to the final service?
If it is not possible to do it automatically how can I retrieve the Ltpa cookie inside the adapter code for add it to the headers parameter of the WL.Server.invokeHTTP() method.
This is my security configuration:
For it work I have had to add the login.html by hand in the customized war generated in worklight studio.
Application-descriptor:
<ipad bundleId="xxxx" securityTest="BPMApp-strong-mobile-securityTest" version="1.0">
Adapter-descriptor:
<procedure connectAs="endUser" name="getRest" securityTest="BPMAdapter-securityTest"/>
Security configuration:
<realm loginModule="WASLTPAModule" name="BPMAuthRealm">
<className>com.worklight.core.auth.ext.WebSphereFormBasedAuthenticator</className>
<parameter name="login-page" value="/login.html"/>
<parameter name="error-page" value="/login.html"/>
<parameter name="cookie-name" value="LtpaToken2"/>
</realm>
<loginModule name="WASLTPAModule" canBeResourceLogin="true" isIdentityAssociationKey="false">
<className>com.worklight.core.auth.ext.WebSphereLoginModule</className>
</loginModule>
<mobileSecurityTest name="BPMApp-strong-mobile-securityTest">
<testUser realm="BPMAuthRealm"/>
<testDeviceId provisioningType="none"/>
</mobileSecurityTest>
<customSecurityTest name="BPMAdapter-securityTest">
<test isInternalUserID="true" realm="BPMAuthRealm" isInternalDeviceID="true"/>
</customSecurityTest>
Thank you.
I believe this is what you're looking for:
function getCurrentUser() {
path = '/snoop';
var attributes = WL.Server.getActiveUser().attributes;
var token = "LtpaToken=" + attributes.get('LtpaToken');
var input = {
method : 'get',
returnedContentType : 'html',
headers: {"Cookie": token},
path : path
};
return WL.Server.invokeHttp(input);
}
This code snipped is from 5.0.3, so I think the syntax may have changed for getting the token from the attributes object in newer versions.
You may need to change:
var token = "LtpaToken=" + attributes.get('LtpaToken');
to:
var token = "LtpaToken=" + attributes['LtpaToken'];
But this is the idea. The adapter is not sending the cookie upon subsequent requests, however the cookie is available to the adapter through the user's 'attributes' object. It's only a matter of getting the cookie and adding it to the header upon each adapter invocation.
I create a Dynamic web project with name 'testUpdate' (and of course I don't forget to change the dynamic web module version to 2.5 and in configuration I choose Axis 2 web service
I add to my Dynamic web project these two classes :
SimpleService .java and PWCBHandler.java
I right click on SimpleService.java -> New ->Other -> Web Service to create my web service
I don't forget to copy all the jar files from rampart distribution to testUpdate/ WebContent/WEB_INF/lib and all .mar modules into testUpdate/ WebContent/WEB_INF/modules
I change services.xml file so it looks like
<service name="SimpleService" >
<module ref="rampart" />
<Description>
</Description>
<messageReceivers>
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-only" class="org.apache.axis2.rpc.receivers.RPCInOnlyMessageReceiver" />
<messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out" class="org.apache.axis2.rpc.receivers.RPCMessageReceiver"/>
</messageReceivers>
<parameter name="ServiceClass" locked="false">com.gismo.SimpleService</parameter>
<parameter name="InflowSecurity">
<action>
<items>UsernameToken</items>
<passwordCallbackClass>com.gismo.PWCBHandler</passwordCallbackClass>
</action>
</parameter>
</service>
I right click on testUpdate -> RUN AS _> Run on Server (and my web service is deployed successfully)
File -> New -> Other -> Web Service Client
and in Service Definition I paste the url of the wsdl file of SimpleService
( http://localhost:9091/testUpdate/services/SimpleService?wsdl)
I add testcl.java class to my web-service client. Here is the code
public class testCL {
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.out.println(args.length);
System.out
.println("Usage: $java Client endpoint_address client_repo_path");
}
ConfigurationContext ctx = ConfigurationContextFactory
.createConfigurationContextFromFileSystem(args[1], args[1]
+ "/conf/axis2.xml");
ServiceClient client = new ServiceClient(ctx, null);
Options options = new Options();
options.setAction("urn:echo");
options.setTo(new EndpointReference(args[0]));
client.setOptions(options);
OMElement response = client.sendReceive(getPayload("Hello world"));
System.out.println(response);
}
private static OMElement getPayload(String value) {
OMFactory factory = OMAbstractFactory.getOMFactory();
OMNamespace ns = factory.createOMNamespace("com.gismo/xsd", "ns1");
OMElement elem = factory.createOMElement("echo", ns);
OMElement childElem = factory.createOMElement("param0", null);
childElem.setText(value);
elem.addChild(childElem);
return elem;
}
}
I don't forget to change webSercice_client/WebContent/axis2-web/conf/axis2.xml and add
<module ref="rampart"/>
<parameter name="OutflowSecurity">
<action>
<items>UsernameToken</items>
<user>bob</user>
<passwordCallbackClass>com.gismo.PWCBHandler</passwordCallbackClass>
</action>
</parameter>
But when I run testCl as Java Application it gives me an exception
Usage: $java Client endpoint_address client_repo_path
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 1
at com.gismo.testcl.main(testcl.java:24)
My psychic debugging powers tell me that you ran it without providing two command-line arguments. You can see the error message "Usage: $java Client endpoint_address client_repo_path" is present in your program output, meaning that you didn't supply two command-line arguments, so args[1] may not be valid. Your program doesn't exit after checking the number of command-line arguments, so it tries to access args[1] after complaining that the program was run incorrectly.
if (args.length != 2) {
System.out.println(args.length);
System.out
.println("Usage: $java Client endpoint_address client_repo_path");
}
ConfigurationContext ctx = ConfigurationContextFactory
.createConfigurationContextFromFileSystem(args[1], args[1]
+ "/conf/axis2.xml");