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.
Related
I need to authenticate a user using AWS's Cognito in Android and get a token to use on my future requests. Some information is provided to me by the backend but I still haven't managed to use it in the appropriate way, and Cognito's documentation did not help me on this. I have this fixed info:
Pool Region: us-east-1
Pool ID: us-east-1:xxxxx-xxxxx-xxxxx-xxxx-xxxxxxxx
And after authenticating the user on the login endpoint I get this info:
{
"cognitoId": "us-east-1:yyyy-yyyy-yyyy-yyyy-yyyyyyy",
"cognitoToken": "hH1Q8bCLh9-pamP6DCrC0-KY4rNtZ115xDedE224CeEanex-CCWh4tWUtJjPc_tU3d6eJ_7Uk23ceTNhCFYT1qnAL_7kAH_lHod4a1GQo29FuTLQSqx4lOFv2Ev3RvYcCzjyLEAA1-EIKBtfSm_YN9y6DHBOzDJ8owLJTxB0JEWvsWfATjug4P8hxCI97RVB2cetrmq4JvZr__bCziUb-7AifPvy4VMW3xLjJ7uyDvogwcx5gJ1rF8Z38_z7kREB1R_CYPRVQuoHzag0j9RoOTNeAYFGO42qgCewTl3Lvm5PUbTIGhCIp6y1RVWAPLEdMWmQ3LVpqJcZKLQRhMmEzOGMyTUiXSwiaXNzIjoiaHR0cHM6Ly9jb2duaXRvLWlkZW50aXR5LmFtYXpvbmF3cy5jb20iLCJleHAiOjE1MTE2NDEzMDksImlhdCI6MTUxMTYyNjkwOX0.QFWGxh_"
}
The IDs were omitted and the token was altered in order to preserve the information. It is important to note that the Pool ID (constant in the app) and the cognitoId (returned by the backend) are different.
I have a static Credentials Provider initialized like this:
credentialsProvider = new CognitoCachingCredentialsProvider(
getApplicationContext(), /* get the context for the application */
IDENTITY_POOL_ID, /* Identity Pool ID */
Regions.US_EAST_1 /* Region for your identity pool--US_EAST_1 or EU_WEST_1*/
);
This is the task that does the work of trying to get the Cognito auth:
private static final class CognitoAuthTask extends AsyncTask<String, Void, String> {
#Override
protected String doInBackground(String... strings) {
String userId = strings[0];
String token = strings[1];
String sessionToken = null;
try {
Map<String, String> logins = new HashMap<String, String>();
logins.put(userId, token);
credentialsProvider.setLogins(logins);
AWSSessionCredentials credentials = credentialsProvider.getCredentials();
sessionToken = credentials.getSessionToken();
} catch (Exception e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
} finally {
return sessionToken;
}
}
#Override
protected void onPostExecute(String authToken) {
super.onPostExecute(authToken);
cognitoAuthToken = authToken;
if (BuildConfig.DEBUG) {
Log.d("Cognito Token", cognitoAuthToken == null ? "null" : cognitoAuthToken);
}
}
}
And this is where I call it when I have the information from my login endpoint (as I showed above):
public void authenticateCognito(String userId, String token) {
new CognitoAuthTask().execute(userId, token);
}
The problem is that this is not working, I get this error here:
Invalid login token. Can't pass in a Cognito token. (Service:
AmazonCognitoIdentity; Status Code: 400; Error Code:
NotAuthorizedException; Request ID: zzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzz)
The error happens on the task, on this line of code here:
credentialsProvider.getCredentials();
The backend team mentioned that I would need to use the GetCredentialsForIdentity method, but I can't find anything like that on the Cognito Android SDK.
Any help is appreciated.
The class you should be using is AmazonCognitoIdentityClient, that is the class implementing the GetCredentialsForIdentity API.
When credentialsProvider.getCredentials(); is invoked, the internal AmazonCognitoIdentityClient calls GetCredentialsForIdentity to get new credentials from Cognito.
The Invalid login token error is returned by the service if the provided token has expired.
We are planning to separate our UI code from our spring restful apis as we want ui to be customized for different clients. We have a rest api which authenticates user against username and password provided. Actually in my UI package i have a spring controller which calls this api to get the user authenticated with spring RestTemplate and returns appropriate page depending on the outcome. The authentication api uses the IP address from the request and blocks the IP address for 24 hours if someone provides wrong username password three times. But the issue is every time i call this api it gets my server's IP address where my UI package is deployed and i guess this is correct behavior as the caller is my UI server. So is there a way i can get the IP address of caller on my UI package and set it to the request which I make to my api. Is there a way i can set actual caller's IP in RestTemplate request.
You can do it using HttpServletRequest, your api method should have parameter defined for HttpServletRequest.
Your api method should look like:
#RequestMapping(value = "/myApiPath", method = RequestMethod.GET)
public void myApiMethod(MyObject myobject, final HttpServletRequest request) throws IOException {
String ipAddress=getIpAddressOfRequest(request); // get Ip address
}
And then use HttpServletRequest request for getting ip address, as below:
public static String getIpAddressOfRequest(final HttpServletRequest request) {
String remoteAddr = "";
if (request != null) {
remoteAddr = request.getHeader("X-FORWARDED-FOR");
if (remoteAddr == null || "".equals(remoteAddr)) {
remoteAddr = request.getRemoteAddr();
}
}
return remoteAddr;
}
And even you can have your condition on domain name, by getting server name using below code:
public static String getProtocolHostnameAndPort(final HttpServletRequest request) {
String protocol = request.getProtocol().split("/")[0].toLowerCase();
String hostname = request.getServerName();
int port = request.getServerPort();
StringBuilder result = new StringBuilder(protocol + "://" + hostname);
if (port != 80) {
result.append(":").append(port);
}
return result.toString();
}
I want to develop a SOAP client using CXF to connect to SharePoint. The authentication scheme is NTLM.
I am blocked on a scenario where the logged-in user of a machine (on which the SOAP client is being run) has access to SharePoint. The CXF soap client always uses the logged-in user. I want to specify some other user credentials (not the logged-in).
As CXF uses in-JDK HttpURLConnection; and what I have read about HttpURLConnection is, it bypasses the specified credentials when the logged-in user is NTLM authenticated.
Codes were tried on CXF version 2.7.11.
Solutions that I have tried out:
1) Setting Conduit authorization
String username = "user";
String password = "password";
JaxWsProxyfactoryBean factory1 = new JaxWsProxyfactoryBean();
factory1.setServiceClass(WebsSoap.class);
factory1.setAddress(url);
factory1.setUsername(username);
factory1.setPassword(password);
WebsSoap service = (WebsSoap) factory1.create();
Client client = ClientProxy.getClient(service);
HTTPconduit conduit = (HTTPconduit) client.getconduit();
conduit.getAuthorization().setAuthorizationType("NTLM");
conduit.getAuthorization().setUserName(username);
conduit.getAuthorization().setPassword(password);
HTTPClientPolicy httpClientPolicy = new HTTPClientPolicy();
httpClientPolicy.setConnectionTimeout(36000);
httpClientPolicy.setAllowChunking(false);
conduit.setClient(httpClientPolicy);
service.getWeb(".");
Problem:
This does not work for the scenario specified above, as it always uses the logged-in credentials. And when I specify invalid credentials, it does not fail.
2) AsyncHTTPConduit
Another solution is to use AsyncHTTPConduit that uses HttpAsyncClient instead of HttpURLConnection. This is beacuse HTTP components do not bypass specified credentials and logged-in user can be ignored (I have successfully verified this with a test client using HttpClient).
Below is the code snippet::
Bus bus = BusFactory.getDefaultBus();
bus.setProperty( "use.async.http.conduit", "true" );
Client client = ClientProxy.getClient( service );
HTTPConduit http = (HTTPConduit)client.getConduit();
if ( http instanceof AsyncHTTPConduit ) {
AsyncHTTPConduit conduit = (AsyncHTTPConduit)http;
DefaultHttpAsyncClient defaultHttpAsyncClient;
try {
defaultHttpAsyncClient = conduit.getHttpAsyncClient();
}
catch ( IOException exception ) {
throw new RuntimeException( exception );
}
defaultHttpAsyncClient.getCredentialsProvider().setCredentials( AuthScope.ANY,
new NTCredentials( "username", "password", "", "domain" ) );
conduit.getClient().setAllowChunking( false );
conduit.getClient().setAutoRedirect( true );
}
Problem:
Above code throws error:
Authorization loop detected on conduit.
The above code snapshot shows the usage of DefaultHttpAsyncClient which is deprecated now and CloseableHttpAsyncClient is to be used instead. But CloseableHttpAsyncClient does not provide a way to specify credentials to an already existing CloseableHttpAsyncClient object. Not sure how to use CloseableHttpAsyncClient in this scenario.
3) Other solutions
The other solution that I tried out is to use sun.net.www.protocol.http.ntlm.NTLMAuthenticationCallback, to bypass logged-in user authentication, as mentioned here. Use this approach along with solution #1 mentioned above. This works as expected for valid/invalid credentials, and the code bypasses the logged-in credentials :). But when I specify invalid credentials, I do not get HTTP 401 error, instead I get
Could not send message, server reached max retries 20
I am trying to avoid this solution because it uses java’s internal package and there is no way to determine HTTP 401 error directly.
What can I do to arrive at a complete solution?
Try this interceptor. This will avoid automatic authentication.
public class DisableAutomaticNTLMAuthOutInterceptor extends AbstractPhaseInterceptor<Message>
{
private boolean isFieldsAvailable;
private Field tryTransparentNTLMProxyField;
private Field tryTransparentNTLMServerField;
public DisableAutomaticNTLMAuthOutInterceptor() {
super(Phase.PRE_STREAM);
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Void run() {
try {
DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMServerField = HttpURLConnection.class.getDeclaredField("tryTransparentNTLMServer");
DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMServerField.setAccessible(true);
DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMProxyField = HttpURLConnection.class.getDeclaredField("tryTransparentNTLMProxy");
DisableAutomaticNTLMAuthOutInterceptor.this.tryTransparentNTLMProxyField.setAccessible(true);
DisableAutomaticNTLMAuthOutInterceptor.this.isFieldsAvailable = true;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
});
}
#Override
public void handleMessage(final Message message) throws Fault {
if (this.isFieldsAvailable)
AccessController.doPrivileged(new PrivilegedAction<Object>() {
public Void run() {
try {
Object httpConnection = message.get("http.connection");
if (httpConnection != null) {
DisableAutomaticNTLMAuthOutInterceptor.this.processHttpConnection(message.get("http.connection"));
}
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
});
}
private void processHttpConnection(Object httpConnection) throws IllegalArgumentException, IllegalAccessException {
if (HttpURLConnection.class.isAssignableFrom(httpConnection.getClass())) {
tryTransparentNTLMServerField.set(httpConnection, Boolean.FALSE);
tryTransparentNTLMProxyField.set(httpConnection, Boolean.FALSE);
} else {
Field tempField = null;
for (Field field : httpConnection.getClass().getDeclaredFields()) {
if (HttpURLConnection.class.isAssignableFrom(field.getType())) {
field.setAccessible(true);
tempField = field;
break;
}
}
if (tempField != null) {
processHttpConnection(tempField.get(httpConnection));
}
}
}
}
I'm using this ContainerRequestFilter to check HTTP Basic credentials.
private class Filter implements ResourceFilter, ContainerRequestFilter {
#Override
public ContainerRequest filter(ContainerRequest request) {
String auth = request.getHeaderValue("Authorization");
if (auth == null || !auth.startsWith("Basic ")) {
throw new NotAuthorizedException("FAILED\n");
}
auth = Base64.base64Decode(auth.substring("Basic ".length()));
String[] vals = auth.split(":");
String username = vals[0];
String password = vals[1];
boolean validUser = database.Users.validate(username, password);
if (!validUser) {
throw new NotAuthorizedException("FAILED\n");
}
return request;
}
...
}
So by the time I get to this point, I've authenticated the user. Now how I can get the username?
#GET
#Path("some_kind_of_report_or_something")
#Produces(MediaType.TEXT_PLAIN)
public String fetchAReportOrSomething() {
// At this point, I know that the user has provided good credentials,
// now I need get the user's username as a String
String username = ???;
}
I suppose I could use HttpContext.getRequest() and do the same thing as in the AuthFilter (I'd move that username/password extraction logic to its own method). In the filter, can I somehow store the extracted username somewhere in the request object so it gets passed on to this handler?
(By the way, is there a better way to extract the username and password than what I've done in the filter? If so, let me know in a comment.)
This blog entry should enlighten you:
http://plaincode.blogspot.pt/2011/07/openid-authentication-example-in-jersey.html
Take a look how it's done in a working application: www.s3auth.com. The source code is available at github. As you can see on the site, facebook and google authentication mechanisms are used. The application is using JAX-RS/Jersey.
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