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
Related
I'm writing an application using Spring Boot and Java that will be writing files to Azure Blob Storage. How can I use a Service Principal to authenticate? The details of the SP should ideally be read in via some properties or an external file.
I've been wading through the reams of documentation and examples, all of which don't seem to be quite what I'm looking for. Most examples that I've seen use the Storage Account Key which I don't want to do.
Some example code would be really appreciated. As I said, I'm struggling to find a decent example (both of how to use an SP but also generally how to write to Azure BLOB Storage in Java) as there seems to be so many different ways of accessing storage scattered around in the microsoft docs.
You can use ADAL4J to acquire a token, and then use the token to write to blobs.
Add role assignment to your principal.
Get token.
public static String getToken() throws Exception {
String TENANT_ID = "your tenant id or name, e4c9*-*-*-*-*57fb";
String AUTHORITY = "https://login.microsoftonline.com/" + TENANT_ID;
String CLIENT_ID = "your application id, dc17*-*-*-*a5e7";
String CLIENT_SECRET = "the secret, /pG*32";
String RESOURCE = "https://storage.azure.com/";
String ACCESS_TOKEN = null;
ExecutorService service = Executors.newFixedThreadPool(1);
AuthenticationContext context = null;
try {
context = new AuthenticationContext(AUTHORITY, false, service);
ClientCredential credential = new ClientCredential(CLIENT_ID, CLIENT_SECRET);
Future<AuthenticationResult> future = context.acquireToken(RESOURCE, credential, null);
ACCESS_TOKEN = future.get().getAccessToken();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} finally {
service.shutdown();
}
return ACCESS_TOKEN;
}
Access blob.
public static void main(String[] args) throws Exception {
String token = getToken();
StorageCredentialsToken credentialsToken = new StorageCredentialsToken("storagetest789", token);
CloudBlobClient blobClient = new CloudBlobClient(new URI("https://storagetest789.blob.core.windows.net/"), credentialsToken);
CloudBlobContainer blobContainer = blobClient.getContainerReference("pub");
CloudBlockBlob blockBlob = blobContainer.getBlockBlobReference("test.txt");
blockBlob.uploadText("Test!");
}
Hope it helps.
I have created an article on connecting Spring Boot App with Azure Storage account using Serivce Principal you can refer that.
https://medium.com/#iamdeveshkumar/using-azure-blob-storage-with-a-spring-boot-app-6238c137df7
pom.xml
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-storage-blob</artifactId>
<version>12.12.0</version>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.3.1</version>
</dependency>
application.properties
app.config.azure.client-id=xxxxxxxxxxx
app.config.azure.client-secret=xxxxxxxxxxx
app.config.azure.tenant-id=xxxxxxxxxxx
app.config.azure.storage-id=xxxxxxxxxxx
app.config.azure.storage-endpoint=https://{STORAGE-ID}.blob.core.windows.net
app.config.azure.storage.container=xxxxxxxxxxx
AzureStorageConfiguration.java
#Data
#Configuration
#Slf4j
public class AzureStorageConfiguration {
private static final Logger logger = LoggerFactory.getLogger(AzureStorageConfiguration.class);
#Value("${app.config.azure.client-id}")
private String clientId;
#Value("${app.config.azure.client-secret}")
private String clientSecret;
#Value("${app.config.azure.tenant-id}")
private String tenantId;
#Value("${app.config.azure.storage-id}")
private String storageId;
#Value("${app.config.azure.storage-endpoint}")
private String storageEndpoint;
#Value("${app.config.azure.storage.container}")
private String storageContainer;
/**
* Blob service client builder blob service client builder.
*
* #return the blob service client builder
*/
#Bean
public BlobServiceClientBuilder blobServiceClientBuilder() {
return new BlobServiceClientBuilder()
.credential(getAzureClientCredentials())
.endpoint(getStorageEndpoint());
}
private ClientSecretCredential getAzureClientCredentials() {
return new ClientSecretCredentialBuilder()
.clientId(clientId)
.clientSecret(clientSecret)
.tenantId(tenantId)
.build();
}
/**
* Gets storage endpoint.
*
* #return the storage endpoint
*/
public String getStorageEndpoint() {
return storageEndpoint.replace("{STORAGE-ID}", storageId);
}
/**
* A util method to upload a file to Azure Storage.
*
* #param blobServiceClientBuilder service client builder
* #return BlobServiceAsyncClient blob service async client
*/
#Bean(name = "blobServiceAsyncClient")
public BlobServiceAsyncClient blobServiceAsyncClient(
BlobServiceClientBuilder blobServiceClientBuilder) {
/*
retryDelay is by default 4ms and maxRetryDelay is by default 120ms
*/
return blobServiceClientBuilder.retryOptions(
new RequestRetryOptions(
RetryPolicyType.EXPONENTIAL,
5,
Duration.ofSeconds(300L),
null,
null,
null)).buildAsyncClient();
}
}
Then you can use the BlobServiceAsyncClient to create BlobAsyncClient for various blob operations.
/**
* Get blob async client blob async client.
*
* #param container the container
* #param blobName the blob name
* #return the blob async client
*/
public BlobAsyncClient getBlobAsyncClient(String container, String blobName) {
BlobContainerAsyncClient blobContainerAsyncClient =
blobServiceAsyncClient.getBlobContainerAsyncClient(container);
return blobContainerAsyncClient.getBlobAsyncClient(blobName);
}
/**
* Upload to azure blob.
*
* #param container the container
* #param blobName the blob name
* #param data the data
*/
public void uploadToAzureBlob(String container, String blobName, byte[] data) {
BlobAsyncClient blobAsyncClient = getBlobAsyncClient(container, blobName);
long blockSize = 2L * 1024L * 1024L; //2MB
blobAsyncClient.upload(covertByteArrayToFlux(data),
getTransferOptions(blockSize), true)
.doOnSuccess(blockBlobItem -> logger.info("Successfully uploaded !!"))
.doOnError(throwable -> logger.error(
"Error occurred while uploading !! Exception:{}",
throwable.getMessage()))
.subscribe();
}
/**
* Covert byte array to flux flux.
*
* #param byteArray the byte array
* #return the flux
*/
public Flux<ByteBuffer> covertByteArrayToFlux(byte[] byteArray) {
return Flux.just(ByteBuffer.wrap(byteArray));
}
/**
* Creating TransferOptions.
*
* #param blockSize represents block size
* #return ParallelTransferOptions transfer options
*/
public ParallelTransferOptions getTransferOptions(long blockSize) {
return new ParallelTransferOptions()
.setBlockSizeLong(blockSize)
.setMaxConcurrency(5)
.setProgressReceiver(
bytesTransferred -> logger.info("Uploading bytes:{}", bytesTransferred));
}
For more details and code you can refer to my github repo
https://github.com/kdevesh/azure-storage-spring-boot-app
P.S. I am using the async flavour of Blob Client there is a sync flavour also available if somebody wants to use that.
Another way to get the Access token is
using the MS authentication lib
This library used "builders" to build out the confidential client. If you use that class, it handles refreshing the token for you and handles the cache
I use the method as Jack Jia , but it don't work.....i can got the token ,but when i upload , something wrong enter image description here
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.
Ok I have a package with two restful web service class Login.class and Subscribe.class. When I deploy them to the app engine and test them they work. Now I add a new service class name User s.class to the package now none of my service works when I invoke them I get a internal server error.
com.sun.jersey.spi.inject.Errors processErrorMessages: The following errors and warnings have been detected with resource and/or provider classes:
SEVERE: Producing media type conflict. The resource methods public javax.ws.rs.core.Response com.ppsjamaica.webservices.PPSUsers.getUserById(java.lang.Long) and public javax.ws.rs.core.Response com.ppsjamaica.webservices.PPSUsers.getUserByUsernamePassword(java.lang.String,java.lang.String) can produce the same media type
Uncaught exception from servlet
com.sun.jersey.spi.inject.Errors$ErrorMessagesException
at com.sun.jersey.spi.inject.Errors.processErrorMessages(Errors.java:170)
my Subscribe class is:
#Path ("/subscriber")
public class Subscribe {
#GET
#Produces (MediaType.APPLICATION_JSON)
public Response getSubscriber (#QueryParam("email")String email)
{
EntityManager entityManager = EMF.getInstance ().createEntityManager ();
/*
* Sends back a BAD_REQUEST HTTP status
* if email query parameter is empty
*/
if (email.isEmpty ())
return Response.status(Status.BAD_REQUEST).build ();
try
{
Query query = entityManager.createQuery ("SELECT FROM Subscriber WHERE email= :emailParam");
query.setParameter("emailParam", email);
return Response.status(Status.OK).entity((Subscriber) query.getSingleResult ()).build ();
}
catch (NoResultException e)
{
return Response.status (Status.NOT_FOUND).build ();
}
}//end Subscriber method
}
my login class is:
#Path ("/login")
public class Login {
#GET
#Produces (MediaType.APPLICATION_JSON)
public User authenticate (#QueryParam("username") String username, #QueryParam("password")String password)
{
User user = new User ();
return user;
}//end authenticate method
#GET
#Path ("/Subscriber")
#Produces (MediaType.APPLICATION_JSON)
public Response getSubsriber (#QueryParam("email") String email)
{
EntityManager entityManager = EMF.getInstance ().createEntityManager();
try
{
Query query = entityManager.createQuery("SELECT FROM Subscriber WHERE email= :emailParam");
query.setParameter("emailParam", email);
Subscriber subscriber = (Subscriber)query.getSingleResult();
return Response.status(Status.OK).entity(subscriber).build ();
}
catch(Exception e)
{
throw new RuntimeException (e);
}
}//end getSubsriber method
}//end Login class
and my users class is:
#Path ("/users")
public class Users {
/**
* Gets all User objects from datastore. Returns
* NOT_FOUND HTTP Response status if no result
* found and OK HTTP Response status if it does with
* a list of User objects.
*
* URL: domain-name/services/users/all
*
* #author Mario Dennis
* #return Response
*/
#GET
#Path ("/all")
#Produces(MediaType.APPLICATION_JSON)
public Response getAllUsers ()
{
try
{
EntityManager entityManager = EMF.getInstance().createEntityManager();
Query query = entityManager.createQuery("SELECT FROM User");
#SuppressWarnings("unchecked")
List<User> user = (List<User>) query.getSingleResult ();
GenericEntity<List<User>> entity = new GenericEntity<List<User>>(user){};
return Response.status(Status.OK).entity(entity).build ();
}
catch (NoResultException e)
{
return Response.status(Status.NOT_FOUND).build ();
}
}//end getAllUsers method
/**
* Gets User object with user name and password specified from
* datastore. Returns NOT_FOUND HTTP Response status if no result
* found and OK HTTP Response status if it is found and BAD_REQUEST
* if parameter supplied is empty.
* a list of User objects.
*
* URL: domain-name/services/users/{username}&{password}
*
* #author Mario Dennis
* #return Response
*/
#GET
#Produces (MediaType.APPLICATION_JSON)
public Response getUserByUsernamePassword (#QueryParam("username")String username,#QueryParam("password") String password)
{
/*
* Sends back a BAD_REQUEST HTTP status
* if email query parameter is empty
*/
if (username.isEmpty() || password.isEmpty())
return Response.status(Status.BAD_REQUEST).build ();
try
{
EntityManager entityManager = EMF.getInstance().createEntityManager ();
Query query = entityManager.createQuery ("SELECT FROM User WHERE user_name= :usernameParam AND password= :passwordParam");
query.setParameter("usernameParam", username);
query.setParameter("passwordParam",password);
User user = (User) query.getSingleResult ();
return Response.status(Status.OK).entity(user).build ();
}
catch (NoResultException e)
{
return Response.status(Status.NOT_FOUND).build ();
}
}//end getUserByUsernamePassword method
/**
* Gets User object with id specified from datastore.
* Returns NOT_FOUND HTTP Response status if no result
* found and OK HTTP Response status if it is found and
* BAD_REQUEST if parameter supplied is empty.
* a list of User objects.
*
* URL: domain-name/services/users/{id}
*
* #author Mario Dennis
* #return Response
*/
#GET
#Produces (MediaType.APPLICATION_JSON)
public Response getUserById (#QueryParam("id")Long id)
{
/*
* Sends back a BAD_REQUEST HTTP status
* if email query parameter is empty.
*/
if (id == null)
return Response.status(Status.BAD_REQUEST).build ();
try
{
EntityManager entityManager = EMF.getInstance ().createEntityManager ();
User user = entityManager.find(User.class,id);
/*
* Checks if user was found. Return a NOT_FOUND
* HTTP status if user was not located.
*/
if (user == null)
return Response.status(Status.NOT_FOUND).build ();
else
return Response.status(Status.OK).entity(user).build();
}
catch (NoResultException e)
{
return Response.status(Status.NOT_FOUND).build ();
}
}//end getUserById method
/**
* Save User object to database. Return NOT_MODIFIED
* HTTP status if persisting of object failed. Otherwise
* return OK HTTP status if it was successful.
*
* URL: domain-name/services/users/{JSON}
*
* #author Mario Dennis
* #param user
* #return Response
*/
#POST
#Consumes (MediaType.APPLICATION_JSON)
public Response saveUser (User user)
{
try
{
EntityManager entityManager = EMF.getInstance().createEntityManager();
entityManager.getTransaction ().begin();
entityManager.persist(user);
entityManager.getTransaction().commit();
return Response.status(Status.OK).build ();
}
catch(Exception e)
{
return Response.status(Status.NOT_MODIFIED).build ();
}
}//end saveUser method
}//end Users class
When I delete the user class and upload back the other two services starts working again. What is causing this?
web.xml
<!-- Jersey Framework mapping -->
<servlet>
<servlet-name>RESTful</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
<init-param>
<param-name>com.sun.jersey.config.property.packages</param-name>
<param-value>com.ppsjamaica.webservices</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>RESTful</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
<!-- end Jersey Framework mapping -->
Please don't ever design a service that requires users to put password in the URI.
On why it is failing: both methods use the same URI template (note the fact that each of them injects different query parameters does not mean they are mapped to a different URI - it is the value of #Path annotation that matters - and it is the same for both). You have to disambiguate them by mapping them to a different URI template.
On approach is to create a custom exception that inherits from WebApplicationException; you can see more details here. You should not throw directly a RuntimeException because the container does not know if it is an expected behavior.
Another approach is to annotate your custom exception (which inherits directly from RuntimeException) with #ApplicationException although I do not know if this is the way to do it in JAX-RS; only in EJB.
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.
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.