Skip to content

Commit efe4e1b

Browse files
Merge pull request #12393 from jetty/jetty-12.1.x-5442-multipleAuthentication
Issue #5442 - add MultiAuthenticator to support multiple authentication options
2 parents a64c0bb + a5c8879 commit efe4e1b

File tree

11 files changed

+927
-32
lines changed

11 files changed

+927
-32
lines changed
Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,26 +11,27 @@
1111
// ========================================================================
1212
//
1313

14-
package org.eclipse.jetty.security.siwe.internal;
14+
package org.eclipse.jetty.security;
1515

1616
import java.util.function.Function;
1717
import javax.security.auth.Subject;
1818

19-
import org.eclipse.jetty.security.DefaultIdentityService;
20-
import org.eclipse.jetty.security.IdentityService;
21-
import org.eclipse.jetty.security.LoginService;
22-
import org.eclipse.jetty.security.UserIdentity;
23-
import org.eclipse.jetty.security.UserPrincipal;
2419
import org.eclipse.jetty.server.Request;
2520
import org.eclipse.jetty.server.Session;
2621

2722
/**
2823
* A {@link LoginService} which allows unknown users to be authenticated.
24+
* <p>This is useful for authentication protocols like OpenID Connect and Sign in With Ethereum, where Jetty doesn't store
25+
* a collection of user credentials and passwords. Once the user proves authenticates themselves through the respective
26+
* protocol, Jetty does not have to validate any credential.</p>
2927
* <p>
30-
* This can delegate to a nested {@link LoginService} if it is supplied to the constructor, it will first attempt to log in
28+
* This can delegate to a nested {@link LoginService} which can supply roles for known users.
29+
* This nested {@link LoginService} is supplied to the constructor, and this will first attempt to log in
3130
* with the nested {@link LoginService} and only create a new {@link UserIdentity} if none was found with
32-
* {@link LoginService#login(String, Object, Request, Function)}.
31+
* {@link LoginService#login(String, Object, Request, Function)}
3332
* </p>
33+
* <p>This {@link LoginService} does not check credentials, a {@link UserIdentity} will be produced for any
34+
* username provided in {@link #login(String, Object, Request, Function)}.</p>
3435
*/
3536
public class AnyUserLoginService implements LoginService
3637
{

jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/Authenticator.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public interface Authenticator
4242
String NEGOTIATE_AUTH = "NEGOTIATE";
4343
String OPENID_AUTH = "OPENID";
4444
String SIWE_AUTH = "SIWE";
45+
String MULTI_AUTH = "MULTI";
4546

4647
/**
4748
* Configure the Authenticator

jetty-core/jetty-security/src/main/java/org/eclipse/jetty/security/DefaultAuthenticatorFactory.java

Lines changed: 68 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
package org.eclipse.jetty.security;
1515

1616
import java.util.Collection;
17+
import java.util.List;
1718

1819
import org.eclipse.jetty.security.Authenticator.Configuration;
1920
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
@@ -26,6 +27,7 @@
2627
import org.eclipse.jetty.security.internal.DeferredAuthenticationState;
2728
import org.eclipse.jetty.server.Context;
2829
import org.eclipse.jetty.server.Server;
30+
import org.eclipse.jetty.util.StringUtil;
2931
import org.eclipse.jetty.util.ssl.SslContextFactory;
3032

3133
/**
@@ -53,27 +55,74 @@ public class DefaultAuthenticatorFactory implements Authenticator.Factory
5355
@Override
5456
public Authenticator getAuthenticator(Server server, Context context, Configuration configuration)
5557
{
56-
String auth = configuration.getAuthenticationType();
57-
Authenticator authenticator = null;
58+
String auth = StringUtil.asciiToUpperCase(configuration.getAuthenticationType());
59+
if (auth == null)
60+
return null;
5861

59-
if (Authenticator.BASIC_AUTH.equalsIgnoreCase(auth))
60-
authenticator = new BasicAuthenticator();
61-
else if (Authenticator.DIGEST_AUTH.equalsIgnoreCase(auth))
62-
authenticator = new DigestAuthenticator();
63-
else if (Authenticator.FORM_AUTH.equalsIgnoreCase(auth))
64-
authenticator = new FormAuthenticator();
65-
else if (Authenticator.SPNEGO_AUTH.equalsIgnoreCase(auth))
66-
authenticator = new SPNEGOAuthenticator();
67-
else if (Authenticator.NEGOTIATE_AUTH.equalsIgnoreCase(auth)) // see Bug #377076
68-
authenticator = new SPNEGOAuthenticator(Authenticator.NEGOTIATE_AUTH);
69-
if (Authenticator.CERT_AUTH2.equalsIgnoreCase(auth))
62+
return switch (auth)
7063
{
71-
Collection<SslContextFactory> sslContextFactories = server.getBeans(SslContextFactory.class);
72-
if (sslContextFactories.size() != 1)
73-
throw new IllegalStateException("SslClientCertAuthenticator requires a single SslContextFactory instances.");
74-
authenticator = new SslClientCertAuthenticator(sslContextFactories.iterator().next());
75-
}
64+
case Authenticator.BASIC_AUTH -> new BasicAuthenticator();
65+
case Authenticator.DIGEST_AUTH -> new DigestAuthenticator();
66+
case Authenticator.FORM_AUTH -> new FormAuthenticator();
67+
case Authenticator.SPNEGO_AUTH -> new SPNEGOAuthenticator();
68+
case Authenticator.NEGOTIATE_AUTH -> new SPNEGOAuthenticator(Authenticator.NEGOTIATE_AUTH); // see Bug #377076
69+
case Authenticator.MULTI_AUTH -> getMultiAuthenticator(server, context, configuration);
70+
case Authenticator.CERT_AUTH, Authenticator.CERT_AUTH2 ->
71+
{
72+
Collection<SslContextFactory> sslContextFactories = server.getBeans(SslContextFactory.class);
73+
if (sslContextFactories.size() != 1)
74+
throw new IllegalStateException("SslClientCertAuthenticator requires a single SslContextFactory instances.");
75+
yield new SslClientCertAuthenticator(sslContextFactories.iterator().next());
76+
}
77+
default -> null;
78+
};
79+
}
7680

77-
return authenticator;
81+
private Authenticator getMultiAuthenticator(Server server, Context context, Authenticator.Configuration configuration)
82+
{
83+
SecurityHandler securityHandler = SecurityHandler.getCurrentSecurityHandler();
84+
if (securityHandler == null)
85+
return null;
86+
87+
String auth = configuration.getAuthenticationType();
88+
if (Authenticator.MULTI_AUTH.equalsIgnoreCase(auth))
89+
{
90+
MultiAuthenticator multiAuthenticator = new MultiAuthenticator();
91+
92+
String authenticatorConfig = configuration.getParameter("org.eclipse.jetty.security.multi.authenticators");
93+
for (String config : StringUtil.csvSplit(authenticatorConfig))
94+
{
95+
String[] parts = config.split(":");
96+
if (parts.length != 2)
97+
throw new IllegalArgumentException();
98+
99+
String authType = parts[0].trim();
100+
String pathSpec = parts[1].trim();
101+
102+
Authenticator.Configuration.Wrapper authConfig = new Authenticator.Configuration.Wrapper(configuration)
103+
{
104+
@Override
105+
public String getAuthenticationType()
106+
{
107+
return authType;
108+
}
109+
};
110+
111+
Authenticator authenticator = null;
112+
List<Authenticator.Factory> authenticatorFactories = securityHandler.getKnownAuthenticatorFactories();
113+
for (Authenticator.Factory factory : authenticatorFactories)
114+
{
115+
authenticator = factory.getAuthenticator(server, context, authConfig);
116+
if (authenticator != null)
117+
break;
118+
}
119+
120+
if (authenticator == null)
121+
throw new IllegalStateException();
122+
multiAuthenticator.addAuthenticator(pathSpec, authenticator);
123+
}
124+
return multiAuthenticator;
125+
}
126+
return null;
78127
}
79128
}

0 commit comments

Comments
 (0)