public class DuoHttpSecurityService extends HttpSecurityService
This class combines the SSO capability builtin to Windows clients joined to a Windows domain with the Duo 2FA functionality from duosecurity.com using the Duo Universal Prompt Java Client (duo_universal_java).
This class is fully re-entrant such that any number of DuoHttpSecurityService instances with different configurations can be created and used concurrently such as to differentially process requests of user-agents based on cookies, tokens, IP masks or similar.
If a SecurityProvider that implements Windows builtin SSO is used, a Windows Computer account must be created by an Administrator in Active Directory as described in the Installation section of the Jespa Operator's Manual.
See the HttpSecurityService
(HSS) documentation for details about all non-Duo specific functionality.
A Duo admin account on duosecurity.com must create a Web SDK application under Dashboard > Applications. The resulting Client ID, Client Secret and API hostname are supplied to this class as properties.
The following is an example of how Duo properties might appear in a properties file specified using the properties.path HSS property:
duo.clientId = DIRSJWINVALIDK72JSU2 duo.clientSecret = kSKf93kInValidPDfKNdI2lsLmVUiT90011sMCn duo.api.host = api-1234abcd.duosecurity.com duo.redirect.uri = https://apps1.busi.corp:8443/mctrl/duo-callback duo.failmode = OPEN duo.excludes = /api/v1/mctrl,/api/v1/bindings/*.sch
Alternatively, these properties may be supplied as a simple Map to init(java.lang.String, javax.servlet.ServletContext, java.util.Map)
(or using init() and then overriding some or all of those properties with properties from a file).
The fallback.location HSS property is required and should refer to a login form page that is also in the HSS excludes property list.
See the HttpSecurityService
API documentation and Jespa Operator's Manual for details.
The Jespa jar file, the duo_universal_java library jar file and the jar files the Duo client depends on must be added to the application lib directory (such as into WEB-INF/lib/
).
The duo_universal_java jar file and the jar files it depends on are most easily obtained using Maven.
When an end-user attempts to access any resource protected by the DuoHttpSecurityService, Windows built-in SSO will occur followed by the Duo second factor authentication as follows:
NtlmSecurityProvider
is configured as the HSS security provider, and the user's browser is configured to perform SSO, the user should be transparently authenticated using the credentials used to log into their workstation / device for a completely password-free experience.
If an error occurs with Duo or Windows built-in SSO or other configured Jespa security provider, the Duo token and Jespa security provider state will be removed from the user's session and the user will be redirected to the fallback.location.
Name | Description | Example |
---|---|---|
duo.clientId | The Client ID from the Web SDK application properties created in the Duo dashboard. | DIRSJWINVALIDK72JSU2 |
duo.clientSecret | The Client secret from the Web SDK application properties created in the Duo dashboard. | kSKf93kInValidPDfKNdI2lsLmVUiT90011sMCn |
duo.api.host | The API hostname from the Web SDK application properties created in the Duo dashboard. | api-1234abcd.duosecurity.com |
duo.redirect.uri | The URL that Duo should redirect user-agents back to after performing 2FA. This is just the URL to the webapp root of your application but it must be HTTPS and must end with "duo-callback". | https://apps1.busi.corp:8443/mctrl/duo-callback |
duo.excludes | A comma separated list of DOS-style wildcard expressions for matching paths relative to the webapp base that should be excluded from Duo 2FA.
This property may be used to effectively disable Duo for resources that are to be accessed by user-agents that cannot perform 2FA like service agents.
Note: This property serves the same purpose as the HttpSecurityService excludes property but for Duo 2FA and not the first factor authentication performed by the HSS.
The HSS excludes property is evaluated before this property.
Duo will not be invoked if HSS authentication does not occur because an HSS excludes pattern matched the request path.
Meaning it is not necessary to add expressions to duo.excludes if the paths they exclude are already excluded by HSS excludes.
|
/api/v1/mctrl,/api/v1/bindings/*.sch |
duo.failmode |
If set to "OPEN" and the Duo health-check step fails, such as because of a connectivity issue with Duo servers, user-agents will be permitted to access protected resources using only Windows builtin SSO and not Duo 2FA.
The default value is "CLOSED" such that Duo 2FA is always required. If a fail-open condition occurs, the "jespa.duo.token" in the session will be set to the specialDUO_TOKEN_FAILED_OPEN object.
|
CLOSED |
duo.disabled | If set to true, 1 or yes, the Duo 2FA code will be completely disabled such that it will be equivalent to the regular HttpSecurityService. | false |
This flow can be augmented to some extent by extending this class to create a custom handler or Filter that overrides various methods of this class and / or the parent HttpSecurityService.
Modifier and Type | Field and Description |
---|---|
static java.lang.Object |
DUO_TOKEN_FAILED_OPEN
Under normal operating conditions,
HttpSession.getAttribute("jespa.duo.token") returns a com.duosecurity.model.Token object with detailed information about the Duo step including the end-user's mobile number and location. |
Constructor and Description |
---|
DuoHttpSecurityService() |
Modifier and Type | Method and Description |
---|---|
void |
doFilter(javax.servlet.ServletRequest request,
javax.servlet.ServletResponse response,
javax.servlet.FilterChain chain)
This method overrides the parent doFilter method to invoke the Duo client only after a successful authentication by the parent HttpSecurityService.doFilter method.
|
void |
init(java.lang.String name,
javax.servlet.ServletContext servletContext,
java.util.Map props)
Initialize the DuoHttpSecurityService with explicitly supplied properties.
|
protected boolean |
isDuoProtected(javax.servlet.http.HttpServletRequest request)
Called by doFilter to determine if the request is for a protected resource which may only be accessed by an end-user that has successfully authenticated using Duo 2FA.
|
protected boolean |
isLogout(javax.servlet.http.HttpServletRequest request)
Called by the parent HttpSecurityService.doFilter method to determine if this is a request to "logout" the current authenticated user.
|
protected void |
onDuoException(com.duosecurity.exception.DuoException de,
javax.servlet.http.HttpServletRequest req,
javax.servlet.http.HttpServletResponse rsp,
SecurityProvider sp)
Called when a DuoException occurs including but not limited to exceptions thrown by the duo_universal_java library.
|
protected java.lang.String |
onDuoResult(com.duosecurity.model.Token token,
boolean isAllow,
javax.servlet.http.HttpServletRequest req,
javax.servlet.http.HttpServletResponse rsp,
SecurityProvider provider,
java.lang.String entryUrl)
This method is called when the Duo client processes 2FA but regardless of the result.
|
protected void |
onException(SecurityProviderException spe,
javax.servlet.http.HttpServletRequest req,
javax.servlet.http.HttpServletResponse rsp,
SecurityProvider sp)
Called when a SecurityProviderException occurs within the HttpSecurityService.
|
protected void |
onPropertiesUpdate(java.util.Map props)
Called by the HttpSecurityService during initialization and when the file identified by the properties.path property has been modified (although not more than once within a 5 second period).
|
destroy, getBindingsCertHashPolicy, getBindingsTargetSpnsPolicy, getConnectionId, getRequestCredential, getRequestPath, getServletContext, init, isAllowedAccess, isAnonymous, isProtected, matchWildcard, toString
public static final java.lang.Object DUO_TOKEN_FAILED_OPEN
Under normal operating conditions, HttpSession.getAttribute("jespa.duo.token")
returns a com.duosecurity.model.Token object with detailed information about the Duo step including the end-user's mobile number and location.
However, if the Duo health-check fails and the property duo.failmode = OPEN
is set, the Duo Token will not be returned even if the authentication was successful.
Instead, getAttribute will return the constant DuoHttoSecurityService.DUO_TOKEN_FAILED_OPEN.
If you write code that inspects the Duo Token, such as to initialize profile information of new users, and you want to the implementation to be able to fail open, you will need to check to see if the DUO_TOKEN_FAILED_OPEN value was returned and act accordingly (probably by throwing an Exception or returning an error). Such code might look like the following:
Token duoToken = (Token)ssn.getAttribute("jespa.duo.token"); if (duoToken == DuoHttpSecurityService.DUO_TOKEN_FAILED_OPEN) { throw new Exception("Failed to initialize profile: The Duo service is currently not available"); }
public void init(java.lang.String name, javax.servlet.ServletContext servletContext, java.util.Map props) throws SecurityProviderException
DuoHttpSecurityFilter
for a code example that uses this method.init
in class HttpSecurityService
name
- The name of this HttpSecurityService instanceservletContext
- The ServletContext that is initializing this HttpSecurityService instanceprops
- The HttpSecurityService and SecurityProvider propertiesSecurityProviderException
protected void onPropertiesUpdate(java.util.Map props) throws SecurityProviderException
A method that overrides this must first call super.onPropertiesUpdate(props)
.
The default implementation of this method enumerates and isolates the "duo." prefixed properties, resets the duo.clientSecret to be an encrypted property (so that it is not leaked into log files) and resets other objects that depends on these properties such as the Duo client, the excludes list and more.
onPropertiesUpdate
in class HttpSecurityService
props
- The updated list of properties. The properties Map should not be modified.SecurityProviderException
protected void onException(SecurityProviderException spe, javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse rsp, SecurityProvider sp) throws SecurityProviderException
The default implementation of this method sets the HttpSession attribute "jespa.message" to the exception message so that the page identified by the fallback.location can optionally retrieve it, remove it and display it to the end-user.
onException
in class HttpSecurityService
SecurityProviderException
protected void onDuoException(com.duosecurity.exception.DuoException de, javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse rsp, SecurityProvider sp) throws SecurityProviderException
The default implementation of this method sets the HttpSession attribute "jespa.message" to the exception message so that the page identified by the fallback.location can optionally retrieve it, remove it and display it to the end-user.
SecurityProviderException
protected java.lang.String onDuoResult(com.duosecurity.model.Token token, boolean isAllow, javax.servlet.http.HttpServletRequest req, javax.servlet.http.HttpServletResponse rsp, SecurityProvider provider, java.lang.String entryUrl) throws com.duosecurity.exception.DuoException
Token.getAuth_result().getStatus() == "allow"
.
This method may be overridden to implement custom Duo specific functionality when the Duo client completes such as storing information from the Token into a database or constructing a more complete security context with data from LDAP (see Account
for an example).
The default implementation of this method simply returns the entryUrl parameter so that end-users will be redirected to the resource that was originally requested (unless that request was not GET in which case the entryUrl parameter and return value will be null).
If isAllow is not true, the return value is ignored and the end user will be redirected to the location indicated by the fallback.location property.
token
- The duo_universal_java library Token resulting from 2FA with Duo serviceisAllow
- true only if the auth_result status is "allow"req
- The HttpServletRequest of the final redirect to the landing location. Note that this is not the entry request that triggered Duo 2FA.rsp
- The HttpServletResponse that will emit a minimal HTML page containing the JavaScript redirect URL to the landing location.provider
- The Jespa SecurityProvider representing the authenticated security context (such as the NtlmSecurityProvider
).entryUrl
- The URL, including query string parameters, of the entry request that triggered Duo 2FA but will be null if the entry request was not a GET request.com.duosecurity.exception.DuoException
protected boolean isDuoProtected(javax.servlet.http.HttpServletRequest request) throws javax.servlet.ServletException
The default implementation of this method checks to see if duo.disabled = true
or if the request path matches any expression in the duo.excludes list.
If either case is true, false is returned to indicate that the resource is not protected from Duo 2FA.
request
- the HttpServletRequest object passed to doFilter for the protected resourcejavax.servlet.ServletException
protected boolean isLogout(javax.servlet.http.HttpServletRequest request) throws javax.servlet.ServletException
The default implementation of this method just removes the jespa.duo.token object and returns the result of calling super.isLogout.
isLogout
in class HttpSecurityService
request
- the HttpServletRequest passed to doFilterjavax.servlet.ServletException
public void doFilter(javax.servlet.ServletRequest request, javax.servlet.ServletResponse response, javax.servlet.FilterChain chain) throws java.io.IOException, javax.servlet.ServletException
doFilter
in class HttpSecurityService
request
- the HttpServletRequest being handledresponse
- the HttpServletResponse being handledchain
- the FilterChain to call if the client successfully traverses the HttpSecurityService's security controlsjava.io.IOException
javax.servlet.ServletException