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
>