Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #5442 - add MultiAuthenticator to support multiple authentication options #12393

Open
wants to merge 6 commits into
base: jetty-12.1.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.security;

import java.util.function.Function;
import javax.security.auth.Subject;

import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Session;

/**
* A {@link LoginService} which allows unknown users to be authenticated.
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
* <p>
* This can delegate to a nested {@link LoginService} if it is supplied to the constructor, it will first attempt to log in
* with the nested {@link LoginService} and only create a new {@link UserIdentity} if none was found with
* {@link LoginService#login(String, Object, Request, Function)}.
* </p>
lachlan-roberts marked this conversation as resolved.
Show resolved Hide resolved
*/
public class AnyUserLoginService implements LoginService
{
private final String _realm;
private final LoginService _loginService;
private IdentityService _identityService;

/**
* @param realm the realm name.
* @param loginService optional {@link LoginService} which can be used to assign roles to known users.
*/
public AnyUserLoginService(String realm, LoginService loginService)
{
_realm = realm;
_loginService = loginService;
_identityService = (loginService == null) ? new DefaultIdentityService() : null;
}

@Override
public String getName()
{
return _realm;
}

@Override
public UserIdentity login(String username, Object credentials, Request request, Function<Boolean, Session> getOrCreateSession)
{
if (_loginService != null)
{
UserIdentity login = _loginService.login(username, credentials, request, getOrCreateSession);
if (login != null)
return login;

UserPrincipal userPrincipal = new UserPrincipal(username, null);
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
if (credentials != null)
subject.getPrivateCredentials().add(credentials);
subject.setReadOnly();
return _loginService.getUserIdentity(subject, userPrincipal, true);
}

UserPrincipal userPrincipal = new UserPrincipal(username, null);
Subject subject = new Subject();
subject.getPrincipals().add(userPrincipal);
if (credentials != null)
subject.getPrivateCredentials().add(credentials);
subject.setReadOnly();
return _identityService.newUserIdentity(subject, userPrincipal, new String[0]);
}

@Override
public boolean validate(UserIdentity user)
{
if (_loginService == null)
return user != null;
return _loginService.validate(user);
}

@Override
public IdentityService getIdentityService()
{
return _loginService == null ? _identityService : _loginService.getIdentityService();
}

@Override
public void setIdentityService(IdentityService service)
{
if (_loginService != null)
_loginService.setIdentityService(service);
else
_identityService = service;
}

@Override
public void logout(UserIdentity user)
{
if (_loginService != null)
_loginService.logout(user);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public interface Authenticator
String NEGOTIATE_AUTH = "NEGOTIATE";
String OPENID_AUTH = "OPENID";
String SIWE_AUTH = "SIWE";
String MULTI_AUTH = "MULTI";

/**
* Configure the Authenticator
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package org.eclipse.jetty.security;

import java.util.Collection;
import java.util.List;

import org.eclipse.jetty.security.Authenticator.Configuration;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
Expand All @@ -26,6 +27,7 @@
import org.eclipse.jetty.security.internal.DeferredAuthenticationState;
import org.eclipse.jetty.server.Context;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.ssl.SslContextFactory;

/**
Expand Down Expand Up @@ -53,27 +55,74 @@ public class DefaultAuthenticatorFactory implements Authenticator.Factory
@Override
public Authenticator getAuthenticator(Server server, Context context, Configuration configuration)
{
String auth = configuration.getAuthenticationType();
Authenticator authenticator = null;
String auth = StringUtil.asciiToUpperCase(configuration.getAuthenticationType());
if (auth == null)
return null;

if (Authenticator.BASIC_AUTH.equalsIgnoreCase(auth))
authenticator = new BasicAuthenticator();
else if (Authenticator.DIGEST_AUTH.equalsIgnoreCase(auth))
authenticator = new DigestAuthenticator();
else if (Authenticator.FORM_AUTH.equalsIgnoreCase(auth))
authenticator = new FormAuthenticator();
else if (Authenticator.SPNEGO_AUTH.equalsIgnoreCase(auth))
authenticator = new SPNEGOAuthenticator();
else if (Authenticator.NEGOTIATE_AUTH.equalsIgnoreCase(auth)) // see Bug #377076
authenticator = new SPNEGOAuthenticator(Authenticator.NEGOTIATE_AUTH);
if (Authenticator.CERT_AUTH2.equalsIgnoreCase(auth))
return switch (auth)
{
Collection<SslContextFactory> sslContextFactories = server.getBeans(SslContextFactory.class);
if (sslContextFactories.size() != 1)
throw new IllegalStateException("SslClientCertAuthenticator requires a single SslContextFactory instances.");
authenticator = new SslClientCertAuthenticator(sslContextFactories.iterator().next());
}
case Authenticator.BASIC_AUTH -> new BasicAuthenticator();
case Authenticator.DIGEST_AUTH -> new DigestAuthenticator();
case Authenticator.FORM_AUTH -> new FormAuthenticator();
case Authenticator.SPNEGO_AUTH -> new SPNEGOAuthenticator();
case Authenticator.NEGOTIATE_AUTH -> new SPNEGOAuthenticator(Authenticator.NEGOTIATE_AUTH); // see Bug #377076
case Authenticator.MULTI_AUTH -> getMultiAuthenticator(server, context, configuration);
case Authenticator.CERT_AUTH, Authenticator.CERT_AUTH2 ->
{
Collection<SslContextFactory> sslContextFactories = server.getBeans(SslContextFactory.class);
if (sslContextFactories.size() != 1)
throw new IllegalStateException("SslClientCertAuthenticator requires a single SslContextFactory instances.");
yield new SslClientCertAuthenticator(sslContextFactories.iterator().next());
}
default -> null;
};
}

return authenticator;
private Authenticator getMultiAuthenticator(Server server, Context context, Authenticator.Configuration configuration)
{
SecurityHandler securityHandler = SecurityHandler.getCurrentSecurityHandler();
if (securityHandler == null)
return null;

String auth = configuration.getAuthenticationType();
if (Authenticator.MULTI_AUTH.equalsIgnoreCase(auth))
{
MultiAuthenticator multiAuthenticator = new MultiAuthenticator();

String authenticatorConfig = configuration.getParameter("org.eclipse.jetty.security.multi.authenticators");
for (String config : StringUtil.csvSplit(authenticatorConfig))
{
String[] parts = config.split(":");
if (parts.length != 2)
throw new IllegalArgumentException();

String authType = parts[0].trim();
String pathSpec = parts[1].trim();

Authenticator.Configuration.Wrapper authConfig = new Authenticator.Configuration.Wrapper(configuration)
{
@Override
public String getAuthenticationType()
{
return authType;
}
};

Authenticator authenticator = null;
List<Authenticator.Factory> authenticatorFactories = securityHandler.getKnownAuthenticatorFactories();
for (Authenticator.Factory factory : authenticatorFactories)
{
authenticator = factory.getAuthenticator(server, context, authConfig);
gregw marked this conversation as resolved.
Show resolved Hide resolved
if (authenticator != null)
break;
}

if (authenticator == null)
throw new IllegalStateException();
multiAuthenticator.addAuthenticator(pathSpec, authenticator);
}
return multiAuthenticator;
}
return null;
}
}
Loading
Loading