Monday, March 28, 2011

OAM and ADF applications with Anonymous access

This post is part of a larger series on Oracle Access Manager 11g called Oracle Access Manager Academy. An index to the entire series with links to each of the separate posts is available.

In the last post here we pointed to Olaf's blog on integrating your ADF app with OAM. That post covers taking an ADF app that was protected with Forms auth and switching it over to be protected in its entirety by OAM. In other words to access any part of the app you have to log into OAM. Once you're logged in to OAM you access the app and OAM sends your identity down.

But what if you want to let users access part of the app anonymously, but require them to log in to access some of the apps features? I don't know what anyone else calls this sort of flow, but I call it the shopping cart model (browse around tossing stuff in your card, then sign in to check out).

I recently wired this flow up in an ADF application (I started with Olaf's sample app). All in all it was pretty easy once I got everything setup, but I couldn't find any docs spelling the process out step by step. So without further ado...

This is what Olaf's app looks like before you login:

When you click on the Action menu there's one option - "Login". If you look at the source you'll see that this triggers a pop-up for you to enter your credentials:
                    <af:menuBar id="pt_mb1">
                      <af:menu text="Action" id="pt_m2"
                               textAndAccessKey="&Action">
                        <af:commandMenuItem text="Login" id="pt_cmi2"
                                            rendered="#{!securityContext.authenticated}"
                                            >
                          <af:showPopupBehavior popupId="loginPopup"
                                                triggerType="action"/>                                                
                        </af:commandMenuItem>
                        <af:commandMenuItem text="Logout" id="pt_cmi3" action="#{login.performLogout}"
                                            rendered="#{securityContext.authenticated}"/>
                      </af:menu>

We're going to get rid of that pop-up login box and switch over to an OAM login page. To do that there's only three simple things that need to be changed:

  1. Configure OAM to protect the ADF authentication URL but leave everything else unprotected
  2. Configure the OAM Identity Asserter
  3. Change the web.xml to work with OAM
  4. Change the login menu option to force you through the ADF authentication URL which is protected by OAM

Click through for a step-by-step Step 1: Configure OAM to protect the ADF authentication URL but leave everything else unprotected

This is the most involved step because you're defining all of the resources, selecting an authentication scheme and setting up authorization. In my case I needed three resources:

I probably didn't need the "/accessdemo/" resource, but I included it for completeness so that if you try to go to "/accessdemo/" directly you'll be allowed access before being redirected to the app's home page. The second resource "/accessdemo/.../*" covers all of the resources I'll be accessing in the application. Because this is an ADF app the browser is going to be accessing resources like /accessdemo/faces/welcome.jspx"; the ellipses (...) says 'anything between the slashes including more slashes', so "/.../*" matches basically any resource. The third and most interesting resource is the "/accessdemo/adfAuthentication*"; you'll note there is no ellipsis there - because adfAuthentication is always located directly inside the web app's root.

Once you've created the resources you need to add them to the authentication and authorization policies. In my case I'm doing something very, very simple. I want to add the first two resources to the "Public Resource Policy" for Authentication and Authorization. The adfAuthentication URL gets added to the "Protected Resource Policy".

Step 2: Configure the OAM Identity Asserter (if you haven't already)

You can find instructions on this step in the previous post so I won't go into the details here, but here's a screen shot of what your config should look like:

Step 3: Change the web.xml to work with OAM In Olaf's app he had this:

    <auth-method>CLIENT-CERT,FORM</auth-method>
    <form-login-config>
      <form-login-page>/login.html</form-login-page>
      <form-error-page>/error.html</form-error-page>
    </form-login-config>

This will almost certainly work (he had CLIENT-CERT), but just to be sure it's always better to just specify only the CLIENT-CERT authentication method. So my web.xml now says:

    <auth-method>CLIENT-CERT</auth-method>
(note that I deleted the form-login-config section since it's not needed with CLIENT-CERT.

Step 4: Change the login menu option to force you through the ADF authentication URL which is protected by OAM

There are two steps here - replacing the menu option and adding code to kick you into the process.

Step 4.1 I replaced the Login menu option above with a new chunk of code:

                        <af:commandMenuItem text="Login via OAM" id="pt_cmi4"
                                            rendered="#{!securityContext.authenticated}"
                                            action="#{login.performOAMLogin}"
                                            />
This makes the new menu option appear (note that I left the old "Login" option there for this screen shot):

Step 4.2 Then we need to add a small chunk of code to the bean that gets called when you invoke login.performOAMLogin from the JSF page:

  public String performOAMLogin() {
    HttpServletRequest request = JSFUtils.getHttpServletRequest();
    HttpServletResponse response = JSFUtils.getHttpServletResponse();

    FacesContext ctx = JSFUtils.getFacesContext();
    ctx.responseComplete();

    String loginUrl =
      request.getContextPath() + "/adfAuthentication?success_url=/faces" + JSFUtils.getRootViewId();

    try {
      response.sendRedirect( loginUrl );
    } catch (IOException ioe) {
      reportUnexpectedLoginError("IOException", ioe);
    }

    return null;    
  }

What that code does is tells JSF that it should stop processing the page and redirect the user to "/adfAuthentication" with the query string set to "?success_url=/faces/" plus the current view. In other words go over to adfAuthentication (which is protected by OAM) and then once you get there come back here. /adfAuthentication is, as the name implies, part of the ADF framework. We can't do an internal "forward" of the request because we need the web browser to make a request directly so that OAM (which is running in the web server) sees it and forces the user to login.

That's all there is to it.

Now when the user clicks the menu button they'll make a request to a protected resource, will see a login page. After they login the OAM Identity Asserter will re-establish their identity to the container before they reach /adfAuthentication. The /adfAuthentication URL will do some work and then redirect them back to the JSF view they were looking at when they decided to login.

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.