View Source

{toc:type=flat|separator=bracket}

----

h1. Introduction

This builds upon the [Custom Authentication Module] page. This is specifically designed for Verisign Identity Protection. This article provides step-by-step instructions for designing, developing, deploying and testing a *Verisign Identity Protection Authentication Module*.

This article was originally written by [Jeff Bounds|http://wikis.sun.com/display/~jboundssun] (see the [original blog entry|http://blogs.sun.com/bounds/entry/verisign_identity_protection_and_opensso]), it includes screenshots of the module in action).

{note:title=Be Careful}This module does *NOT* implement two-factor authentication since your one time password only counts as "something you have": you would need to add the capacity to check a password / PIN ("something you know") for this module to qualify as two-factor authentication.{note}

h1. This Guide

This guide uses the following software:
* [OpenSSO V4 | https://opensso.dev.java.net]
* [Glassfish V2u2 | http://glassfish.dev.java.net]
* [Netbeans | http://www.netbeans.org]

This article will guide the reader through developing an Authentication Module for OpenSSO (previously Access Manager). The Custom Authentication Module will contact a system on the internet and validate a One Time Password (OTP) based upon the TokenID. If the response consists of "Success", then the user is authenticated. Any other response, or any failure in the attempt to authenticate the user, will result in an exception being thrown. OpenSSO then processes the exception and disallows the authentication.

h1. Architecture

The Custom Authentication Module to be developed in this article will authenticate the credentials supplied by contacting the [Verisign Developer Test Drive Web Service API| http://www.verisign.com/authentication/consumer-authentication/trial/], and will be named *VIPLoginModule*. The TokenID and OTP are provided on the login page. If the Web Service cannot be contacted, the authentication fails, or any error occurs, *VIPLoginModule* throws an *AuthLoginException*. Otherwise, the authentication is successful.

h1. Prerequisites

* OpenSSO installed
* Web container installed
* Policy Agent installed
* Content to be protected

h1. Step-by-Step Guide to Creating a Custom Authentication Module


h2. Step 1 - create module properties file

{code:xml|title=VIPLoginModule.xml|borderStyle=solid}
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE ModuleProperties PUBLIC "=//iPlanet//Authentication Module Properties XML Interface 1.0 DTD//EN"
"jar://com/sun/identity/authentication/Auth_Module_Properties.dtd">
<ModuleProperties moduleName="VIPLoginModule" version="1.0" >
<Callbacks length="3" order="1" timeout="60"
header="In order to authenticate, you must enter your
username, TokenID, and a OTP." >
<NameCallback>
<Prompt>
Username
</Prompt>
</NameCallback>
<NameCallback>
<Prompt>
TokenID
</Prompt>
</NameCallback>
<NameCallback>
<Prompt>
OTP
</Prompt>
</NameCallback>
</Callbacks>
</ModuleProperties>
{code}

h2. Step 2 - create client Class to call WebService

The first piece of Java code is the WebService module, this was created using the JAX-WS capabilities of Netbeans 6.1. Create a Web Service using the WSDL file provided in the VIP Test Drive directory. The validate method is the only method that is implemented:
{code:java|title=VIPWebServiceClient.java}
/**
* VIPWebServiceClient
*
* Contributors: Jeff Bounds
*/
package com.sun;

import com.verisign._2006._08.vipservice.TokenIdType;
import com.verisign._2006._08.vipservice.ValidateType;
import java.math.BigInteger;
import java.util.Map;
import java.util.logging.Logger;
import javax.xml.ws.BindingProvider;

public class VIPWebServiceClient {

/**
* creates an instance from username, password, and success value
* <br>
* @param username username to transmit
* @param password password to transmit
* @param success success value to transmit
*/
private String username;
private String tokenId;
private String otp;
private boolean ok = false;
private Logger logger;

public VIPWebServiceClient(String _username, String _tokenId, String _otp, boolean success) {
this.username = _username; //username is not really needed for validation
this.otp = _otp; //one time password
this.tokenId = _tokenId;
this.ok = success;
}

/**
* creates an instance from username, password, and false success
* <br>
* @param username username to transmit
* @param password password to transmit
*/
public VIPWebServiceClient(String _username, String _tokenId, String _otp) {
this(_username, _tokenId, _otp, false);
}

/**
* @return the success/failure of the transaction
*/
public boolean isOK() {
return ok;
}

public String validateToken() {

//Using Web Service client created by Netbeans 6.1. (JAX-WS)
try {

//Constructors with initial values didn't seem to be created
//Thats why I use the methods below rather than creating the ValidateType
//with values set in constructor.
ValidateType vipVT = new com.verisign._2006._08.vipservice.ValidateType();

//ID should be a unique value, for this sample code I just hardcode it
vipVT.setId("abcd1234");
vipVT.setVersion("2.0");

TokenIdType vipTIT = new TokenIdType();

//TokenID is an input parameter on the login page, you would most likely lookup the users
//tokenid based upon their username and not have them input it on the form
vipTIT.setValue(tokenId);
//tit.setType(EVENT);

vipVT.setTokenId(vipTIT);
vipVT.setOTP(otp);

com.verisign._2006._08.vipservice.VipSoapInterfaceService service1 = new com.verisign._2006._08.vipservice.VipSoapInterfaceService();
com.verisign._2006._08.vipservice.VipSoapInterface port1 = service1.getVipServiceAPI();
// TODO process result here

//The VIP WSDL doesn't fully define the enpoint, instead the VIPDemo
//plugs in the URI for each method.
//We only use validate, so the URI ending is /val/soap
//Wasn't sure how to do this or if this is the proper technique, but it works
BindingProvider bp = (BindingProvider) port1;
Map<String, Object> rc = bp.getRequestContext();
rc.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, "https://pilot-vipservices-auth.verisign.com/val/soap");


com.verisign._2006._08.vipservice.ValidateResponseType resp = port1.validate(vipVT);

BigInteger reason = new BigInteger(resp.getStatus().getReasonCode());

if (reason.intValue() != 0) {
return resp.getStatus().getStatusMessage();
} else {
ok = true;
return "Success";
}
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
{code}
Next is the class that implements the {{java.security.Principal}} interface:
{code:java|title=VIPPrincipal.java}
/*
* VIPPrincipal.java
*
* Contributors: Terry J. Gardner, Jeff Bounds
*/

package com.sun;

import java.security.Principal;

/**
* Implements the methods in the Principal interface
*
* @author Terry J. Gardner
*/
public class VIPPrincipal implements Principal {

/**
* @serial
*/
private String name;

public VIPPrincipal(String name) {
if(name == null) {
throw new NullPointerException("illegal null input");
}
this.name = name;
}

/**
* Return the LDAP username for this <code>VIPPrincipal</code>.
*
* <p>
*
* @return the LDAP username for this <code>VIPPrincipal</code>
*/
public String getName() {
return this.name;
}

/**
* Return a string representation of this <code>VIPPrincipal</code>.
*
* <p>
*
* @return a string representation of this <code>VIPPrincipal</code>.
*/
public String toString() {
return("VIPPrincipal: " + name);
}

/**
* Compares the specified Object with this <code>VIPPrincipal</code>
* for equality. Returns true if the given object is also a
* <code>VIPPrincipal</code> and the two VIPPrincipals
* have the same username. If the object to be compared is null
* no action is taken and no exception id thrown.
*
* <p>
*
* @param o Object to be compared for equality with this
* <code>VIPPrincipal</code>.
*
* @return true if the specified Object is equal equal to this
* <code>VIPPrincipal</code>.
*/
public boolean equals(Object o) {
if(o == null) {
return false;
}
if(this == o) {
return true;
}
if(!(o instanceof VIPPrincipal)) {
return false;
}
VIPPrincipal that = (VIPPrincipal)o;

if(this.getName().equals(that.getName())) {
return true;
}
return false;
}

/**
* Return a hash code for this <code>VIPPrincipal</code>.
*
* <p>
*
* @return a hash code for this <code>VIPPrincipal</code>.
*/
public int hashCode() {
return name.hashCode();
}
}
{code}

h2. Step 3 - create custom login module class

{code:java|title=VIPLoginModule.java}
/**
* A Sun Java System Access Manager Custom Authentication Module
* Based upon the SocketLoginModule at http://wikis.sun.com/
* Contributors: Terry J. Gardner, Jeff Bounds
*/
package com.sun;


import com.sun.identity.authentication.spi.AMLoginModule;
import com.sun.identity.authentication.spi.AuthLoginException;




import java.util.Map;

import javax.security.auth.*;
import javax.security.auth.callback.*;

/**
* @author Terry J. Gardner
*/
public class VIPLoginModule extends AMLoginModule {

//------------ public ------------

/**
* initialize this object
*
* @param subject
* @param sharedState
* @param options
*/
@Override
public void init(Subject subject, Map sharedState, Map options) {
// no implementation necessary
}

/**
* This method does the authentication of the subject
*
* @param callbacks the array of callbacks from the module configuration file
* @param state the current state of the authentication process
* @throws AuthLoginException if an error occurs
*/
@Override
public int process(Callback[] callbacks,int state) throws AuthLoginException {

// this module is married to the module properties file
// therefore the number of callbacks must match
if(callbacks.length < 3) {
throw new AuthLoginException("fatal configuration error, wrong number of callbacks");
}

int currentState = state;

if(currentState == 1) {

// get the username
userName = ((NameCallback)callbacks[0]).getName();
if(userName == null || userName.equals("")) {
throw new AuthLoginException("username cannot be empty");
}

vipTokenId = ((NameCallback)callbacks[1]).getName();
if(vipTokenId == null || vipTokenId.equals("")) {
throw new AuthLoginException("Token ID cannot be empty");
}

otp = ((NameCallback)callbacks[2]).getName();
if(otp == null || otp.equals("")) {
throw new AuthLoginException("OTP cannot be empty");
}

//Create the VIPWebServiceClient. We pass in the userName, tokenID, and otp
//Here is where we would use the username to determine the tokenID.

VIPWebServiceClient vipWSC = new VIPWebServiceClient(userName,vipTokenId,otp);
String response = null;

try {
response = vipWSC.validateToken();
} catch(Exception ex) {
throw new AuthLoginException("Exception receiving response");
}

// check the response from the peer
if(response == null) {
throw new AuthLoginException("null response from authenticator system");
} else if(!vipWSC.isOK()) {
throw new AuthLoginException("login failure");
}
++currentState;
// this login module only has one state, though

// save the user name. getPrincipal()
// will use the userTokenID to return the
// Principal object. <code>getPrincipal</code>
// should return the last good authentication
userTokenId = userName;

}
return -1; // -1 indicates success
}

/**
* return the Principal object,
* creating it if necessary. This method
* is invoked at the end of successful
* authentication session. relies on
* userTokenID being set by process()
*
* <p>
*
* @return the Principal object or null if userTokenId is null
*/
@Override
public java.security.Principal getPrincipal() {

java.security.Principal thePrincipal = null;
if(userPrincipal != null) {
thePrincipal = userPrincipal;
} else if(userTokenId != null) {
userPrincipal = new VIPPrincipal(userName);
thePrincipal = userPrincipal;
}
return thePrincipal;

}

// ------------ private ------------

private java.security.Principal userPrincipal = null;
private String userTokenId;
private String userName;
private String vipTokenId;
private String otp;

}
{code}

h2. Step 4 - install and configure in OpenSSO

Create a jar file containing the *VIPLoginModule.class*, *VIPPrincipal.class*, and the *VIPWebServiceClient.class\* files and copy the jar file to the
{noformat}
WEB-INF/lib
{noformat}
directory. Copy the \*VIPLoginModule.xml\* file to the directory containing the login modules definitions (the default is {{config/auth/default}} but this is customizable also). Restart OpenSSO.
Add the
{noformat}
com.sun.VIPLoginModule
{noformat}
to the list of pluggable authentication module classes list from the Configuration \-> Authentication \-> Core page.
Configure policy agents to use
{noformat}
http://openssoHost:port/opensso/UI/Login?module=VIPLoginModule
{noformat}
and restart the web container using the policy agent.

h2. Step 5 - Final Steps

VIP Test Drive requires the use of a Client certificate (which is provided). This needs to be added to the keystore.

h1. Documentation

* [Developer's Guide|http://docs.sun.com/app/docs/doc/819-4675]
* [Policy Agent 2.2 Documentation | http://docs.sun.com/app/docs/coll/1322.1?l=en]

h1. External Links

* [Download the policy agent | http://www.sun.com/download/products.xml?id=4648e019]
* [Netbeans | http://www.netbeans.org]
* [Java Authentication and Authorization Service (Wikipedia)| http://en.wikipedia.org/wiki/Java_Authentication_and_Authorization_Service]
* [Java Authentication and Authorization Service | http://java.sun.com/products/jaas/]
* [The original blog entry (probably outdated|http://blogs.sun.com/bounds/entry/verisign_identity_protection_and_opensso])

h1. Contributors

{contributors-summary}

The individuals who post here are part of the extended Sun Microsystems community and they might not be employed or in any way formally affiliated with Sun Microsystems. The opinions expressed here are their own, are not necessarily reviewed in advance by anyone but the individual authors, and neither Sun nor any other party necessarily agrees with them.

Copyright 1994-2009 Sun Microsystems, Inc.
Powered by Atlassian Confluence
Sun Guidelines on Public Discourse Privacy Policy Terms of Use Trademarks Site Map Employment Investor Relations Contact