From 441280c9fca882a6632786588c60bde8f9cfd473 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Tue, 19 Mar 2019 11:10:05 +1100 Subject: [PATCH 1/2] Issue #3463 Fix jaas documentation realm and login module names. Signed-off-by: Jan Bartel --- .../asciidoc/configuring/security/jaas-support.adoc | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/jetty-documentation/src/main/asciidoc/configuring/security/jaas-support.adoc b/jetty-documentation/src/main/asciidoc/configuring/security/jaas-support.adoc index d6e486c86e4..37f8f7f95ca 100644 --- a/jetty-documentation/src/main/asciidoc/configuring/security/jaas-support.adoc +++ b/jetty-documentation/src/main/asciidoc/configuring/security/jaas-support.adoc @@ -52,13 +52,13 @@ Let's look at an example. ===== Step 1 -Configure a Jetty `org.eclipse.jetty.jaas.JAASLoginService` to match the `` in your `web.xml` file. For example, if the `web.xml` contains a realm called "xyz" like so: +Configure a Jetty `org.eclipse.jetty.jaas.JAASLoginService` to match the `` in your `web.xml` file. For example, if the `web.xml` contains a realm called "Test JAAS Realm" like so: [source, xml, subs="{sub-order}"] ---- FORM - xyz + Test JAAS Realm /login/login /login/error @@ -66,7 +66,7 @@ Configure a Jetty `org.eclipse.jetty.jaas.JAASLoginService` to match the ` ---- -Then you need to create a `JAASLoginService` with the matching name of "xyz": +then you need to create a `JAASLoginService` with the matching realm name of "Test JAAS Realm": [source, xml, subs="{sub-order}"] ---- @@ -76,9 +76,10 @@ Then you need to create a `JAASLoginService` with the matching name of "xyz": ---- +The `LoginModuleName` must match the name of your LoginModule as declared in your login module configuration file (see <>). ____ [CAUTION] -The name of the realm-name that you declare in `web.xml` must match exactly the name of your `JAASLoginService`. +The name of the realm-name that you declare in `web.xml` must match *exactly* the `Name` field of your `JAASLoginService`. ____ You can declare your `JAASLoginService` in a couple of different ways: @@ -135,7 +136,7 @@ xyz { ____ [CAUTION] -It is imperative that the application name on the first line is exactly the same as the `LoginModuleName` of your `JAASLoginService`. +It is imperative that the application name on the first line is *exactly* the same as the `LoginModuleName` of your `JAASLoginService`. ____ You may find it convenient to name this configuration file as `etc/login.conf` because, as we will see below, some of the wiring up for JAAS has been done for you. From 432fc41a32320a6025ae1f718d5ac739b5ab5314 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Wed, 20 Mar 2019 18:19:55 +1100 Subject: [PATCH 2/2] Jetty 9.4.x 3456 programmatic authentication (#3472) * Issue #3456 Allow multiple programmatic login/logout in same request. Signed-off-by: Jan Bartel --- .../security/AbstractUserAuthentication.java | 24 ++ .../eclipse/jetty/security/Authenticator.java | 2 +- .../security/LoggedOutAuthentication.java | 60 +++++ .../jetty/security/SecurityHandler.java | 5 +- .../jetty/security/UserAuthentication.java | 5 +- .../DeferredAuthentication.java | 22 +- .../authentication/FormAuthenticator.java | 21 +- .../authentication/LoginAuthenticator.java | 15 ++ .../authentication/SessionAuthentication.java | 38 ++- .../jetty/security/ConstraintTest.java | 237 ++++++++++++++++++ .../eclipse/jetty/server/Authentication.java | 86 +++++-- .../org/eclipse/jetty/server/Request.java | 13 +- 12 files changed, 467 insertions(+), 61 deletions(-) create mode 100644 jetty-security/src/main/java/org/eclipse/jetty/security/LoggedOutAuthentication.java diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java index 5ca2bc7ce02..6a197dae7b8 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/AbstractUserAuthentication.java @@ -21,6 +21,11 @@ package org.eclipse.jetty.security; import java.io.Serializable; import java.util.Set; +import javax.servlet.ServletRequest; +import javax.servlet.http.HttpServletRequest; + +import org.eclipse.jetty.security.authentication.LoginAuthenticator; +import org.eclipse.jetty.server.Authentication; import org.eclipse.jetty.server.Authentication.User; import org.eclipse.jetty.server.UserIdentity; import org.eclipse.jetty.server.UserIdentity.Scope; @@ -95,4 +100,23 @@ public abstract class AbstractUserAuthentication implements User, Serializable return false; } + + @Override + public Authentication logout (ServletRequest request) + { + SecurityHandler security=SecurityHandler.getCurrentSecurityHandler(); + if (security!=null) + { + security.logout(this); + Authenticator authenticator = security.getAuthenticator(); + if (authenticator instanceof LoginAuthenticator) + { + ((LoginAuthenticator)authenticator).logout(request); + return new LoggedOutAuthentication((LoginAuthenticator)authenticator); + } + } + + return Authentication.UNAUTHENTICATED; + } + } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java index 42229058576..56b026e019e 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java @@ -80,7 +80,7 @@ public interface Authenticator * @param mandatory True if authentication is mandatory. * @return An Authentication. If Authentication is successful, this will be a {@link org.eclipse.jetty.server.Authentication.User}. If a response has * been sent by the Authenticator (which can be done for both successful and unsuccessful authentications), then the result will - * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}. If Authentication is not manditory, then a + * implement {@link org.eclipse.jetty.server.Authentication.ResponseSent}. If Authentication is not mandatory, then a * {@link org.eclipse.jetty.server.Authentication.Deferred} may be returned. * * @throws ServerAuthException if unable to validate request diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/LoggedOutAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/LoggedOutAuthentication.java new file mode 100644 index 00000000000..fff4d41b991 --- /dev/null +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/LoggedOutAuthentication.java @@ -0,0 +1,60 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd. +// ------------------------------------------------------------------------ +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the Eclipse Public License v1.0 +// and Apache License v2.0 which accompanies this distribution. +// +// The Eclipse Public License is available at +// http://www.eclipse.org/legal/epl-v10.html +// +// The Apache License v2.0 is available at +// http://www.opensource.org/licenses/apache2.0.php +// +// You may elect to redistribute this code under either of these licenses. +// ======================================================================== +// + +package org.eclipse.jetty.security; + +import javax.servlet.ServletRequest; + +import org.eclipse.jetty.security.authentication.LoginAuthenticator; +import org.eclipse.jetty.server.Authentication; +import org.eclipse.jetty.server.UserIdentity; + +/** + * LoggedOutAuthentication + * + * An Authentication indicating that a user has been previously, but is not currently logged in, + * but may be capable of logging in after a call to Request.login(String,String) + */ +public class LoggedOutAuthentication implements Authentication.NonAuthenticated +{ + private LoginAuthenticator _authenticator; + + public LoggedOutAuthentication (LoginAuthenticator authenticator) + { + _authenticator = authenticator; + } + + + @Override + public Authentication login(String username, Object password, ServletRequest request) + { + if (username == null) + return null; + + UserIdentity identity = _authenticator.login(username, password, request); + if (identity != null) + { + IdentityService identity_service = _authenticator.getLoginService().getIdentityService(); + UserAuthentication authentication = new UserAuthentication("API",identity); + if (identity_service != null) + identity_service.associate(identity); + return authentication; + } + return null; + } +} diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java index 68f19442bdf..1c4be92dc12 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/SecurityHandler.java @@ -581,8 +581,11 @@ public abstract class SecurityHandler extends HandlerWrapper implements Authenti public void logout(Authentication.User user) { LOG.debug("logout {}",user); + if (user == null) + return; + LoginService login_service=getLoginService(); - if (login_service!=null) + if (login_service != null) { login_service.logout(user.getUserIdentity()); } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java index 6c2d586d5e9..12659c9034c 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/UserAuthentication.java @@ -40,10 +40,9 @@ public class UserAuthentication extends AbstractUserAuthentication } @Override + @Deprecated public void logout() { - SecurityHandler security=SecurityHandler.getCurrentSecurityHandler(); - if (security!=null) - security.logout(this); } + } diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java index e28a634d74a..beaef79c174 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/DeferredAuthentication.java @@ -34,6 +34,8 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.security.IdentityService; import org.eclipse.jetty.security.LoginService; +import org.eclipse.jetty.security.LoggedOutAuthentication; +import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.security.ServerAuthException; import org.eclipse.jetty.security.UserAuthentication; import org.eclipse.jetty.server.Authentication; @@ -66,7 +68,6 @@ public class DeferredAuthentication implements Authentication.Deferred try { Authentication authentication = _authenticator.validateRequest(request,__deferredResponse,true); - if (authentication!=null && (authentication instanceof Authentication.User) && !(authentication instanceof Authentication.ResponseSent)) { LoginService login_service= _authenticator.getLoginService(); @@ -131,6 +132,25 @@ public class DeferredAuthentication implements Authentication.Deferred } return null; } + + + + @Override + public Authentication logout (ServletRequest request) + { + SecurityHandler security=SecurityHandler.getCurrentSecurityHandler(); + if (security!=null) + { + security.logout(null); + if (_authenticator instanceof LoginAuthenticator) + { + ((LoginAuthenticator)_authenticator).logout(request); + return new LoggedOutAuthentication((LoginAuthenticator)_authenticator); + } + } + + return Authentication.UNAUTHENTICATED; + } /* ------------------------------------------------------------ */ public Object getPreviousAssociation() diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java index 0b8ce9210a3..e7edd78d358 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/FormAuthenticator.java @@ -192,6 +192,7 @@ public class FormAuthenticator extends LoginAuthenticator UserIdentity user = super.login(username,password,request); if (user!=null) { + HttpSession session = ((HttpServletRequest)request).getSession(true); Authentication cached=new SessionAuthentication(getAuthMethod(),user,password); session.setAttribute(SessionAuthentication.__J_AUTHENTICATED, cached); @@ -199,7 +200,22 @@ public class FormAuthenticator extends LoginAuthenticator return user; } - + + + @Override + public void logout(ServletRequest request) + { + super.logout(request); + HttpServletRequest httpRequest = (HttpServletRequest)request; + HttpSession session = httpRequest.getSession(false); + + if (session == null) + return; + + //clean up session + session.removeAttribute(SessionAuthentication.__J_AUTHENTICATED); + } + /* ------------------------------------------------------------ */ @Override public void prepareRequest(ServletRequest request) @@ -536,7 +552,8 @@ public class FormAuthenticator extends LoginAuthenticator } /* ------------------------------------------------------------ */ - /** This Authentication represents a just completed Form authentication. + /** + * This Authentication represents a just completed Form authentication. * Subsequent requests from the same user are authenticated by the presents * of a {@link SessionAuthentication} instance in their session. */ diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java index 817dcc094d7..fd97b2a91e6 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/LoginAuthenticator.java @@ -40,6 +40,8 @@ public abstract class LoginAuthenticator implements Authenticator protected LoginService _loginService; protected IdentityService _identityService; private boolean _renewSession; + + protected LoginAuthenticator() { @@ -63,6 +65,19 @@ public abstract class LoginAuthenticator implements Authenticator return null; } + + public void logout (ServletRequest request) + { + HttpServletRequest httpRequest = (HttpServletRequest)request; + HttpSession session = httpRequest.getSession(false); + if (session == null) + return; + + session.removeAttribute(Session.SESSION_CREATED_SECURE); + } + + + @Override public void setConfiguration(AuthConfiguration configuration) { diff --git a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java index db529af60cc..3f778edcd67 100644 --- a/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java +++ b/jetty-security/src/main/java/org/eclipse/jetty/security/authentication/SessionAuthentication.java @@ -33,18 +33,25 @@ import org.eclipse.jetty.security.AbstractUserAuthentication; import org.eclipse.jetty.security.LoginService; import org.eclipse.jetty.security.SecurityHandler; import org.eclipse.jetty.server.UserIdentity; -import org.eclipse.jetty.server.session.Session; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; -public class SessionAuthentication extends AbstractUserAuthentication implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener + +/** + * SessionAuthentication + * + * When a user has been successfully authenticated with some types + * of Authenticator, the Authenticator stashes a SessionAuthentication + * into a HttpSession to remember that the user is authenticated. + * + */ +public class SessionAuthentication extends AbstractUserAuthentication + implements Serializable, HttpSessionActivationListener, HttpSessionBindingListener { private static final Logger LOG = Log.getLogger(SessionAuthentication.class); private static final long serialVersionUID = -4643200685888258706L; - - public final static String __J_AUTHENTICATED="org.eclipse.jetty.security.UserIdentity"; private final String _name; @@ -92,23 +99,12 @@ public class SessionAuthentication extends AbstractUserAuthentication implements } @Override + @Deprecated public void logout() { - if (_session!=null && _session.getAttribute(__J_AUTHENTICATED)!=null) - _session.removeAttribute(__J_AUTHENTICATED); - - doLogout(); - } - - private void doLogout() - { - SecurityHandler security=SecurityHandler.getCurrentSecurityHandler(); - if (security!=null) - security.logout(this); - if (_session!=null) - _session.removeAttribute(Session.SESSION_CREATED_SECURE); } + @Override public String toString() { @@ -118,7 +114,6 @@ public class SessionAuthentication extends AbstractUserAuthentication implements @Override public void sessionWillPassivate(HttpSessionEvent se) { - } @Override @@ -131,18 +126,15 @@ public class SessionAuthentication extends AbstractUserAuthentication implements } @Override + @Deprecated public void valueBound(HttpSessionBindingEvent event) { - if (_session==null) - { - _session=event.getSession(); - } } @Override + @Deprecated public void valueUnbound(HttpSessionBindingEvent event) { - doLogout(); } } diff --git a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java index 8288fed3d9e..8bb9cdb9ce8 100644 --- a/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java +++ b/jetty-security/src/test/java/org/eclipse/jetty/security/ConstraintTest.java @@ -34,6 +34,8 @@ import java.util.function.Consumer; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Stream; + +import javax.servlet.DispatcherType; import javax.servlet.HttpConstraintElement; import javax.servlet.HttpMethodConstraintElement; import javax.servlet.ServletException; @@ -61,6 +63,7 @@ import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.server.handler.HandlerWrapper; import org.eclipse.jetty.server.session.SessionHandler; import org.eclipse.jetty.util.B64Code; +import org.eclipse.jetty.util.StringUtil; import org.eclipse.jetty.util.TypeUtil; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Password; @@ -1053,6 +1056,174 @@ public class ConstraintTest assertThat(response, startsWith("HTTP/1.1 403")); assertThat(response, containsString("!role")); } + + + /** + * Test Request.login() Request.logout() with FORM authenticator + * @throws Exception + */ + @Test + public void testFormProgrammaticLoginLogout() throws Exception + { + //Test programmatic login/logout within same request: + // login - perform programmatic login that should succeed, next request should be also logged in + // loginfail - perform programmatic login that should fail, next request should not be logged in + // loginfaillogin - perform programmatic login that should fail then another that succeeds, next request should be logged in + // loginlogin - perform successful login then try another that should fail, next request should be logged in + // loginlogout - perform successful login then logout, next request should not be logged in + // loginlogoutlogin - perform successful login then logout then login successfully again, next request should be logged in + _security.setHandler(new ProgrammaticLoginRequestHandler()); + _security.setAuthenticator(new FormAuthenticator("/testLoginPage","/testErrorPage",false)); + _server.start(); + + String response; + + //login + response = _connector.getResponse("GET /ctx/prog?action=login HTTP/1.0\r\n\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + String session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + assertThat(response, containsString("user=admin")); + _server.stop(); + + //loginfail + _server.start(); + response = _connector.getResponse("GET /ctx/prog?action=loginfail HTTP/1.0\r\n\r\n"); + assertThat(response, startsWith("HTTP/1.1 500 Server Error")); + if (response.contains("JSESSIONID")) + { + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + } + else + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n\r\n"); + + assertThat(response, not(containsString("user=admin"))); + _server.stop(); + + //loginfaillogin + _server.start(); + response = _connector.getResponse("GET /ctx/prog?action=loginfail HTTP/1.0\r\n\r\n"); + assertThat(response, startsWith("HTTP/1.1 500 Server Error")); + response = _connector.getResponse("GET /ctx/prog?action=login HTTP/1.0\r\n\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + assertThat(response, containsString("user=admin")); + _server.stop(); + + //loginlogin + _server.start(); + response = _connector.getResponse("GET /ctx/prog?action=loginlogin HTTP/1.0\r\n\r\n"); + assertThat(response, startsWith("HTTP/1.1 500 Server Error")); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + assertThat(response, containsString("user=admin")); + _server.stop(); + + //loginlogout + _server.start(); + response = _connector.getResponse("GET /ctx/prog?action=loginlogout HTTP/1.0\r\n\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + assertThat(response, containsString("user=null")); + _server.stop(); + + //loginlogoutlogin + _server.start(); + response = _connector.getResponse("GET /ctx/prog?action=loginlogoutlogin HTTP/1.0\r\n\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, startsWith("HTTP/1.1 200 OK")); + assertThat(response, containsString("user=user0")); + _server.stop(); + + + //Test constraint-based login with programmatic login/logout: + // constraintlogin - perform constraint login, followed by programmatic login which should fail (already logged in) + _server.start(); + response = _connector.getResponse("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); + assertThat(response, containsString(" 302 Found")); + assertThat(response, containsString("/ctx/testLoginPage")); + assertThat(response, containsString("JSESSIONID=")); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + + response = _connector.getResponse("GET /ctx/testLoginPage HTTP/1.0\r\n"+ + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, containsString(" 200 OK")); + assertThat(response, not(containsString("JSESSIONID=" + session))); + response = _connector.getResponse("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 35\r\n" + + "\r\n" + + "j_username=user&j_password=password"); + assertThat(response, startsWith("HTTP/1.1 302 ")); + assertThat(response, containsString("Location")); + assertThat(response, containsString("/ctx/auth/info")); + assertThat(response, containsString("JSESSIONID=")); + assertThat(response, not(containsString("JSESSIONID=" + session))); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?action=constraintlogin HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, startsWith("HTTP/1.1 500 Server Error")); + _server.stop(); + + // logout - perform constraint login, followed by programmatic logout, which should succeed + _server.start(); + response = _connector.getResponse("GET /ctx/auth/info HTTP/1.0\r\n\r\n"); + assertThat(response, containsString(" 302 Found")); + assertThat(response, containsString("/ctx/testLoginPage")); + assertThat(response, containsString("JSESSIONID=")); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + + response = _connector.getResponse("GET /ctx/testLoginPage HTTP/1.0\r\n"+ + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, containsString(" 200 OK")); + assertThat(response, not(containsString("JSESSIONID=" + session))); + response = _connector.getResponse("POST /ctx/j_security_check HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "Content-Type: application/x-www-form-urlencoded\r\n" + + "Content-Length: 35\r\n" + + "\r\n" + + "j_username=user&j_password=password"); + assertThat(response, startsWith("HTTP/1.1 302 ")); + assertThat(response, containsString("Location")); + assertThat(response, containsString("/ctx/auth/info")); + assertThat(response, containsString("JSESSIONID=")); + assertThat(response, not(containsString("JSESSIONID=" + session))); + session = response.substring(response.indexOf("JSESSIONID=") + 11, response.indexOf(";Path=/ctx")); + response = _connector.getResponse("GET /ctx/prog?action=logout HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, containsString(" 200 OK")); + response = _connector.getResponse("GET /ctx/prog?x=y HTTP/1.0\r\n" + + "Cookie: JSESSIONID=" + session + "\r\n" + + "\r\n"); + assertThat(response, containsString(" 200 OK")); + assertThat(response, containsString("user=null")); + } @Test public void testStrictBasic() throws Exception @@ -1512,6 +1683,72 @@ public class ConstraintTest } } + private class ProgrammaticLoginRequestHandler extends AbstractHandler + { + @Override + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response ) throws IOException, ServletException + { + baseRequest.setHandled(true); + + String action = request.getParameter("action"); + if (StringUtil.isBlank(action)) + { + response.setStatus(200); + response.setContentType("text/plain; charset=UTF-8"); + response.getWriter().println("user="+request.getRemoteUser()); + return; + } + else if ("login".equals(action)) + { + request.login("admin", "password"); + return; + } + else if ("loginfail".equals(action)) + { + request.login("admin", "fail"); + return; + } + else if ("loginfaillogin".equals(action)) + { + try + { + request.login("admin", "fail"); + } + catch (ServletException se) + { + request.login("admin", "password"); + } + return; + } + else if ("loginlogin".equals(action)) + { + request.login("admin", "password"); + request.login("foo", "bar"); + } + else if ("loginlogout".equals(action)) + { + request.login("admin", "password"); + request.logout(); + } + else if ("loginlogoutlogin".equals(action)) + { + request.login("admin", "password"); + request.logout(); + request.login("user0", "password"); + } + else if ("constraintlogin".equals(action)) + { + String user = request.getRemoteUser(); + request.login("admin", "password"); + } + else if ("logout".equals(action)) + { + request.logout(); + } + else + response.sendError(500); + } + } private class RoleRefHandler extends HandlerWrapper { /* ------------------------------------------------------------ */ diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java index eaadb742bdc..60df8fe1988 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Authentication.java @@ -45,11 +45,12 @@ public interface Authentication /* ------------------------------------------------------------ */ /** A successful Authentication with User information. */ - public interface User extends Authentication + public interface User extends LogoutAuthentication { String getAuthMethod(); UserIdentity getUserIdentity(); boolean isUserInRole(UserIdentity.Scope scope,String role); + @Deprecated void logout(); } @@ -63,31 +64,13 @@ public interface Authentication HttpServletResponse getHttpServletResponse(); } - /* ------------------------------------------------------------ */ - /** A deferred authentication with methods to progress - * the authentication process. + /** + * An authentication that is capable of performing a programmatic login + * operation. + * */ - public interface Deferred extends Authentication + public interface LoginAuthentication extends Authentication { - /* ------------------------------------------------------------ */ - /** Authenticate if possible without sending a challenge. - * This is used to check credentials that have been sent for - * non-manditory authentication. - * @param request the request - * @return The new Authentication state. - */ - Authentication authenticate(ServletRequest request); - - /* ------------------------------------------------------------ */ - /** Authenticate and possibly send a challenge. - * This is used to initiate authentication for previously - * non-manditory authentication. - * @param request the request - * @param response the response - * @return The new Authentication state. - */ - Authentication authenticate(ServletRequest request,ServletResponse response); - /* ------------------------------------------------------------ */ /** Login with the LOGIN authenticator @@ -98,6 +81,53 @@ public interface Authentication */ Authentication login(String username,Object password,ServletRequest request); } + + + /** + * An authentication that is capable of performing a programmatic + * logout operation. + * + */ + public interface LogoutAuthentication extends Authentication + { + /* ------------------------------------------------------------ */ + /** + * Remove any user information that may be present in the request + * such that a call to getUserPrincipal/getRemoteUser will return null. + * + * @param request the request + * @return NoAuthentication if we successfully logged out + */ + Authentication logout (ServletRequest request); + } + + + /* ------------------------------------------------------------ */ + /** A deferred authentication with methods to progress + * the authentication process. + */ + public interface Deferred extends LoginAuthentication, LogoutAuthentication + { + /* ------------------------------------------------------------ */ + /** Authenticate if possible without sending a challenge. + * This is used to check credentials that have been sent for + * non-mandatory authentication. + * @param request the request + * @return The new Authentication state. + */ + Authentication authenticate(ServletRequest request); + + /* ------------------------------------------------------------ */ + /** Authenticate and possibly send a challenge. + * This is used to initiate authentication for previously + * non-mandatory authentication. + * @param request the request + * @param response the response + * @return The new Authentication state. + */ + Authentication authenticate(ServletRequest request,ServletResponse response); + + } /* ------------------------------------------------------------ */ @@ -127,6 +157,14 @@ public interface Authentication public interface SendSuccess extends ResponseSent { } + + /* ------------------------------------------------------------ */ + /** After a logout, the authentication reverts to a state + * where it is possible to programmatically log in again. + */ + public interface NonAuthenticated extends LoginAuthentication + { + } /* ------------------------------------------------------------ */ /** Unauthenticated state. diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java index e1015d84d4e..b15bcc7e5d6 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/Request.java @@ -2432,11 +2432,13 @@ public class Request implements HttpServletRequest @Override public void login(String username, String password) throws ServletException { - if (_authentication instanceof Authentication.Deferred) + if (_authentication instanceof Authentication.LoginAuthentication) { - _authentication=((Authentication.Deferred)_authentication).login(username,password,this); - if (_authentication == null) + Authentication auth = ((Authentication.LoginAuthentication)_authentication).login(username,password,this); + if (auth == null) throw new Authentication.Failed("Authentication failed for username '"+username+"'"); + else + _authentication = auth; } else { @@ -2448,9 +2450,8 @@ public class Request implements HttpServletRequest @Override public void logout() throws ServletException { - if (_authentication instanceof Authentication.User) - ((Authentication.User)_authentication).logout(); - _authentication=Authentication.UNAUTHENTICATED; + if (_authentication instanceof Authentication.LogoutAuthentication) + _authentication = ((Authentication.LogoutAuthentication)_authentication).logout(this); } /* ------------------------------------------------------------ */