Using JOAuth library to post jobs in LinkedIn - java

This is in continuation of a question posted by me on LinkedIn Job Posting API implementation in Java. I'm new to this OAuth based authentication of posting Jobs and am also learning as well during this process. Please bear with me if my questions are very basic/naive.
Am trying to use JOAuth library for OAuth authentication and post jobs to LinkedIn. I'm using OAuth2 calls. I've the following questions with JOAuth library:
In the example shown at the JOAuth link, how do I get Request Token for LinkedIn? I don't find any statement for requesting Request Token. But I could see consumer.generateRequestAuthorizationUrl(ResponseType.CODE, redirectUri, null, (String[])null));
If I want to use oob based callback redirection, then what do I need to pass/set in redirectUri?
If everything is successful and if I've the Access Token, how do I finally submit/send my Job data XML at http://api.linkedin.com/v1/jobs?

You're confused. LinkedIn uses OAuth 1 protocol and not OAuth 2 protocols. Here's how you would do Oauth 1 authorization to/with LinkedIn.
If you're creating a Web application, under WEB-INF folder, create a oauth-config.xml file and have a configuration similar to this:
<?xml version="1.0" encoding="UTF-8"?>
<oauth-config>
<!-- LinkedIn OAuth Config -->
<oauth name="linkedIn" version="1">
<consumer key="API_KEY" secret="API_SECRET" />
<provider requestTokenUrl="https://api.linkedin.com/uas/oauth/requestToken" authorizationUrl="https://api.linkedin.com/uas/oauth/authorize" accessTokenUrl="https://api.linkedin.com/uas/oauth/accessToken" />
</oauth>
<service path="/authorize_ready" class="com.neurologic.example.LinkedInOAuthService" oauth="linkedIn">
<success path="/start.htm" />
</service>
</oauth-config>
LinkedIn uses OAuth version 1 (hence the version).
Under your WEB-INF\web.xml, add the following:
<servlet>
<description>An OAuth Servlet Controller</description>
<display-name>OAuthServlet</display-name>
<servlet-name>OAuthServlet</servlet-name>
<servlet-class>com.neurologic.oauth.servlet.OAuthServlet</servlet-class>
<init-param>
<param-name>config</param-name>
<param-value>/WEB-INF/oauth-config.xml</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>OAuthServlet</servlet-name>
<url-pattern>/oauth/*</url-pattern>
</servlet-mapping>
Now, we need to create a service that will receive the authorized token from Linked In.
package com.neurologic.example;
import javax.servlet.http.HttpServletRequest;
import net.oauth.signature.OAuthSignature;
import net.oauth.signature.impl.OAuthHmacSha1Signature;
import net.oauth.token.v1.AccessToken;
import net.oauth.token.v1.RequestToken;
import com.neurologic.oauth.service.impl.OAuth1Service;
/**
* #author Buhake Sindi
* #since 31 May 2011
*
*/
public class LinkedInOAuthService extends OAuth1Service {
public static final String LINKED_IN_REQUEST_TOKEN_SESSION = "LINKED_IN_REQUEST_TOKEN_SESSION";
public static final String LINKED_IN_ACCESS_TOKEN_SESSION = "LINKED_IN_ACCESS_TOKEN_SESSION";
/* (non-Javadoc)
* #see com.neurologic.oauth.service.impl.OAuth1Service#getOAuthSignature()
*/
#Override
protected OAuthSignature getOAuthSignature() {
// TODO Auto-generated method stub
return new OAuthHmacSha1Signature();
}
/* (non-Javadoc)
* #see com.neurologic.oauth.service.impl.OAuth1Service#getRealm()
*/
#Override
protected String getRealm() {
// TODO Auto-generated method stub
return null;
}
/* (non-Javadoc)
* #see com.neurologic.oauth.service.impl.OAuth1Service#getRequestToken(javax.servlet.http.HttpServletRequest)
*/
#Override
protected RequestToken getRequestToken(HttpServletRequest request) {
// TODO Auto-generated method stub
return (RequestToken) request.getSession().getAttribute(LINKED_IN_REQUEST_TOKEN_SESSION);
}
/* (non-Javadoc)
* #see com.neurologic.oauth.service.OAuthService#saveAccessToken(javax.servlet.http.HttpServletRequest, java.lang.Object)
*/
#Override
public void saveAccessToken(HttpServletRequest request, AccessToken accessToken) {
// TODO Auto-generated method stub
request.getSession().setAttribute(LINKED_IN_ACCESS_TOKEN_SESSION, accessToken);
}
}
Now, use the following example:
package com.neurologic.example;
import net.oauth.consumer.OAuth1Consumer;
import net.oauth.exception.OAuthException;
import net.oauth.provider.OAuth1ServiceProvider;
import net.oauth.signature.impl.OAuthHmacSha1Signature;
import net.oauth.token.v1.AccessToken;
import net.oauth.token.v1.AuthorizedToken;
import net.oauth.token.v1.RequestToken;
/**
* #author Buhake Sindi
* #since 14 June 2011
*
*/
public class LinkedInExample {
private static final String LINKEDIN_API_URL = "https://api.linkedin.com";
private static final String API_KEY = "";
private static final String API_SECRET = "";
private static final String CALLBACK_URL = "http://localhost:8080/myapp/oauth/authorize_ready";
private OAuth1Consumer consumer;
/**
*
*/
public LinkedInExample() {
super();
// TODO Auto-generated constructor stub
consumer = new OAuth1Consumer(API_KEY, API_SECRET, new OAuth1ServiceProvider(LINKEDIN_API_URL + "/uas/oauth/requestToken", LINKEDIN_API_URL + "/uas/oauth/authorize", LINKEDIN_API_URL + "/uas/oauth/accessToken"));
}
public RequestToken requestUnauthorizedRequestToken() throws OAuthException {
return consumer.requestUnauthorizedToken(null, CALLBACK_URL, null, new OAuthHmacSha1Signature());
}
public String getAuthorizationUrl(RequestToken token) throws OAuthException {
return consumer.createOAuthUserAuthorizationUrl(token, null);
}
public AccessToken requestAccessToken(AuthorizedToken authorizedToken, RequestToken token) throws OAuthException {
return consumer.requestAccessToken(null, authorizedToken, token.getTokenSecret(), new OAuthHmacSha1Signature());
}
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
try {
LinkedInExample example = new LinkedInExample();
RequestToken rt = example.requestUnauthorizedRequestToken();
//Now that we have request token, let's authorize it....
String url = example.getAuthorizationUrl(rt);
//Copy the URL to your browser and make sure that OAuth 1 Servlet is running....
} catch (OAuthException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
As you can see, the CALLBACK_URL is set to point to the OAuth Servlet configured to JOAuth, as you'll receive an Authorized Token.
You must make sure that you return the unauthorized request token back to the service on the RequestToken getRequestToken(HttpServletRequest request) method as you'll need it to retrieve the access token.
The service saveAccessToken() method is called when LinkedIn returns an access token. You can log to verify if the access token is returned.
Once you have an access token, you can use a LinkedIn API and Post Jobs using the Access Token. JOAuth is designed to retrieve access tokens only and not communicate with other APIs that exists.

To clarify a few things:
1) These are two-legged OAuth 1.0a calls. You don't need to do the OAuth dance.
2) These is no need for oob or anything like this.
3) You generate the Authorization header using the OAuth library of your choice and send an HTTP POST.
Here is a quick start: http://developer.linkedin.com/docs/DOC-1299
This being said, I will repeat again that this is a closed program, so unless you are on a special whitelisted set of partners, your calls will fail even if you solve all the above.
Furthermore, there is additional authorization necessary of your application by a job poster within their LinkedIn account to make your request succeed and related data needs to be in the XML POST body.
Based on the amount of questions you've had about this, if you are a whitelisted partner, please follow up with the LinkedIn Jobs API team, and we'll arrange for additional assistance.

Related

Using Jodd HTTP library behind proxy

I have just been handed a groovy project that is using the Jodd library (I have little experience with this). I am looking to find out how you setup configuration so that http and https calls can be made behind a company proxy.
At the moment a helper class has been setup
#! /usr/bin/groovy
package org.myOrg
import groovy.json.JsonBuilder
#Grab("org.jodd:jodd-http:3.8.5")
import jodd.http.HttpRequest
/**
* Helper class for making REST calls from a Jenkins Pipeline job.
*/
class JenkinsHttpClient {
// Constants
private static final String USER_AGENT = "User-Agent";
private final HttpRequest httpRequest
private final String userAgent = 'Jenkins'
JenkinsHttpClient() {
httpRequest = new HttpRequest()
}
/**
* GET method
* #param url - This is the endpoint
* #return response body as String
*/
private def get(String url) {
def resp = httpRequest.get(url)
.header(USER_AGENT, userAgent)
.send()
return resp.bodyText()
}
How or where do I add config so that this will work behind a proxy?
HttpConnectionProvider also allows you to specify the proxy. Just provide the ProxyInfo instance with the information about the used proxy (type, address, port, username, password):
SocketHttpConnectionProvider scp = new SocketHttpConnectionProvider();
scp.useProxy(ProxyInfo.httpProxy("proxy_url", 1090, null, null));
HttpResponse response = HttpRequest
.get("http://jodd.org/")
.withConnectionProvider(scp)
.send();
Jodd supports HTTP, SOCKS4 and SOCKE5 proxy types.
See the documentation.

Unable to use Java Signpost to properly OAuth (with Discogs)

I'm trying to use Java SignPost to get Oauth support from discogs
If I call this method it provides a url to a website, then if I login I get a verification code, so far so good.
public static String requestDiscogsAuthorization() throws Exception
{
OAuthConsumer consumer = new DefaultOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
OAuthProvider provider = new DefaultOAuthProvider(
"http://api.discogs.com/oauth/request_token",
"http://api.discogs.com/oauth/access_token",
"http://www.discogs.com/oauth/authorize");
provider.setRequestHeader("User-Agent", SongKong.USER_AGENT);
return provider.retrieveRequestToken(consumer, OAuth.OUT_OF_BAND);
}
I store this verification code in my application and can retrieve as UserPreferences.getInstance().getDiscogsAuthorization(). So now I want to sign a url I try and call the getAccessToken() below to get an access code from my verification key but I get
oauth.signpost.exception.OAuthExpectationFailedException: Authorized request token or token secret not set. Did you retrieve an authorized request token before?
at oauth.signpost.AbstractOAuthProvider.retrieveAccessToken(AbstractOAuthProvider.java:89)
at com.jthink.songkong.analyse.toplevelanalyzer.DiscogsAuth.getAccessToken(DiscogsAuth.java:54)
I dont see what im doing wrong
public class DiscogsAuth
{
/**
* Constructs a consumer with valid access token for signing urls (can only be used once user has authorizaed application and auth code
* available to application somewhere
*
* NOTE:Not thread safe, has to be done once for each thread
*
* #return
* #throws Exception
*/
public static OAuthConsumer getAccessToken() throws Exception
{
OAuthConsumer consumer = new DefaultOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
OAuthProvider provider = new DefaultOAuthProvider(
"http://api.discogs.com/oauth/request_token",
"http://api.discogs.com/oauth/access_token",
"http://www.discogs.com/oauth/authorize");
provider.setRequestHeader("User-Agent", SongKong.USER_AGENT);
provider.retrieveAccessToken(consumer, UserPreferences.getInstance().getDiscogsAuthorization());
return consumer;
}
/**
* Sign Url Connection
*
* #param urlConnection
* #param consumer
* #throws Exception
*/
public static void signUrl(HttpURLConnection urlConnection, OAuthConsumer consumer) throws Exception
{
consumer.sign(urlConnection);
}
}
Probably your method call not able to get the verification code when you try to call provider.retrieveAccessToken ,you can try by printing the verification code before you pass it on to the provider.retrieveAccessToken method.
You are missing access token and access token secret.
To sign an OAuth 1.0 request you need three things: the CONSUMER_SECRET that you already have, the access token and the access token secret which you will get in the oauth_token and oauth_token_secret variables respectively in the response of the Temporary Credentials request.
I have no experience with Java, but according to Signpost documentation i guess that you should call this...
consumer.setTokenWithSecret(ACCESS_TOKEN, TOKEN_SECRET);
...before you try to sign the url
consumer.sign(urlConnection);
Seems like you can get ACCESS_TOKEN and TOKEN_SECRET with ...
String accessToken = provider.getResponseParameter('oauth_token');
String tokenSecret = provider.getResponseParameter('oauth_token_secret');
... after you call this:
provider.retrieveAccessToken(consumer, verificationCode);
Here is my take (I didn't try it):
public class DiscogsAuth
{
/**
* Constructs a consumer with valid access token for signing urls (can only be used once user has authorizaed application and auth code
* available to application somewhere
*
* NOTE:Not thread safe, has to be done once for each thread
*
* #return
* #throws Exception
*/
public static OAuthConsumer getAccessToken() throws Exception
{
OAuthConsumer consumer = new DefaultOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);
OAuthProvider provider = new DefaultOAuthProvider(
"http://api.discogs.com/oauth/request_token",
"http://api.discogs.com/oauth/access_token",
"http://www.discogs.com/oauth/authorize");
provider.setRequestHeader("User-Agent", SongKong.USER_AGENT);
provider.retrieveAccessToken(consumer, UserPreferences.getInstance().getDiscogsAuthorization());
// Retrieve access token and access token secret
String accessToken = provider.getResponseParameter('oauth_token');
String tokenSecret = provider.getResponseParameter('oauth_token_secret');
// Set them on the consumer so it can sign the requestst with them
consumer.setTokenWithSecret(accessToken, tokenSecret);
return consumer;
}
/**
* Sign Url Connection
*
* #param urlConnection
* #param consumer
* #throws Exception
*/
public static void signUrl(HttpURLConnection urlConnection, OAuthConsumer consumer) throws Exception
{
consumer.sign(urlConnection);
}
}
The problem was that I was not using the same instance of OAuthConsumer and OAuthProvider for every step.

Basic authentication with Camel Jetty

I want to implement basic authentication for Jetty server programmatically, as shown here. For the sake of convenience, I am ^C-^V'ing that snippet here.
import org.mortbay.jetty.security.*;
Server server = new Server();
Connector connector = new SelectChannelConnector();
connector.setPort(8080);
server.setConnectors(new Connector[]{connector});
Constraint constraint = new Constraint();
constraint.setName(Constraint.__BASIC_AUTH);;
constraint.setRoles(new String[]{"user","admin","moderator"});
constraint.setAuthenticate(true);
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/*");
SecurityHandler sh = new SecurityHandler();
sh.setUserRealm(new HashUserRealm("MyRealm",System.getProperty("jetty.home")+"/etc/realm.properties"));
sh.setConstraintMappings(new ConstraintMapping[]{cm});
WebAppContext webappcontext = new WebAppContext();
webappcontext.setContextPath("/mywebapp");
webappcontext.setWar("./path/to/my/war/orExplodedwar");
webappcontext.addHandler(sh);
HandlerCollection handlers= new HandlerCollection();
handlers.setHandlers(new Handler[]{webappcontext, new DefaultHandler()});
server.setHandler(handlers);
server.start();
server.join();
Now the problem is that the above approach requires you to have a handle to the server. However in my case, since I am using Camel, I do not have a direct access to the server. This is how my pipeline is defined:
from("jetty:http://localhost:8080/documents_in?matchOnUriPrefix=true").
process(new MyProcessor());
How do I adapt the linked authentication solution to my case? Or do I have to follow some completely different method?
Please note that I am both a Camel and Jetty novice. Any help will be greatly appreciated. Thanks.
Addendum:
This page shows how to do it with Spring XML, however we are not using Spring, so that's of no use to us.
I stumbled across this problem a couple of days ago and I solved this issue by defining an own implementation of ConstraintSecurityHandler which uses a customized LoginService which takes care of the authentication BasicAuthenticator requires. As I did not find any existing LoginService implementation that is capable of dealing with bean-managed authentication, I needed to come up with this solution.
I'll post the almost complete class except for the internal stuff which has to be kept private.
import java.security.Principal;
import javax.annotation.Resource;
import javax.security.auth.Subject;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.DefaultIdentityService;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.MappedLoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Strings;
/**
* <p>
* Sets up a basic authentication mechanism for REST based services exposed via
* Jetty for our REST API (http(s)://server:port/api/v1/...).
* </p>
* <p>
* It moreover defines a login service which is capable of using an internal
* persistence layer for authenticating a user and his credentials received via
* a challenge response against a user entity retrieved via the persistence
* layer.
* </p>
*/
public class JettyBasicAuthAuthorizationHandler extends ConstraintSecurityHandler
{
/** The logger of this class **/
private static final Logger logger =
LoggerFactory.getLogger(JettyBasicAuthAuthorizationHandler.class);
/** The persistence service to retrieve the user informations from **/
#Resource
private ISomePersistenceService persistenceService;
private final String[] roles = new String[] {"user"};
/**
* <p>
* Initializes a Jetty based Basic Authentication mechanism.
* </p>
*/
public JettyBasicAuthAuthorizationHandler()
{
super();
// Specifies the challenge to be of type BASIC and that users have
// to fulfill one of the roles listed in roles. Moreover authentication
// is required
Constraint constraint = new Constraint();
constraint.setName(Constraint.__BASIC_AUTH);
constraint.setRoles(this.roles);
constraint.setAuthenticate(true);
// Map the defined constraints from above to the services provided via
// our REST API
ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/api/v1/*");
// BasicAuthenticator takes care of sending a challenge to the caller
// and calls our login service in case of a challenge response to
// evaluate if the user is permitted to use the service.
// The realm name defines the name of the login service which should be
// used for authentication.
BasicAuthenticator basic = new BasicAuthenticator();
this.setAuthenticator(basic);
this.addConstraintMapping(cm);
this.setRealmName("REST");
this.setLoginService(new BeanManagedLoginService("REST"));
logger.debug("JettyBasicAuthAuthorizationHandler created!");
}
/**
* <p>
* Implements a bean managed login service where an authentication response
* is propagated to a business layer bean which retrieves the user and
* credentials from a backing data store.
* </p>
*/
class BeanManagedLoginService implements LoginService
{
/** An identity service used to create a UserIdentity object for us **/
private IdentityService identityService = new DefaultIdentityService();
private String name = "REST";
/**
* <p>
* Initializes a new instance.
* </p>
*/
public BeanManagedLoginService()
{
}
/**
* <p>
* Initializes a new instance and sets the realm name this login service
* will work for.
* </p>
*
* #param name The name of this login service (also known as the realm it
* will work for)
*/
public BeanManagedLoginService(String name)
{
this.name = name;
}
/**
* <p>
* Returns the name of the login service (the realm name)
* </p>
*
* #return Get the name of the login service (aka Realm name)
*/
#Override
public String getName()
{
return this.name;
}
/**
* <p>
* Logs in a user by checking the username with known users and
* comparing the credentials with the stored ones. If the user could not
* be authenticated successfully an unauthenticated user identity will
* be returned.
* </p>
*
* #param username The user name as sent by the ChallengeResponse
* #param credentials The credentials provided in the ChallengeResponse
*
* #return If the user could be authenticated successfully a valid
* {#link UserIdentity}, else an unauthorized user identity
*/
#Override
public UserIdentity login(String username, Object credentials)
{
if (logger.isDebugEnabled())
logger.debug("received login request for user: '{}' with credentials: '{}'!",
username, credentials);
// check if the username is valid
if (!Strings.isNullOrEmpty(username))
{
String password = credentials.toString();
// retrieve the user from the business layer
final UserEntity sue = persistenceService.findUser(username);
if (sue == null)
{
if (logger.isErrorEnabled())
logger.error("No User could be found for UserId '{}'. The UserKey (which was not checked) is '{}'",
username, password);
return UserIdentity.UNAUTHENTICATED_IDENTITY;
}
// check whether the password matches the one in the user entity
// found for the user id
if (password.equals(sue.getUserKey()))
{
// the user could be successfully authenticated
if (logger.isDebugEnabled())
logger.debug("UserKey {} of User {} was successfully authenticated",
sue.getUserKey(), sue.getUserId());
if (logger.isDebugEnabled())
logger.debug("User '{}'/'{}' works for '{}'",
userId, userName, sue.getCompany().getName());
return this.createIdentityForUser(username, password);
}
else
{
// the password set in the request and the one stored in the
// user entity do not match
if (logger.isErrorEnabled())
logger.error(
"User {} could not be authenticated. The UserKey in the user entity is {} but the UserKey in the request was {}",
new Object[] { username, sue.getUserKey(), password });
return UserIdentity.UNAUTHENTICATED_IDENTITY;
}
}
else
{
if (logger.isErrorEnabled())
logger.error("Username is empty and therefore could not get authenticated correctly");
return UserIdentity.UNAUTHENTICATED_IDENTITY;
}
}
/**
* <p>
* Creates a UserIdentity object for a successfully authenticated user.
* </p>
*
* #param username The name of the authenticated user
* #param password The password of the authenticated user
*
* #return A valid UserIdentity object
*/
private UserIdentity createIdentityForUser(String username, String password)
{
// create a principal object needed for the user identity
Credential cred = Credential.getCredential(password);
// a principal is basically an identity of a real person
// (subject). So a user can have multiple principals
Principal userPrincipal = new MappedLoginService.KnownUser(username, cred);
// a subject collects all data necessary to identify a certain
// person. It may store multiple identities and passwords or
// cryptographic keys
Subject subject = new Subject();
// add a Principal and credential to the Subject
subject.getPrincipals().add(userPrincipal);
subject.getPrivateCredentials().add(cred);
subject.setReadOnly();
return this.identityService.newUserIdentity(subject, userPrincipal, roles);
}
/**
* <p>
* Validate just checks if a user identity is still valid.
* </p>
*/
#Override
public boolean validate(UserIdentity user)
{
return true;
}
#Override
public IdentityService getIdentityService()
{
return this.identityService;
}
#Override
public void setIdentityService(IdentityService service)
{
this.identityService = service;
}
#Override
public void logout(UserIdentity user)
{
}
}
}
To add this handler to Camel's embedded Jetty server you can define an endpoint using this handler like this:
jetty:https://your-server:port/api/v1/yourService?sslContextParameters=#sslContextParameters&handlers=#jettyAuthHandler
where jettyAuthHandler is the bean name of this handler - if you don't use SSL just omit the sslContextParameters parameter.
The JettyComponent in Camel has getter/setter where you can configure this in Java code.
JettyComponent jetty = new JettyComponent();
// use getter/setter to configure
// add component to Camel
camelContext.addComponent("jetty", jetty);
// after this you can add the routes and whatnot

Secure a java embedded web service with javax.jws without application server

I wrote the following code to implement a Java web service that communicates with an application written in another language on the same host:
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
#WebService(name = "MyWebService")
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.WRAPPED)
public class MyWebService {
#WebMethod(operationName = "methodName", action = "urn:#methodName")
#WebResult(name = "result", partName = "output")
public String methodName(#WebParam(name = "param1", partName = "input") String param1,
#WebParam(name = "param2", partName = "input") String param2){
// ...do something
return "You called this service with params: " + param1 + "," + param2;
}
Since requirements are not to use an application server to expose the web service I instantiated the service from another class as follows:
Endpoint endpoint = Endpoint.create(new MyWebService());
URL url = new URL("http://localhost:7777/MyWebService");
endpoint.publish(url.toString());
Questions:
1) Which is the simplest way to secure this service with username and password considering the architecture of this project?
Any code sample would be greatly appreciated.
2) I made some research and found the use of SOAPHandler and I think it would work for me.
In the case of using the SOAPHandler class how do I add headers to the message to require authentication from the client?
Thank you in advance
thanks so much for the response that's the direction I'm following too but
when I check any of the headers for example:
SOAPHeader header = soapContext.getMessage().getSOAPPart().getEnvelope().getHeader();
Iterator<SOAPElement> iterator = header.getAllAttributes();
I get a nullpointer exception...any ideas?
I did a working program. Just to add to what you already found out, following is a way to use handler
Endpoint endpoint = Endpoint.create(new MyWebService());
Binding binding = endpoint.getBinding();
List<Handler> handlerChain = new ArrayList<Handler>(1);
handlerChain.add(new MyHandler());
binding.setHandlerChain(handlerChain);
URL url = new URL("http://localhost:7777/MyWebService");
endpoint.publish(url.toString());
MyHandler is class extending Handler interface. Alternately, you can use #HandlerChain annotation which will need an xml configuration file for handlers. Configure this for incoming messages only
public class MyHandler implements SOAPHandler{
#Override
public Set<?> getHeaders() {
// TODO Auto-generated method stub
return null;
}
#Override
public void close(MessageContext context) {
// TODO Auto-generated method stub
}
#Override
public boolean handleFault(MessageContext context) {
// TODO Auto-generated method stub
return false;
}
#Override
public boolean handleMessage(MessageContext context) {
System.out.println("Hehehe the handler");
SOAPMessageContext soapContext = (SOAPMessageContext)context;
try {
SOAPHeader header = soapContext.getMessage().getSOAPPart().getEnvelope().getHeader();
//Check there if the required data (username/password) is present in header or not and return true/false accordingly.
} catch (SOAPException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
}
From the client side also, if your client is using JAB-WS, you will have to use client handlers. Following is a typical JAX-WS client invocation example
Dispatch<Source> dispatch = … create a Dispatch<Source>
dispatch.getBinding().setHandlerChain(chain)
Source request = … create a Source object
Source response = dispatch.invoke(request);
Here the handler in chain will add header to outgoing request. Configure this for Outgoing messages only.
What you did is fair enough.
Concerning the authentication you can just expose a method for passing user name and password as login credentials.
Once the user has provided the correct credentials the user has been authenticated.
Note: Now you must maintain session data and make sure that an incoming request is from an authenticated user. The Endpoint just deploys internally a lightweight http server. You must design you web service implementation to keep "state" among requests.
You have 2 more options.
Do the authentication at the SOAP level. I would not really recomend
it. But if you do, note that the Endpoint does not deploy a
WSDL. So you must communicate exactly to the client connecting,
the SOAP header you expect. It is possible though to write a WSDL by
yourself and "attach" it to the Endpoint.
Do the authentication at the http request level. I.e. add a token or
cookie to the http request. To be honest I do not remember if this
is easy using the Endpoint

JAX-WS authentication against a database

I'm implementing a JAX-WS webservice that will be consumed by external Java and PHP clients.
The clients have to authenticate with a username and password stored in a database per client.
What authentication mechanism is best to use to make sure that misc clients can use it?
For our Web Service authentication we are pursuing a twofold approach, in order to make sure that clients with different prerequisites are able to authenticate.
Authenticate using a username and password parameter in the HTTP Request Header
Authenticate using HTTP Basic Authentication.
Please note, that all traffic to our Web Service is routed over an SSL secured connection. Thus, sniffing the passwords is not possible. Of course one may also choose HTTP authentication with digest - see this interesting site for more information on this.
But back to our example:
//First, try authenticating against two predefined parameters in the HTTP
//Request Header: 'Username' and 'Password'.
public static String authenticate(MessageContext mctx) {
String s = "Login failed. Please provide a valid 'Username' and 'Password' in the HTTP header.";
// Get username and password from the HTTP Header
Map httpHeaders = (Map) mctx.get(MessageContext.HTTP_REQUEST_HEADERS);
String username = null;
String password = null;
List userList = (List) httpHeaders.get("Username");
List passList = (List) httpHeaders.get("Password");
// first try our username/password header authentication
if (CollectionUtils.isNotEmpty(userList)
&& CollectionUtils.isNotEmpty(passList)) {
username = userList.get(0).toString();
password = passList.get(0).toString();
}
// No username found - try HTTP basic authentication
if (username == null) {
List auth = (List) httpHeaders.get("Authorization");
if (CollectionUtils.isNotEmpty(auth)) {
String[] authArray = authorizeBasic(auth.get(0).toString());
if (authArray != null) {
username = authArray[0];
password = authArray[1];
}
}
}
if (username != null && password != null) {
try {
// Perform the authentication - e.g. against credentials from a DB, Realm or other
return authenticate(username, password);
} catch (Exception e) {
LOG.error(e);
return s;
}
}
return s;
}
/**
* return username and password for basic authentication
*
* #param authorizeString
* #return
*/
public static String[] authorizeBasic(String authorizeString) {
if (authorizeString != null) {
StringTokenizer st = new StringTokenizer(authorizeString);
if (st.hasMoreTokens()) {
String basic = st.nextToken();
if (basic.equalsIgnoreCase("Basic")) {
String credentials = st.nextToken();
String userPass = new String(
Base64.decodeBase64(credentials.getBytes()));
String[] userPassArray = userPass.split(":");
if (userPassArray != null && userPassArray.length == 2) {
String userId = userPassArray[0];
String userPassword = userPassArray[1];
return new String[] { userId, userPassword };
}
}
}
}
return null;
}
The first authentication using our predefined "Username" and "Password" parameters is in particular useful for our integration testers, who are using SOAP-UI (Although I am not entirely sure whether one cannot get to work SOAP-UI with HTTP Basic Authentication too). The second authentication attempt then uses the parameters which are provided by HTTP Basic authentication.
In order to intercept every call to the Web Service, we define a handler on every endpoint:
#HandlerChain(file = "../../../../../handlers.xml")
#SchemaValidation(handler = SchemaValidationErrorHandler.class)
public class DeliveryEndpointImpl implements DeliveryEndpoint {
The handler.xml looks like:
<handler-chains xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee">
<handler-chain>
<handler>
<handler-name>AuthenticationHandler</handler-name>
<handler-class>mywebservice.handler.AuthenticationHandler</handler-class>
</handler>
</handler-chain>
</handler-chains>
As you can see, the handler points to an AuthenticationHandler, which intercepts every call to the Web Service endpoint. Here's the Authentication Handler:
public class AuthenticationHandler implements SOAPHandler<SOAPMessageContext> {
/**
* Logger
*/
public static final Log log = LogFactory
.getLog(AuthenticationHandler.class);
/**
* The method is used to handle all incoming messages and to authenticate
* the user
*
* #param context
* The message context which is used to retrieve the username and
* the password
* #return True if the method was successfully handled and if the request
* may be forwarded to the respective handling methods. False if the
* request may not be further processed.
*/
#Override
public boolean handleMessage(SOAPMessageContext context) {
// Only inbound messages must be authenticated
boolean isOutbound = (Boolean) context
.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (!isOutbound) {
// Authenticate the call
String s = EbsUtils.authenticate(context);
if (s != null) {
log.info("Call to Web Service operation failed due to wrong user credentials. Error details: "
+ s);
// Return a fault with an access denied error code (101)
generateSOAPErrMessage(
context.getMessage(),
ServiceErrorCodes.ACCESS_DENIED,
ServiceErrorCodes
.getErrorCodeDescription(ServiceErrorCodes.ACCESS_DENIED),
s);
return false;
}
}
return true;
}
/**
* Generate a SOAP error message
*
* #param msg
* The SOAP message
* #param code
* The error code
* #param reason
* The reason for the error
*/
private void generateSOAPErrMessage(SOAPMessage msg, String code,
String reason, String detail) {
try {
SOAPBody soapBody = msg.getSOAPPart().getEnvelope().getBody();
SOAPFault soapFault = soapBody.addFault();
soapFault.setFaultCode(code);
soapFault.setFaultString(reason);
// Manually crate a failure element in order to guarentee that this
// authentication handler returns the same type of soap fault as the
// rest
// of the application
QName failureElement = new QName(
"http://yournamespacehere.com", "Failure", "ns3");
QName codeElement = new QName("Code");
QName reasonElement = new QName("Reason");
QName detailElement = new QName("Detail");
soapFault.addDetail().addDetailEntry(failureElement)
.addChildElement(codeElement).addTextNode(code)
.getParentElement().addChildElement(reasonElement)
.addTextNode(reason).getParentElement()
.addChildElement(detailElement).addTextNode(detail);
throw new SOAPFaultException(soapFault);
} catch (SOAPException e) {
}
}
/**
* Handles faults
*/
#Override
public boolean handleFault(SOAPMessageContext context) {
// do nothing
return false;
}
/**
* Close - not used
*/
#Override
public void close(MessageContext context) {
// do nothing
}
/**
* Get headers - not used
*/
#Override
public Set<QName> getHeaders() {
return null;
}
}
In the AuthenticationHandler we are calling the authenticate() method, defined further above. Note that we create a manual SOAP fault called "Failure" in case something goes wrong with the authentication.
Basic WS-Security would work with both Java and PHP clients (amongst others) plugged in to JAAS to provide a database backend . How to implement that kind of depends on your container. Annotate your web service methods with the #RolesAllowed annotation to control which roles the calling user must have. All J2EE containers will provide some mechanism to specify against which JAAS realm users should be authenticated. In Glassfish for example, you can use the admin console to manage realms, users and groups. In your application.xml you then specify the realm and the group to role mappings.
Here are some details of how to achieve this on Glassfish
With JBoss WS in JBoss, it's even easier.
What JAX-WS implementation are you using and in which container?
Is there a way independent on the current container? I'd like to define which class is responsible for authorisation. That class could call database or have password elsewhere.

Categories

Resources