Programmatic Authentication for Oracle ADF Business Components
Monday, 20. November 2006, 16:51:24
The ADF Business Components make creating and using the model layer extremely fast and easy to manage. They have several very useful features including history columns. History columns are extra auditing fields in an entity object that track changes. The best feature of this is that ADF BC is able to use the session user to populate the created by and modified by columns. However, this only works if you use declarative JAZN authentication (configured in the web.xml deployment descriptor). If you want to programmatically authenticate your users, or you use a single sign-on that passes user information to your application after authentication, the user's details are not accesible in the HTTP request and therefore ADF BC is not able to find the username.
The problem is that when an Application Module is initiated there is no authenticated user in the HTTP request. After the initiation, it doesn't matter whether the user is in the session or not, however it would also be useful to be able to use the getRemoteUser and isUserInRole methods, which will fail unless using the declarative authentication method.
The solution is quite simple. When you have authenticated the user, or retrieved the user information from an SSO implementation, put the username and roles into the session. Then create a new servlet filter for all pages (URL pattern /*), and make sure that it is called before the adfBindings and adfFaces filters, but after any filter that performs or requests authentication.
Sample web.xml
Here I've called mine AuthFilter and the SecurityFilter requests authentication from an SSO provider, receives the username and roles and stores them in the session with the attribute names security.auth.user and security.auth.roles respectively. The AuthFilter looks like this:
The main work of the filter is done through the AuthRequestWrapper, which extends javax.servlet.http.HttpServletRequestWrapper and implements the getRemoteUser, isUserInRole and getUserPrincipal methods.
AuthUserPrincipal is a small class that implements java.security.Principal to pass the username to ADF BC.
And that’s it. ADF BC finds the username in the request wrapper and initiates an Application Module instance with it, the history columns get populated correctly, and roles are accessible in the request for authorisation.
If, like me, you need to pass roles from the SSO to your application, I will write another post about how to modify CAS (Central Authentication Service) to do this.
Edit 5th December 2006:
Due to a request by Rashid (see comments), I'm including the SecurityFilter in this post.
My SecurityFilter class is loosely based on the CASFilter class that comes with the CAS Java client. If you take out which authentication method it uses, the part that allows AuthFilter to work is the same.
Here I've highlighted the parts of the code that are specific to my AuthFilter implementation.
The main bit is where you add the session variables, the rest is how you get those values in the first place, which depends on your SSO or custom authentication implementation.
The problem is that when an Application Module is initiated there is no authenticated user in the HTTP request. After the initiation, it doesn't matter whether the user is in the session or not, however it would also be useful to be able to use the getRemoteUser and isUserInRole methods, which will fail unless using the declarative authentication method.
The solution is quite simple. When you have authenticated the user, or retrieved the user information from an SSO implementation, put the username and roles into the session. Then create a new servlet filter for all pages (URL pattern /*), and make sure that it is called before the adfBindings and adfFaces filters, but after any filter that performs or requests authentication.
Sample web.xml
... <filter-mapping> <filter-name>SecurityFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>AuthFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>adfBindings</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>adfBindings</filter-name> <url-pattern>*.jspx</url-pattern> </filter-mapping> <filter-mapping> <filter-name>adfFaces</filter-name> <url-pattern>*.jsp</url-pattern> </filter-mapping> <filter-mapping> <filter-name>adfFaces</filter-name> <url-pattern>*.jspx</url-pattern> </filter-mapping> ...
Here I've called mine AuthFilter and the SecurityFilter requests authentication from an SSO provider, receives the username and roles and stores them in the session with the attribute names security.auth.user and security.auth.roles respectively. The AuthFilter looks like this:
import java.io.IOException;
import java.util.Set;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class AuthFilter implements Filter
{
private static final String SESSION_ATTR_USER =
"security.auth.user";
private static final String SESSION_ATTR_ROLES =
"security.auth.roles";
public void doFilter(ServletRequest request,
ServletResponse response, FilterChain fc)
throws IOException, ServletException
{
if (ServletRequest instanceof HttpServletRequest)
{
HttpServletRequest httpRequest =
(HttpServletRequest)request;
HttpSession session = httpRequest.getSession();
if (session.getAttribute(SESSION_ATTR_USER) != null
&& session.getAttribute(SESSION_ATTR_ROLES) != null)
{
String username =
(String)session.getAttribute(SESSION_ATTR_USER);
Set roleSet =
(Set)session.getAttribute(SESSION_ATTR_ROLES);
AuthRequestWrapper wrapper =
new AuthRequestWrapper(httpRequest, username, roleSet);
fc.doFilter(wrapper, response);
}
}
fc.doFilter(request, response);
}
public void init(FilterConfig filterConfig)
{
}
public void destroy()
{
}
} The main work of the filter is done through the AuthRequestWrapper, which extends javax.servlet.http.HttpServletRequestWrapper and implements the getRemoteUser, isUserInRole and getUserPrincipal methods.
import java.security.Principal;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
public class AuthRequestWrapper extends HttpServletRequestWrapper
{
private String username;
private Set roleSet;
private Principal principal;
public AuthRequestWrapper(HttpServletRequest request,
String username, Set roleSet)
{
super(request);
this.username = username;
this.roleSet = roleSet;
this.principal = new AuthUserPrincipal(username);
}
public String getRemoteUser()
{
return username;
}
public Principal getUserPrincipal()
{
return principal;
}
public boolean isUserInRole(String roleName)
{
return roleSet.contains(roleName);
}
} AuthUserPrincipal is a small class that implements java.security.Principal to pass the username to ADF BC.
public class AuthUserPrincipal implements java.security.Principal
{
private String username;
public AuthUserPrincipal(String username)
{
this.username = username;
}
public String getName()
{
return username;
}
} And that’s it. ADF BC finds the username in the request wrapper and initiates an Application Module instance with it, the history columns get populated correctly, and roles are accessible in the request for authorisation.
If, like me, you need to pass roles from the SSO to your application, I will write another post about how to modify CAS (Central Authentication Service) to do this.
Edit 5th December 2006:
Due to a request by Rashid (see comments), I'm including the SecurityFilter in this post.
My SecurityFilter class is loosely based on the CASFilter class that comes with the CAS Java client. If you take out which authentication method it uses, the part that allows AuthFilter to work is the same.
Here I've highlighted the parts of the code that are specific to my AuthFilter implementation.
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SecurityFilter implements Filter
{
/* START - AuthFilter specific */
private static final String SESSION_ATTR_USER =
"security.auth.user";
private static final String SESSION_ATTR_ROLES =
"security.auth.roles";
/* END - Auth Filter specific */
private static final String SESSION_ATTR_RECEIPT =
"security.auth.receipt";
private String loginUrl = null;
private String validateUrl = null;
public void init(FilterConfig filterConfig)
throws ServletException
{
loginUrl = filterConfig.getInitParameter("security.auth.loginUrl");
validateUrl = filterConfig.getInitParameter("security.auth.validateUrl");
if (validateUrl == null) throw new ServletException("validateUrl parameter must be set.");
if (loginUrl == null) throw new ServletException("loginUrl parameter must be set.");
}
public void destroy()
{
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain fc)
throws IOException, ServletException
{
if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse))
{
throw new ServletException("SecurityFilter only accepts HTTP requests");
}
HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
HttpSession session = httpRequest.getSession();
/* START - CAS specific */
AuthReceipt receipt = (AuthReceipt)session.getAttribute(Constants.SESSION_ATTR_RECEIPT);
if (receipt != null)
{
fc.doFilter(request, response);
return;
}
String ticket = request.getParameter("ticket");
if (ticket == null || ticket.equals(""))
{
redirectToSSO(httpRequest, httpResponse);
return;
}
try
{
receipt = getAuthReceipt(httpRequest);
}
catch (AuthenticationException e)
{
throw new ServletException(e);
}
/* END - CAS specific
if (session != null)
{
/* START - AuthFilter specific */
session.setAttribute(SESSION_ATTR_USER, receipt.getUsername());
session.setAttribute(SESSION_ATTR_ROLES, receipt.getAcl());
/* END - AuthFilter specific */
session.setAttribute(SESSION_ATTR_RECEIPT, receipt);
}
fc.doFilter(request, response);
}
/* REST - CAS specific */
private AuthReceipt getAuthReceipt(HttpServletRequest request)
throws ServletException, AuthenticationException
{
TicketValidator tv = null;
tv = new TicketValidator();
tv.setValidateUrl(validateUrl);
tv.setServiceTicket(request.getParameter("ticket"));
tv.setService(getService(request));
return AuthReceipt.getReceipt(tv);
}
private void redirectToSSO(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
String ssoLoginUrl = loginUrl + "?service=" + getService(request);
response.sendRedirect(ssoLoginUrl);
}
private String getService(HttpServletRequest request)
{
String serverName = request.getLocalName() + ":" + request.getLocalPort();
String service = "http://" + serverName + request.getRequestURI();
if (request.getQueryString() != null)
{
String queryString = request.getQueryString();
int ticketIndex = queryString.indexOf("&ticket=");
if (ticketIndex == -1)
{
service += "?" + queryString;
}
else
{
service += "?" + queryString.substring(0, ticketIndex);
}
}
return service;
}
}The main bit is where you add the session variables, the rest is how you get those values in the first place, which depends on your SSO or custom authentication implementation.
By RashidBhatti, # 1. December 2006, 16:12:53
Thanks for your comment.
I've updated the post with the SecurityFilter - I hope it helps. As for the AuthUserPrincipal, I simply put it in the same package as the filters (which for me was myapp.controller.security.client).
To get the username, you call the getRemoteUser() method of the HTTP request.
Example JSP
That will work in a JSP and in a servlet. If you need to access it in other places, could you tell me where and I can find out if it's possible?
Regards,
DominionSpy
By dominionspy, # 5. December 2006, 15:40:22
Mathew,
I am not sure if you article applies to my issue now or not. It seems to kind of fit, and kind of not.
We have written an ADF BC/JSF app using JDev 10.1.3.3.0. We are using SSO to authenticate our application.
Here is what I am observing:
1. Each time a user logs into this application, request.getRemoteUser() is populated and available (I get to this using the Faces Context current instance, External Context).
2. At the application module level, AM.getUserPrincipalName() is available only the first time the end user logs in. If they close their browser, and log in again, getUserPrincipalName() returns null.
Can anybody think why this would be the case?
As a partial solution, I have created an attribute in the app module impl which I write to as soon as a UserInfo session managed bean starts up.
But I need access to the username in AM.prepareSession() because we are setting our user context using VPD.
So I have a problem, you see?
By anonymous user, # 17. June 2008, 21:40:51
We are facing the same problem in getting logged in user id from prepareSession() for VPD. Did you find any solution to this problem?
By anonymous user, # 17. September 2008, 20:21:19