You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tomcat.apache.org by Brian Stansberry <br...@wanconcepts.com> on 2003/05/04 21:46:23 UTC

[proposal] Reauthentication support in SingleSignOn

Hi all,

I would like to propose that Tomcat's SingleSignOn add support for 
reauthenticating user credentials to the Realm each time a request is 
made.  The various Authenticator implementations support this behavior if 
their "cache" property is set to false, but if a SingleSignOn valve is 
added to the Host configuration, this behavior is disabled.  This causes 
problems with security implementations (e.g. JBoss' JaasSecurityManager) 
that expect the user to be reauthenticated to each thread (see 
http://www.jboss.org/thread.jsp?forum=49&thread=8141).

If you think this is a worthwhile suggestion, I've taken a crack at 
implementing this.  Following is a diff to the latest cvs version of the 
various affected classes; also attached is a zip with the complete 
source.  Affected classes are SingleSignOn as well as AuthenticatorBase and 
its subclasses.

Also, while looking at this I noticed that both BasicAuthenticator and 
DigestAuthenticator were never calling associate() to associate their 
session with any SingleSignOn.  This seemed odd, so the attached code deals 
with this as well.

There is one behavior quirk with the attached implementation if it is run 
in "reauthentication" mode.  If the original authentication method was 
BASIC or FORM, the SingleSignOn object uses the username and password 
stored in the SingleSignOnEntry to reauthenticate each subsequent 
request.  No problem there.  If the original authentication was DIGEST or 
CERT, it cannot do this, and counts on a subsequent DigestAuthenticator or 
SSLAuthenticator to reauthenticate.  These authenticators can do this w/o 
requiring user interaction, so no problem there.  However, if the new 
request is for a context that uses FORM or BASIC authentication, the 
context's Basic/FormAuthenticator will not see a Principal attached to the 
request and will have to prompt for a login.  Therefore in that situation 
the user may experience a second login prompt (one for an original DIGEST 
authentication, another for the subsequent FORM or BASIC).  Thereafter the 
SingleSignOn has the data it needs and the problem will not re-arise.

This quirk can be avoided by not mixing DIGEST webapps with BASIC/FORM webapps.

If the powers that be think this suggestion has any merit, I'd be more than 
happy to make any further changes you feel necessary.

Best,
Brian

Index: 
./catalina/src/share/org/apache/catalina/authenticator/AuthenticatorBase.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/AuthenticatorBase.java,v
retrieving revision 1.6
diff -r1.6 AuthenticatorBase.java
791a792
 >         Session session = getSession(request, false);
793,794c794
<         if (cache) {
<             Session session = getSession(request, false);
---
 >         if (cache) {
809,825c809,840
<         // Construct a cookie to be returned to the client
<         if (sso == null)
<             return;
<         HttpServletRequest hreq =
<             (HttpServletRequest) request.getRequest();
<         HttpServletResponse hres =
<             (HttpServletResponse) response.getResponse();
<         String value = generateSessionId();
<         Cookie cookie = new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, value);
<         cookie.setMaxAge(-1);
<         cookie.setPath("/");
<         hres.addCookie(cookie);
<
<         // Register this principal with our SSO valve
<         sso.register(value, principal, authType, username, password);
<         request.setNote(Constants.REQ_SSOID_NOTE, value);
<
---
 >         // Notify any SingleSignOn valve of this event
 >         if (sso != null) {
 >             // Only create a new SSO entry if the SSO did not already
 >             // set a flag accepting the request (as it would do with
 >             // subsequent requests to DIGEST and CERT authenticated 
contexts)
 >             String ssoId =
 >                 (String) request.getNote(Constants.REQ_SSOID_NOTE);
 >             if (ssoId == null) {
 >                 // Construct a cookie to be returned to the client
 >                 HttpServletRequest hreq =
 >                     (HttpServletRequest) request.getRequest();
 >                 HttpServletResponse hres =
 >                     (HttpServletResponse) response.getResponse();
 >                 String value = generateSessionId();
 >                 Cookie cookie =
 >                     new Cookie(Constants.SINGLE_SIGN_ON_COOKIE, value);
 >                 cookie.setMaxAge(-1);
 >                 cookie.setPath("/");
 >                 hres.addCookie(cookie);
 >
 >                 // Register this principal with our SSO valve
 >                 sso.register(value, principal, authType, username, 
password);
 >                 request.setNote(Constants.REQ_SSOID_NOTE, value);
 >             }
 >             else {
 >                 // SingleSignOn entry already exists; associate this
 >                 // request's session with it
 >                 if (session == null)
 >                     session = getSession(request, true);
 >                 sso.associate(ssoId, session);
 >             }
 >         }
948a964
 >
Index: 
./catalina/src/share/org/apache/catalina/authenticator/BasicAuthenticator.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/BasicAuthenticator.java,v
retrieving revision 1.2
diff -r1.2 BasicAuthenticator.java
145a146,148
 >         HttpServletRequest hreq =
 >           (HttpServletRequest) request.getRequest();
 >
147,148c150,151
<         Principal principal =
<             ((HttpServletRequest) request.getRequest()).getUserPrincipal();
---
 >         Principal principal = hreq.getUserPrincipal();
 >         String ssoId = (String) 
request.getNote(Constants.REQ_SSOID_NOTE);
150,151c153,158
<             if (log.isDebugEnabled())
<                 log.debug("Already authenticated '" + principal.getName() 
+ "'");
---
 >             if (log.isDebugEnabled()) {
 >                 log.debug("Already authenticated '"
 >                             + principal.getName() + "'");
 >             }
 >             if (ssoId != null)
 >                 associate(ssoId, getSession(request, true));
153a161,171
 >         else if (ssoId != null)
 >         {
 >             // An ssoId with no UserPrincipal means the SSO is based on
 >             // DIGEST or SSL certification.  SingleSignOn can't 
reauthenticate
 >             // for us with no username and password; have to get it from 
user
 >             if (log.isDebugEnabled())
 >                 log.debug("SSO Id set, but no UserPrincipal");
 >             // Remove the SSO Id note so any register() call
 >             // will generate a new SSO entry
 >             request.removeNote(Constants.REQ_SSOID_NOTE);
 >         }
156,159d173
<         HttpServletRequest hreq =
<             (HttpServletRequest) request.getRequest();
<         HttpServletResponse hres =
<             (HttpServletResponse) response.getResponse();
175a190,191
 >         HttpServletResponse hres =
 >             (HttpServletResponse) response.getResponse();
243a260
 >
Index: 
./catalina/src/share/org/apache/catalina/authenticator/DigestAuthenticator.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/DigestAuthenticator.java,v
retrieving revision 1.1.1.1
diff -r1.1.1.1 DigestAuthenticator.java
81a82,83
 > import org.apache.commons.logging.Log;
 > import org.apache.commons.logging.LogFactory;
95a98
 >     private static Log log = LogFactory.getLog(DigestAuthenticator.class);
217a221,223
 >         HttpServletRequest hreq =
 >             (HttpServletRequest) request.getRequest();
 >
219,221c225,236
<         Principal principal =
<             ((HttpServletRequest) request.getRequest()).getUserPrincipal();
<         if (principal != null)
---
 >         Principal principal = hreq.getUserPrincipal();
 >         if (principal != null) {
 >             if (log.isDebugEnabled()) {
 >                 log.debug("Already authenticated '"
 >                             + principal.getName() + "'");
 >             }
 >             // Did a SingleSignOn valve authenticate? (who else??)
 >             // If so, associate this session with the single sign-on
 >             String ssoId =
 >                 (String) request.getNote(Constants.REQ_SSOID_NOTE);
 >             if (ssoId != null)
 >                 associate(ssoId, getSession(request, true));
222a238
 >         }
225,228d240
<         HttpServletRequest hreq =
<             (HttpServletRequest) request.getRequest();
<         HttpServletResponse hres =
<             (HttpServletResponse) response.getResponse();
246a259,260
 >         HttpServletResponse hres =
 >             (HttpServletResponse) response.getResponse();
464a479
 >
Index: 
./catalina/src/share/org/apache/catalina/authenticator/FormAuthenticator.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/FormAuthenticator.java,v
retrieving revision 1.2
diff -r1.2 FormAuthenticator.java
154a155
 >         String ssoId = (String) 
request.getNote(Constants.REQ_SSOID_NOTE);
156,159c157,158
<             if (log.isDebugEnabled())
<                 log.debug("Already authenticated '" +
<                     principal.getName() + "'");
<             String ssoId = (String) 
request.getNote(Constants.REQ_SSOID_NOTE);
---
 >             if (debug >= 1)
 >                 log("Already authenticated '" + principal.getName() + "'");
163a163,173
 >         else if (ssoId != null)
 >         {
 >             // An ssoId with no UserPrincipal means the SSO is based on
 >             // DIGEST or SSL certification.  SingleSignOn can't 
reauthenticate
 >             // for us with no username and password; have to get it from 
user
 >             if (debug >= 1)
 >                 log("SSO Id set, but no UserPrincipal");
 >             // Remove the SSO Id note so any register() call
 >             // will generate a new SSO entry
 >             request.removeNote(Constants.REQ_SSOID_NOTE);
 >         }
196c206,207
<                 log.debug("Restore request from session '" + 
session.getId() + "'");
---
 >                 log.debug("Restore request from session '"
 >                             + session.getId() + "'");
202d212
<             String ssoId = (String) 
request.getNote(Constants.REQ_SSOID_NOTE);
465a476
 >
Index: 
./catalina/src/share/org/apache/catalina/authenticator/NonLoginAuthenticator.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/NonLoginAuthenticator.java,v
retrieving revision 1.1.1.1
diff -r1.1.1.1 NonLoginAuthenticator.java
135a136,142
 >         // If there is a single sign-on session in effect,
 >         // associate this request's session with it so any
 >         // logout will affect it too
 >         /*String ssoId = (String) request.getNote(Constants.REQ_SSOID_NOTE);
 >         if (ssoId != null)
 >             associate(ssoId, getSession(request, true));*/
 >
140d146
<
144a151
 >
Index: 
./catalina/src/share/org/apache/catalina/authenticator/SSLAuthenticator.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/SSLAuthenticator.java,v
retrieving revision 1.6
diff -r1.6 SSLAuthenticator.java
136a137,142
 >         // References to objects we will need later
 >         HttpServletRequest hreq =
 >             (HttpServletRequest) request.getRequest();
 >         HttpServletResponse hres =
 >             (HttpServletResponse) response.getResponse();
 >
138,139c144
<         Principal principal =
<             ((HttpServletRequest) request.getRequest()).getUserPrincipal();
---
 >         Principal principal = hreq.getUserPrincipal();
143c148,156
<             return (true);
---
 >             String ssoId = (String) 
request.getNote(Constants.REQ_SSOID_NOTE);
 >             if (ssoId != null)
 >                 associate(ssoId, getSession(request, true));
 >             // only return here if we are configured to "cache 
principals".
 >             // An SSO authentication based on BASIC or FORM is weaker
 >             // than this authenticator, so it should not override it
 >             // unless this authenticator has been so configured
 >             if (getCache())
 >                 return (true);
147,148d159
<         HttpServletResponse hres =
<             (HttpServletResponse) response.getResponse();
152c163
<         if ("POST".equalsIgnoreCase(((HttpServletRequest) 
request.getRequest()).getMethod())) {
---
 >         if ("POST".equalsIgnoreCase(hreq.getMethod())) {
154c165,166
<             // removing data from socket so that a cert exchange can 
happen if needed.
---
 >             // removing data from socket so that a cert exchange can happen
 >             // if needed.
156c168
<             ((HttpServletRequest) request.getRequest()).getParameterMap();
---
 >             hreq.getParameterMap();
160c172
<             request.getRequest().getAttribute(Globals.CERTIFICATES_ATTR);
---
 >             hreq.getAttribute(Globals.CERTIFICATES_ATTR);
163c175
< 
request.getRequest().getAttribute(Globals.SSL_CERTIFICATE_ATTR);
---
 >                 hreq.getAttribute(Globals.SSL_CERTIFICATE_ATTR);
222a235
 >
Index: ./catalina/src/share/org/apache/catalina/authenticator/SingleSignOn.java
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/SingleSignOn.java,v
retrieving revision 1.4
diff -r1.4 SingleSignOn.java
82a83
 > import org.apache.catalina.Realm;
167a169,173
 >     /**
 >      * Should we cache authenticated Principals or reauthenticate
 >      * credentials with every request?
 >      */
 >     protected boolean cachePrincipals = true;
168a175
 >
172a180,214
 >      * Gets whether this valve should, following a successful 
authentication,
 >      * associate a cached <code>Principal</code> object with each subsequent
 >      * request that is associated with the single sign on session.
 >      *
 >      * @return  <code>true</code> if a cached <code>Principal</code> should
 >      *          be used without reauthenticating each request to the
 >      *          containing host's <code>Realm</code>; <code>false</code> if
 >      *          each request should be reauthenticated using cached (in the
 >      *          case of BASIC and FORM authentication) or 
browser-resubmitted
 >      *          (in the case of CERT and DIGEST authentication) credentials.
 >      */
 >     public final boolean getCachePrincipals()
 >     {
 >         return (this.cachePrincipals);
 >     }
 >
 >     /**
 >      * Sets whether this valve should, following a successful 
authentication,
 >      * associate a cached <code>Principal</code> object with each subsequent
 >      * request that is associated with the single sign on session.
 >      *
 >      * @param cache <code>true</code> if a cached <code>Principal</code> 
should
 >      *              be used without reauthenticating each request to the
 >      *              containing host's <code>Realm</code>; 
<code>false</code> if
 >      *              each request should be reauthenticated using cached 
(in the
 >      *              case of BASIC and FORM authentication) or
 >      *              browser-resubmitted (in the case of CERT and DIGEST
 >      *              authentication) credentials.
 >      */
 >     public final void setCachePrincipals(boolean cache)
 >     {
 >         this.cachePrincipals = cache;
 >     }
 >
 >     /**
387a430
 >         String ssoId = cookie.getValue();
389,390c432,435
<             log(" Checking for cached principal for " + cookie.getValue());
<         SingleSignOnEntry entry = lookup(cookie.getValue());
---
 >             log(" Checking for cached principal for " + ssoId);
 >         SingleSignOnEntry entry = lookup(ssoId);
 >         boolean validEntry = false;
 >         boolean setRequestAuth = false;
392,401c437,490
<             if (debug >= 1)
<                 log(" Found cached principal '" +
<                     entry.principal.getName() + "' with auth type '" +
<                     entry.authType + "'");
<             request.setNote(Constants.REQ_SSOID_NOTE, cookie.getValue());
<             ((HttpRequest) request).setAuthType(entry.authType);
<             ((HttpRequest) request).setUserPrincipal(entry.principal);
<         } else {
<             if (debug >= 1)
<                 log(" No cached principal found, erasing SSO cookie");
---
 >             if (!getCachePrincipals()) {
 >                 if (entry.canReauthenticate) {
 >                     if (reauthenticate(entry.username, entry.password)) {
 >                         if (debug >= 1) {
 >                             log(" Reauthenticated cached principal '"
 >                                     + entry.principal.getName()
 >                                     + "' with auth type '"
 >                                     + entry.authType + "'");
 >                         }
 >                         validEntry = true;
 >                         setRequestAuth = true;
 >                     }
 >                     else
 >                     {
 >                         // What to do here?  FormAuthenticator does not
 >                         // invalidate any sessions; it just proceeds
 >                         // to the logon process.  This seems suspect, but to
 >                         // be consistent, we'll do the same
 >                         if (debug >= 1)
 >                             log("Reauthentication failed, proceed 
normally");
 >                     }
 >                 }
 >                 else {
 >                     // auth type was NONE, DIGEST or CERT
 >                     // This class can't reauthenticate these types,
 >                     // so we don't want to setUserPrincipal
 >
 >                     // We want to set a request note w/ the ssoId so
 >                     // any call to AuthenticatorBase.register() following
 >                     // reauthentication by SSLAuthenticator or
 >                     // DigestAuthenticator will not generate a new SSO 
entry`
 >                     validEntry = true;
 >                 }
 >             }
 >             else {
 >                 // we are caching principals
 >                 validEntry = true;
 >                 setRequestAuth = true;
 >             }
 >         }
 >
 >         if (validEntry) {
 >             request.setNote(Constants.REQ_SSOID_NOTE, ssoId);
 >             if (setRequestAuth) {
 >                 ((HttpRequest) request).setAuthType(entry.authType);
 >                 ((HttpRequest) request).setUserPrincipal(entry.principal);
 >             }
 >         }
 >         else
 >         {
 >             if (debug >= 1) {
 >                 log(" No currently valid cached principal found, "
 >                         + "erasing SSO cookie");
 >             }
550c639,660
<
---
 >     /**
 >      * Attempts reauthentication to the <code>Realm</code> using
 >      * the passed username and password.
 >      */
 >     private boolean reauthenticate(String username, String password) {
 >
 >         boolean result = false;
 >
 >         Principal reauthPrincipal = null;
 >         Container parent = getContainer();
 >         if (parent != null) {
 >             Realm realm = parent.getRealm();
 >             if (realm != null && username != null)
 >                 reauthPrincipal = realm.authenticate(username, password);
 >         }
 >
 >         result = (reauthPrincipal != null);
 >
 >         return result;
 >     }
 >
 >
624a735,736
 >     public boolean canReauthenticate = false;
 >
631a744,746
 >         this.canReauthenticate =
 >             (Constants.BASIC_METHOD.equals(authType)
 >                 || Constants.FORM_METHOD.equals(authType));
660a776
 >
Index: 
./catalina/src/share/org/apache/catalina/authenticator/mbeans-descriptors.xml
===================================================================
RCS file: 
/home/cvspublic/jakarta-tomcat-catalina/catalina/src/share/org/apache/catalina/authenticator/mbeans-descriptors.xml,v
retrieving revision 1.1
diff -r1.1 mbeans-descriptors.xml
128a129,132
 >     <attribute name="cachePrincipals"
 >                description="Should we cache authenticated Principals if 
the request is part of an HTTP session?"
 >                type="boolean"/>
 >
164a169
 >