Write for OTN
Earn money and promote your technical skills by writing a technical article for Oracle Technology Network.
Learn more
Stay Connected
OTN Architect Community
OTN ArchBeat Blog Facebook Twitter YouTube Podcast Icon

Multi-Factor Authentication in Oracle WebLogic

Shailesh K. Mishra

Using multi-factor authentication to protect web applications deployed on Oracle WebLogic.

October 2013

Downloads
download-icon13-1Oracle WebLogic Server

Introduction

In multi factor-authentication, user genuineness is validated based on multiple factors, not merely the traditional mechanism of user name/password. For example, when a bank customer visits an ATM, one authentication factor is the physical ATM card ("something the user has"); the second factor is the customer’s account PIN ("something the user knows"). Without verification of both of these factors, authentication fails.

The multi-factor authentication concept can also be applied to web applications deployed on Oracle WebLogic Server, as the following sections detail.

Note: This article assumes that reader has good understanding of Oracle WebLogic security concepts and authentication mechanisms. Oracle WebLogic version 10.3.5 was used for this article.

Understanding the Oracle WebLogic Authentication Process

In the interests of concision, this article describes only weblogic.security.spi.ChallengeIdentityAsserterV2 and weblogic.security.spi.ServletAuthenticationFilter interfaces. For further details on interfaces and for lifecycle details about authentication providers in Oracle WebLogic, please see Developing Security Providers for Oracle WebLogic Server in the References section.

weblogic.security.spi.ChallengeIdentityAsserterV2:

This interface allows Identity Assertion providers to support such authentication protocols as Microsoft's Windows NT Challenge/Response (NTLM), Simple and Protected GSS-API Negotiation Mechanism (SPNEGO), and other challenge/response authentication mechanisms. Its two methods -- assertChallengeIdentity and continueChallengeIdentity -- are of interest to this article. The first method uses the supplied token to establish client identity, possibly with multiple challenges; the second is used to continue establishing the client identity until the process is complete.

weblogic.security.spi.ServletAuthenticationFilter:

This interface is used to signal that the Servlet container should include this authentication provider’s authentication filters during the authentication process. It defines one method, getServletAuthenticationFilters, which returns an ordered list of javax.servlet.Filters that will be executed during the authentication process of the Servlet container.

Implementing Multi-Factor Authentication for a Web Application

We need to create the following artifacts:

  • A web application to collect data used for multi-factor authentication
  • A custom authentication provider that will implement: weblogic.security.spi.ChallengeIdentityAsserterV2 and weblogic.security.spi.ServletAuthenticationFilter interfaces.

Create web application

For demonstration, this web application contains only two jsp pages: the first collects the user name/password and second collects a code, sent to the user’s cell phone. Below are snippets of these JSP pages.

JSP page to collect user name/password:

<%@ page language="java" contentType="text/html;charset=UTF-8"%>
 <h1>Multi Factor Login</h1> 
 <form method="post" action="login"> 
 <input type="hidden" name="originalRequest" value="<%=request.getParameter("originalRequest") 
%>"/>
<br> 
   <input type="text" name="j_username" value=""/><br> 
   <input type="password" name="j_password" value=""/><br> 
   <input type="submit" name="submit" title="submit" value="submit"> 
</form>

JSP page for collection code sent to user’s cell phone:

<%@ page language="java" contentType="text/html;charset=ISO-8859-1" 
pageEncoding="ISO-8859-1"%> 
<h1>Multi Factor Login</h1> 
<form method="post" action="login"> 
   <input type="text" name="OTP" value=""/><br> 
   <input type="submit" name="submit" title="submit" value="submit"> 
</form>

web.xml snippet of this web app

Please note the protected url "/login" used to submit user name/password and OTP in the jsp pages above.

  <security-constraint>
    <web-resource-collection>
      <web-resource-name>login</web-resource-name>
      <url-pattern>/login</url-pattern>
    </web-resource-collection>
    <auth-constraint>
      <role-name>secured</role-name>
    </auth-constraint>
    </security-constraint>
    <security-role>
      <role-name>authenticatedusers</role-name>
  </security-role>

Create Custom Authentication Provider

Below is the sample code for a custom authentication provider that was used to test this concept. To improve readability, some code has been removed.

MultiFactorIdentityAsserterProviderImpl.java

public final class MultiFactorIdentityAsserterProviderImpl implements
  AuthenticationProviderV2, ChallengeIdentityAsserterV2,
  ServletAuthenticationFilter {	
		
......................................................................

  public javax.servlet.Filter[] getServletAuthenticationFilters() {
    System.out.println("You are getting my filters....");
    return new Filter[] { new MultiFactorFilter(this.config), new SampleFilter() };
  }
  
  @Override
  public ProviderChallengeContext assertChallengeIdentity(String token,
	Object value, ContextHandler ctx) throws IdentityAssertionException {
      System.out.println("AssertChallengeIdentity");
      System.out.println("AssertChallengeContext " + token + " " + value
        + " " + Arrays.asList(ctx.getNames()));
		MultiFactorChallengeContext challengeCtx = 
			new MultiFactorChallengeContext(this.config);

		try {
			challengeCtx.processRequest(ctx);
		} catch (Exception e) {
			throw new IdentityAssertionException(e.getMessage());
		}

		if (challengeCtx.hasChallengeIdentityCompleted()) {

			complete(challengeCtx,ctx);

		}

		return challengeCtx;
	}

  @Override
  public void continueChallengeIdentity(
			ProviderChallengeContext providerChallengeContext, String arg1,
			Object arg2, ContextHandler ctx) throws IdentityAssertionException {
		// TODO Auto-generated method stub
		System.out.println("Continuing....");
		System.out.println("AssertChallengeContext " + arg1 + " " + arg2 + " "
				+ Arrays.asList(ctx.getNames()));

		MultiFactorChallengeContext challengeCtx = 
			(MultiFactorChallengeContext) providerChallengeContext;

		try {
			challengeCtx.processRequest(ctx);
		} catch (Exception e) {
			throw new IdentityAssertionException(e.getMessage());
		}}}

MultiFactorAuthenticationFilter.java

public class MultiFactorAuthenticationFilter implements Filter {

	private String [] protectedPaths;
	private String webAppPath;
	
	public MultiFactorAuthenticationFilter(MultiFactorAuthenticationProviderMBean config){
		// TODO Auto-generated constructor stub
		this.protectedPaths = config.getProtectedPaths();
		this.webAppPath = config.getMultiFactorWebAppPath();
		System.out.println("Filtering: "+Arrays.asList(this.protectedPaths));
	}

	@Override
	public void destroy() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void doFilter(ServletRequest req, ServletResponse resp,
			FilterChain chain) throws IOException, ServletException {
		// TODO Auto-generated method stub
		
		
		HttpServletRequest hReq = (HttpServletRequest)req;
		HttpServletResponse hResp = ((HttpServletResponse)resp);
		
		System.out.println("In MultiFactorFilter requestURI="+hReq.getRequestURI());
		
		if (!hReq.getRequestURI().startsWith(this.webAppPath)) {
		
			boolean isProtectedPath = false;
			
			for (int i=0; i<this.protectedPaths.length; i++) {
			
		if(hReq.getRequestURI().startsWith(this.protectedPaths[i])) {
					isProtectedPath = true;
					break;
				}
				
				
			}
			
			if (!isProtectedPath) {
				chain.doFilter(req, resp);
				return;
			}
		
		//This is not the /webAppPath path, so the user is accessing
		//a protected resource in a protected application.
		//Redirect the user over to the /webAppPath application for 
            //multifactor authentication.
			hResp.sendRedirect(this.webAppPath+"/?originalRequest="
				+URLEncoder.encode(hReq.getRequestURI()));
			return;
			
		}
		
		
		if (hReq.getMethod().equalsIgnoreCase("HEAD") ||
hReq.getHeader("Accept").contains("application/xrds+xml")) {
			
			System.out.println("Ignoring....REALM DISCOVERY");
			hResp.setStatus(HttpServletResponse.SC_OK);
			return;
			
		}		
		
		
Authentication auth = new weblogic.security.services.Authentication();
		
		try {
			
			HttpSession session = hReq.getSession(true);
			
			AppChallengeContext acc = 
				(AppChallengeContext)session.getAttribute("ChallengeCtx");
			
MultiFactorAuthenticationChallengeContext ctx = 
	new MultiFactorAuthenticationChallengeContext (req,resp);
	if (acc==null) {			
				
			acc = auth.assertChallengeIdentity("multi","multi",ctx);
				
		//This session is for the target application
		session.setAttribute("ChallengeCtx", acc);
	} else {
				
		auth.continueChallengeIdentity(acc, "multi", "multi",ctx);
	}
			
	if (acc.hasChallengeIdentityCompleted()) {
				
	String originalRequest = (String)hReq.getAttribute("originalRequest");
				
			System.out.println("Redirecting to: "+originalRequest);
				
		//Done -> if they don't get access, they'll need to log in again
		session.removeAttribute("ChallengeCtx");
				
           Subject subject = (Subject)session.getAttribute("subject");
		ServletAuthentication.runAs(subject, hReq);
				
           
	} else {
			
		Object challengeToken = acc.getChallengeToken();			
			
	if (challengeToken == null) {
			//Redirect to Login
			throw new ServletException("Challenge Token is NULL");
					
	} else {
					
		hResp.sendRedirect(this.webAppPath+"/otp.jsp");
			
	  }
				
				
				
	    }
			
	} catch (Exception e) {
			e.printStackTrace();
			throw new ServletException(e.getMessage());
	 }
		
		
	}

	@Override
	public void init(FilterConfig arg0) throws ServletException {
		// TODO Auto-generated method stub
		
		System.out.println("Initializing.....");
		
	}

}

MultiFactorAuthenticationChallengeContext.java

public class MultiFactorAuthenticationChallengeContext implements ProviderChallengeContext {

	private boolean completed = false;
	private Object challengeToken = null;
	private CallbackHandler handler = null;

	private Subject discovered = null;
	
	private String originalRequest;
	
	private String webAppPath = null;
	
	public MultiFactorAuthenticationChallengeContext
		(MultiFactorAuthenticationProviderMBean config) {
		super();
		try {
					
			this.webAppPath = config.getMultiFactorWebAppPath();
			
		} catch (Exception e) {
			throw new SecurityException(e.getMessage());
		}
	}
................................................................................

	@Override
	public boolean hasChallengeIdentityCompleted() {
		// TODO Auto-generated method stub
		return completed;
	}

	
void processRequest(ContextHandler ctx) throws Exception {

	HttpServletRequest req = (HttpServletRequest) ctx.getValue("req");
		HttpSession session = req.getSession(true);

		if (session.getAttribute("subject") == null) {

			String userName = req.getParameter("j_username");
			String password = req.getParameter("j_password");

			if (userName == null || password == null) {
				
throw new Exception("No user name and password in request");
	} else {

		this.originalRequest = req.getParameter("originalRequest");
		System.out.println("Saving the original request: "+this.originalRequest);
		//validate the user name password
		Authentication auth = new weblogic.security.services.Authentication();
		
		try{
		Subject s = auth.login(new MyCallBackHandler(userName, password));
		session.setAttribute("subject", s);
		}catch(LoginException le){
			throw new Exception("Invalid user name/password", le);
		}
		this.generateOTP(req);

	}

    } else {
    
    	this.validateResponse(req);
    
    }

	}

	private void validateResponse(HttpServletRequest req)
			throws Exception {
		// TODO Auto-generated method stub

		System.out.println("Getting Validated....");
        String token = req.getParameter("OTP");
        if(token == null || token.length() == 0 || !this.challengeToken.equals(token)){
        	throw new Exception("Invalid OTP");
        }
        
        
        
		this.completed = true;
		if (this.originalRequest!=null) {
		//Set it as an attribute on the request, so the filter can use it
		req.setAttribute("originalRequest", this.originalRequest);
	} else {
		throw new Exception("Couldn't locate original request in ChallengeContext");
	}
	
	//return verified;  // success

	}

	private void generateOTP(HttpServletRequest req)
			throws Exception {
    // TODO Auto-generated method stub
       /*
        *this is for demo only. Write your custom code to 
		*generate a token and send it to user
        *using a mechanism which works for your env.
this.challengeToken = "0123456789";

	}

}

Note the methods processRequest and generateOTP in the MultifactorAuthenticationContext class. The processRequest method is responsible for validating user name/password using one of the WebLogic APIs. Once user name and password are valid, this method calls the generateOTP method to generate an OTP and send it to the user. The user is then redirected to the otp.jsp page of the web application, as mentioned above, by the MultiFactorAuthenticationFilter class. The user enters otp, which is validated by the validateResponse method of the MultifactorAuthenticationContext class. At this point, overall authentication is successful and the user is allowed access to a protected web application.

Custom Authentication Provider Configuration

It’s important to note that it may not be desirable or appropriate to protect all your web apps using this mechanism. For example, we should not use this protection mechanism to protect an Oracle WebLogic administrative console, because any code error could prevent access to the administrative console. For more details on configuring the authentication provider, please see Developing Security Providers for Oracle WebLogic Server in the References section .

Below are the important mbean attributes for this custom authentication provider:

<MBeanAttribute  
  Name 		= "MultiFactorWebAppPath"
  Type 		= "java.lang.String"
  Default 	= ""/multifactor""
  Description = "The Path (Context Root) of the custom Web App 
  which is responsible for collecting user's credentials and OTP"
 /> 
<MBeanAttribute  
  Name 		= "ProtectedPaths"
  Type 		= "java.lang.String[]"
  Default 	= "new String[] { "/App" }"
  Description = "The paths of web apps that are protected by 
  multi factor authentication"
 />

Conclusion

This article has shown how multi-factor authentication (user name plus password and OTP combination) can be achieved to protect web applications deployed in the WebLogic server. It has also shown that only a set of web applications can be protected by multi-factor authentication, and that the Oracle WebLogic administrative console should not be protected by the mechanism described in this article.

The author would like to thank Yi Wang for reviewing and providing feedback for this article.

References

  1. Oracle WebLogic Server Sample Code (look for OpenId SSO for WLS)
  2. Developing Security Providers for Oracle WebLogic Server

About the Author

Shailesh K. Mishra is part of the Oracle Identity Manager team. He earned his B. Tech. from IIT BHU. He explores middleware performance and security on his free time.