OpenID
Introduction
This extension encapsulates the OpenId4Java framework to provide simple OpenID 2.0 access in Restlet. Using the extension a developer can create both OpenId Providers as well as doing remote authentication of users.
The example described in this howto can be downloaded here.
Description
Most people will use this extension to do authentication. The extension contains 2 classes for doing this.
OpenIdVerifier extends Verifier to do OpenId based authentication (or verification of a user). Unlike most verifiers the OpenIdVerifier is supposed to be invoked twice since it expects a callback from an OpenIdProvider with the result of an authentication request. It follows this basic logic
- Create a authentication request (and setting a callback back to "this" resource ref) to a given provider. Do a temporary redirect to the provider . In this step the verifier will return Verifier.RESULT_MISSING
- Get a response from the OpenId provider. Verifies the response and if it is valid it will set the User object in the request and return Verifier.RESULT_VALID
- In case of failure (either not finding any OpenIdProvider or authentication failure) it will return Verifier.RESULT_INVALID
RedirectAuthenticator extends Authenticator to allow for remote style authentication, for instance, as done by the OpenIdVerifier. This Authenticator follow the following logic
- Check if a session cookie has been set. If so set the request's User identifier and return true
- Check if a cookie containing the original request's resourceRef is present, if not set it, and call its verifier.verify
- If the verifier returns Verifier.RESULT_VALID it will set a session cookie containing the user identifier, remove the orignal resourceRef cookie and do a permanent redirection to the original resourceRef and return true. In this process it will also call the handleUser method - useful when overriding the RedirectAuthenticator.
- If the verifier returns Verifier.RESULT_INVALID it will clear all all cookies and call the forbid method. The default behavior of forbid is to do a redirect to an error resource (if specified) and put the original resource ref in a request attribute (this attribute will be accessible in case of e.g. riap).
- if the verifier returns any other result the Authenticator does nothing (it assumes the result to be an intermediate result)
Usage Example
Simple Authentication
In this example we will create a very simple Application that is guarded by a RedirectAutheticator using the OpenIdVerifier. The application will use Google to authenticate its users. In a real world application we would probably save users locally as well to store additional information.
public class MyApplication extends Application{
public synchronized Restlet createInboundRoot(){
Routher root = new Router(getContext());
OpenIdVerifier verifier = new OpenIdVerifier(OpenIdVerifier.PROVIDER_GOOGLE);
verifier.addRequiredAttribute(AttributeExchange.EMAIL);
Authenticator guard = new RedirectAuthenticator(getContext(), verifier, null);
guard.setNext(root);
return guard;
}
}
This simple setup will protect your entire application using Google Id's. A couple of other things to note.
- An OpenIdVerifer can search for its openId provider in several ways (query parameter, request attribute or default provider). In this case we set a default provider which means that Verifier will always use that provider if it doesn't find another provider. Furthermore, we are also asking the Verifier to request a person's email in the authentication request.
- For the RedirectAuthentictor we are telling it to use our openIdVerifier. We are also telling it to not redirect to any resource in case of an invalid verification (the null parameter). OBSERVE that the verifier will set the email attribute in the User object but the RedirectAuthenticator will not save that information (only the user identifier). It is left to developer to override handleUser if such information is to be stored locally
Setting up an OpenId Provider
Next we will set up our Restlet Application to serve as an OpenId Provider - meaning that Relay Parties can use our server to route authentication requests, i.e. in the same way we used Google in the previous example. The main tasks is to set up proper user interaction and handling XRDS - a Relay Party will at various points verify the Provider by requesting specific XRDS documents (for more information go to the OpenId specification). Lets start with the overall structure
public class ProviderApplication extends Application{
ServerManager sm = new ServerManager();
Provider p = new Provider();
sm.setOPEndpointUrl("http://localhost:8095/openid");
sm.setEnforceRpId(true); //verify relay party
getContext().getAttributes().put("server_manager", sm);
getContext().getAttributes().put("openid_provider", p);
Router router = new Router(getContext());
//setting up the authentication page
ChallengeAuthenticator au = new ChallengeAuthenticator(getContext(),
ChallengeScheme.HTTP_BASIC, "OpenId Authorize");
au.setVerifier(new MyVerifier());
au.setNext(AuthorizeOpenIdRequest.class);
router.attach("/auth", au);
//setting up our OpenId Provider Resources
router.attach("/users/{uname}", XRDSResource.class);
router.attachDefault(ProviderResource.class);
}
The Application is fairly straight forward. We first create our application wide attributes and set our openid endpoint. Next we attach our authentication request page that is guarded by a simple ChallangeAuthenticator (OBSERVE that it should be changed in production). Last, we attach our XRDSResource that will return the correct server signon document that the Relay Party uses to verify the openid claimed identifier. We also attach our ProviderResource that will handle all incoming OpenId requests. Lets start to define ProviderResource.
public class ProviderResource extends ServerResource {
@Post("form")
public Representation represent(Representation input) throws Exception{
Form f = new Form(input);
return handle(new ParameterList(f.getValuesMap()));
}
@Get("form")
public Representation represent() throws Exception{
return handle(new ParameterList(getQuery().getValuesMap()));
}
We are treating both Post and Get's in the same way. Just extract any OpenId parameters and call handle
public Representation handle(ParameterList pl) throws Exception{
Provider p = (Provider) getContext().getAttributes().get("openid_provider");
ServerManager sm = (ServerManager) getContext().getAttributes().get("server_manager");
String sessionId = getQuery().getFirstValue("oidsession");
UserSession us = null;
if(sessionId != null){
getLogger().info("Existing session...should use");
us = p.getSession(sessionId);
if(us != null && us.getUser() != null){
if(us.getUser().getApproved()){
getLogger().info("populating openid info");
populateUser(us, p);
}
else if(!us.getUser().getApproved()){
getLogger().info("user rejected request...do nothing");
}
}
System.out.println(us.getParameterList());
}
ProviderResult plres = null;
try{
plres = p.processOPRequest(sm, us == null ? pl : null, getRequest(), getResponse(), us);
}
catch(Exception e){ //assume discovery??
return XRDS.serverXrds("http://localhost:8095/openid", true);
}
if(plres == null){
getLogger().info("Could not process");
}
else if(plres.ret == ProviderResult.OPR.GET_USER){
getLogger().info("Needs to interact with user");
//interact(p.getSession(plres.text), sm, p);
Reference ref = new Reference("http://localhost:8095/openid/auth");
ref.addQueryParameter("oidsession", plres.text);
getResponse().redirectSeeOther(ref);
return null;
}
else if(plres.text.equals("")){
getLogger().info("everything ok!");
return new EmptyRepresentation();
}
else{
getLogger().info("returning: "+plres.text);
return new StringRepresentation(plres.text);
}
return null;
}
The handle method does several things. The basic logic is the following
- Check for the existence of an oidsession parameter. If that exists it means that we have interacted with the user and are ready to send back a response to the relay party
- Secondly we will call provider.processRequest(...). Based on the result of that call we will either redirect the response to a page where a user can approve or reject the relay party request. Otherwise we just return
- If the call to processRequest fails we assume that the relay party is doing XRDS lookup so we simply return an server XRDS document specifying our OpenIdProvider endpoint (http://localhost:8095/openid
The final thing a Relay Part does is to verify that the OpenId Provider does indeed allow the returned ClaimedIdentifier. The way our sample application is set up the OpenId Provider will return identifiers on the form http://localhost:8095/openid/users/uname. Thus the Replay Party will call that URL and expects a XRDS document to be returned specifying the server signon URL. The code is straightforward
public class XRDSResource extends ServerResource {
@Get
public Representation represent() throws Exception{
String uname = (String) getRequest().getAttributes().get("uname");
getLogger().info(uname);
return XRDS.serverSignon("http://localhost:8095/openid");
}
}
You can download the full example here (application/zip, 95.3 kB, info) (it also contains some OAuth code). Observe that the example is built for Restlet 2.2 development version so you need to change the pom accordingly. There are also a couple of new functions in org.restlet.ext.openid.internal.Provider, org.restlet.ext.openid.AttributeExchange and org.restlet.ext.openid.RedirectAuthenticator that the sample uses. The simplest fix is to look at the the implementation of those functions and implement them yourself our use the 2.2 branch.
Once you have compiled and started the Application you can do the following to test the OpenId functionality
- goto http://localhost:8095/user. Here you can create a user
- goto http://loclahost:8095/update. Update your user with some information
- goto http://localhost:8095/openid/login. Do an openid login that request information about given name and email. If all goes well the information should be returned and presented
- goto http://localhost:8095/update. Change something, e.g. your email
- goto http://localhost:8095/openid/login. The RedirectAuthenticator has cached the login info so you should see the old info. Click "Clear Cache Cookie"
- reload http://localhost:8095/openid/login. This time it should once again bring up the authentication page. After you accept the new email should be visible
Building with Maven
<repositories>
<repository>
<id>restlet</id>
<name>Restlet public</name>
<url>http://maven.restlet.org/</url>
</repository>
</repositories>
<properties>
<restlet.version>2.1-M5</restlet.version>
<restlet.groupId>org.restlet.jee</restlet.groupId>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependency>
<groupId>${restlet.groupId}</groupId>
<artifactId>org.restlet.ext.openid</artifactId>
<version>${restlet.version}</version>
<scope>compile</scope>
</dependency>
For build 2.1 M5 there is a maven dependency missing. When using this version make sure to include the following dependency
<dependency> <groupId>commons-httpclient</groupId> <artifactId>commons-httpclient</artifactId> <version>[3.0,)</version> </dependency>


http://code.google.com/p/openid4java/issues/detail?id=166
and when i set up version in maven projet to use http client 4.1.1 instead of 4.1.2 it start working again.
We had reverted to 4.1.1 already due to this exact issue, but there was a bug in the library.xml/pom.xml file. Thanks, this is now fixed in SVN trunk.
Best regards,
Jerome
1) OpenIdVerifier verifer = new OpenIdVerifer(OpenIdVerifier.PROVIDER_GOOGLE);
"new OpenIdVerifer" should be "new OpenIdVerifier" notice the missing i. :)
2) On that same line "verifer" should be "verifier" again, missing i. (someone has a lazy left middle finger ;))
3) Authenticator guard = new RedirectAuthenticator(getContext(), openIdVerifier, null);
The var "openIdVerifier" should be just "verifier"
4)According to the Javadocs "OpenIdVerifier.AX.email" should be AttributeExchange.EMAIL
java.lang.NoClassDefFoundError: javax.net.ssl.KeyManagerFactory is a restricted class. Please see the Google App Engine developer's guide for more details.
I am going to keep working through the sample code, but was wondering if I was doing something obviously wrong. I am running Restlet 2.2 GAE edition.
(I realize I am not providing much detail, that is because I recognize I have much work to do on my side - just posting this in case my error is obvious to those more experienced).
Thanks!
RB
Issue 160: OpenId4Java no longer works on App Engine after revision 658 (http://code.google.com/p/openid4java/issues/detail?id=160). Guess that is the problem. If anyone knows relatively straightforward workarounds, please let me know. (My programming skills have some pretty significant limits :) ).