Closed xresch closed 4 years ago
I have the same issue. There is no example of how to use this new class ConfigurableSpnegoLoginService along with AuthorizationService.
I tried new ConfigurableSpnegoLoginService("realm", AuthorizationService.from(null, null)));
But it doesn't work :(
AuthorizationService
is a way to get a UserIdentity
from a the current HttpServletRequest
and username.
When you use AuthorizationService.from(LoginService, Object)
you use the LoginService
implementation of your choice to establish a lambda that will perform the login.
Note: LoginService.login(String username, Object credentials, ServletRequest request)
is used when you call AuthorizationService.from()
.
For list of LoginService
choices, see https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/security/LoginService.html
Example of usage:
String realm = "my.site.org";
Path realmPropsPath = Paths.get("/path/to/realm.properties");
HashLoginService authorizationService = new HashLoginService(realm, realmPropsPath.toString());
ConfigurableSpnegoLoginService loginService = new ConfigurableSpnegoLoginService(realm, AuthorizationService.from(authorizationService, ""));
Which is perfectly reasonable configuration.
I'm trying to upgrade my current code which is working fine with Jetty 9.4.12.v20180830, to the new one - Jetty 9.4.27.v20200227 (SpnegoAuthenticator and SpnegoLoginService are deprecated)
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setAuthenticator(new SpnegoAuthenticator());
securityHandler.setLoginService(new SpnegoLoginService("realm", "/path/to/spnego.properties"));
securityHandler.setRealmName("realm");
securityHandler.setConstraintMappings(mappings);
ServletContextHandler contextHandler = server.getBean(ServletContextHandler.class);
contextHandler.setSecurityHandler(securityHandler);
The new code is not working at all
SpnegoLoginService authorizationService = new SpnegoLoginService("realm", "/path/to/spnego.properties");
ConfigurableSpnegoLoginService loginService = new ConfigurableSpnegoLoginService(properties.getDomainRealm(), AuthorizationService.from(authorizationService, ""));
loginService.setKeyTabPath(Paths.get("/path/to/keytab-file"));
loginService.setServiceName("HTTP");
loginService.setHostName("host");
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setAuthenticator(new ConfigurableSpnegoAuthenticator());
securityHandler.setLoginService(loginService);
securityHandler.setRealmName("realm");
securityHandler.setConstraintMappings(mappings);
ServletContextHandler contextHandler = server.getBean(ServletContextHandler.class);
contextHandler.setSecurityHandler(securityHandler);
And BTW SpnegoLoginService which I'm using in the AuthorizationService is deprecated.
Would appreciate any help.
Thanks, -AJ
Example usages can be found in this test case:
Look especially at the prepare()
method to setup the KDC, and at the startSPNEGO()
method to setup Jetty on the server side.
The Jetty client side configuration is present in every test.
Note that the test case interoperates correctly with Apache Kerby so it should interoperate with any other compliant KDC implementation.
in the AuthorizationService is deprecated.
org.eclipse.jetty.security.authentication.AuthorizationService
is not deprecated.
AuthorizationService is not deprecated but SpnegoLoginService does, and I'm using it in the AuthorizationService.from....
SpnegoLoginService authorizationService = new SpnegoLoginService("realm", "/path/to/spnego.properties");
ConfigurableSpnegoLoginService loginService = new ConfigurableSpnegoLoginService("realm", AuthorizationService.from(authorizationService, ""));
The SpnegoLoginService
was deprecated as part of the work on Issue #2868
Example usages can be found in this test case:
Look especially at the
prepare()
method to setup the KDC, and at thestartSPNEGO()
method to setup Jetty on the server side. The Jetty client side configuration is present in every test.Note that the test case interoperates correctly with Apache Kerby so it should interoperate with any other compliant KDC implementation.
Thanks, but in my case I'm not using properties file with usernames/pwd. I want to use GSSAPI Negotiation, since user already authenticated via AD (windows ntlm).
I don't know but it might be important, code is running on Solaris 10. Again no issues with old code - works perfectly.
The
SpnegoLoginService
was deprecated as part of the work on Issue #2868
Thanks. So then what LoginService should I use in AuthorizationService.from(...), to kick off GSSAPI Negotiation...
SpnegoLoginService authorizationService = new SpnegoLoginService("realm", "/path/to/spnego.properties"); ConfigurableSpnegoLoginService loginService = new ConfigurableSpnegoLoginService("realm", AuthorizationService.from(authorizationService, ""));
That's non-sensical code. You can't have ConfigurableSpnegoLoginService be based on SpnegoLoginService. It's a loop with no authorization service, no source for user identities, and no roles.
Your AuthorizationService needs to be something that handles the realm and login for you, which isn't Spnego.
Your old code did this ...
securityHandler.setLoginService(new SpnegoLoginService("realm", "/path/to/spnego.properties"));
That's a SpnegoLoginService with a realm called "realm" and a spnego.properties that simply sets the "targetName" to whatever you have/need it for (which you configured for {serviceName}@{hostName}
).
That old SpnegoLoginService still needed an IdentityService, and your code snippets do not show you setting one.
It has to be there, otherwise you have no UserIdentities, no roles, no successful logins, no Spnego established, zilch. Pretty much an environment with no real constraints applied on it.
My bet is that you have a LDAP source declared as your IdentityService.
In ConfiguredSpnegoLoginService you still have to declare the realm, serviceName, and hostName, but not via a properties file (which you have done). But now you have to declare the LoginService implementation you want to use for the AuthorizationService handling (where the roles and UserIdentities come from). You have many choices, you have to pick one, and not rely on any kind of "default" or "unset" behavior (like the old SpnegoLoginService). As pointed out in a prior comment, you have many LoginService implementations to choose from. See "All Known Implementing Classes" at https://www.eclipse.org/jetty/javadoc/current/org/eclipse/jetty/security/LoginService.html Pick one that best fits your needs. Examples: a realm properties file, a JDBC database, a DataSource, a JAAS source (like ldap), an OpenID service, etc...
@ajleetch89 please show the code and configuration that works, and then the code and configuration that does not work, separately.
Also, you don't really want to do GSS yourself - I think you just need to find the configuration that works in your case.
@ajleetch89 please show the code and configuration that works, and then the code and configuration that does not work, separately.
Also, you don't really want to do GSS yourself - I think you just need to find the configuration that works in your case.
Exactly !!
Exactly !!
Well, so far we don't understand what you're trying to do - "does not work" is not very helpful. Nor we have seen clear examples of your code. Nor we have seen DEBUG logs.
new ConfigurableSpnegoLoginService
That's what I'm trying to figure how use ConfigurableSpnegoLoginService, considering what was "default"/"unset" in the previous SpnegoLoginService. Since I was simple just set it up like this:
SpnegoLoginService authorizationService = new SpnegoLoginService("realm", "/path/to/spnego.properties");
I have no idea what is the "default"/"unset" IdentityService been used. I'm running embbeded Jetty with Spring Boot.
@ajleetch89 you continue to post half-lines of random code, and we don't understand what you're talking about.
How about, for a change, you post the full code that was working for you before, and the full code that you are writing now? How about you also attach full DEBUG logs so we can see what is not working?
Exactly !!
Well, so far we don't understand what you're trying to do - "does not work" is not very helpful. Nor we have seen clear examples of your code. Nor we have seen DEBUG logs.
@Override
public void customize(JettyServletWebServerFactory factory) {
factory.addServerCustomizers((Server server) -> {
List<ConstraintMapping> mappings = new LinkedList<>();
Constraint portal = ConstraintSecurityHandler.createConstraint(Constraint.__SPNEGO_AUTH, true, new String[]{properties.getDomainRealm()}, Constraint.DC_CONFIDENTIAL);
ConstraintMapping portalMapping = new ConstraintMapping();
portalMapping.setConstraint(portal);
portalMapping.setPathSpec("/*");
mappings.add(portalMapping);
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setAuthenticator(new SpnegoAuthenticator());
securityHandler.setLoginService(new SpnegoLoginService("realm" "/path-to-spengo"));
securityHandler.setRealmName("realm");
securityHandler.setConstraintMappings(mappings);
ServletContextHandler contextHandler = server.getBean(ServletContextHandler.class);
contextHandler.setSecurityHandler(securityHandler);
});
}
Who to make it work with new classes ConfigurableSpnegoLoginService and ConfigurableSpnegoAuthenticator.
That has no IdentityService
, where is the IdentityService
coming from in your environment?
Since you mentioned SpringBoot, it could be coming from dozens of different locations, including SpringBoot itself.
You should probably start debugging the working configuration and figure out what your SpnegoLoginService.getIdentityService()
returns AFTER it has been started (not while it's being configured).
That has no
IdentityService
, where is theIdentityService
coming from in your environment?Since you mentioned SpringBoot, it could be coming from dozens of different locations, including SpringBoot itself. You should probably start debugging the working configuration and figure out what your
SpnegoLoginService.getIdentityService()
returns AFTER it has been started (not while it's being configured).
It's DefaultIdentityService
. I don't explicitly set it in the code. Jetty set it in doStart() after sometime.
DefaultIdentityService
is basically a no-op IdentityService
used for tests/testing.
Your "working configuration" has no roles, has no real security, and performs no login against spnego.
To be fair, this isn't wholly accurate, it did ask GSS for a new server credentials, but those server credential has no meaning / value to a user without a valid login to spengo, and an IdentityService to supply the roles for your users.
Your "working configuration" has the following features non-functional.
This scenario is one of many that caused the change to ConfiguredSpnegoLoginService
, making it mandatory to declare how the login/user identity works in the constructor.
The old SpnegoLoginService
required you to declare a valid IdentityService
that pointed to something for the login (like ldap) for it to actually do something.
But that was unclear and many folks (including yourself) didn't bother to setup the IdentityService
properly.
Now you have to work with ConfiguredSpnegoLoginService
, and it needs a login mechanism (aka a LoginService
) to obtain the usernames / roles from, which is wrapped in the ConfiguredSpnegoLoginService
via a lambda called AuthorizationService
(which calls this LoginService
for each attempt to login via spnego). You have to choose where these roles associated to usernames come from. That LoginService
you choose will be used to perform the login and obtain the usernames to roles mappings that the Servlet side needs.
To say this a different way, the following steps happend for working with spnego in servlet.
In your "working configuration" you stop at step 4 because of the DefaultIdentityService
, and you result in an no-op User Identity with no roles or abilities.
Which doesn't allow the Constraint
you have to be applied in allow or deny mode, so it also results in a no-op.
In the ConfiguredSpnegoLoginService
configuration you need to declare a LoginService
that handles step 7 and 8.
I use J2eePreAuthenticatedProcessingFilter
in Spring Boot, and the main goal of Spnego/Kerberos in mine Jetty setup to get user principal, since user already authenticated thru windows ntlm. After final negotiation round Jetty/SPNEGO have user principal, I use to lookup datastore where it mapped to specific application role.
Sounds like what you are looking for is a generic spnego library, not a spnego implementation of the servlet spec authentication and authorization layers.
Jetty provides the Servlet spec Authentication and Authorization layers.
The fact that you are using Constraint
, ConstraintMapping
, and ConstraintSecurityHandler
means you want this Servlet spec authentication and authorization layers, not a generic spnego library.
All of those require properly defined roles at the time that those specifically execute, which you are not providing with either your old "working configuration" or the new ConfiguredSpnegoLoginService
.
Also, all of the Servlet defined authentication and authorization configurations apply before the servlet filter chain.
So your J2eePreAuthenticatedProcessingFilter
would execute after the Constraint
is applied to the incoming request.
Hi joakime and sbordet,
as I started this thread I want to thank you both for all the answers so far. I understood that my approach seems not the correct way to achieve what I need. I have a similar use case as ajleetch89, my employeer asks for the ability to use Kerberos authentication, but the only thing I actually need to fetch from the Kerberos Authentication is the user principal as everything else like roles are already managed in the DB.
As security as quite a topic and you seem to have already a loot of professional knowledge in this area, and I'm not too deep into it in present time, can you give me a hint about the main classes I need to look into to implement the "Servlet spec Authentication and Authorization layers" to get it working with Kerberos and Jetty?
Thanks and Regards Reto
... if you already have a working example that would be helpful too.
I have a similar use case as ajleetch89, my employeer asks for the ability to use Kerberos authentication, but the only thing I actually need to fetch from the Kerberos Authentication is the user principal as everything else like roles are already managed in the DB.
This sounds like ConfiguredSpnegoLoginService
(the authentication piece) configured with a AuthorizationService
based on JDBCLoginService
(the authorization piece where the user principal and roles come from).
Thanks, checked JDBCLoginService, seems not really what I want but I helped me, I will most probably create a custom login service based on that one.
With the following code I'm able to get prompted by the browser for credentials:
private ConstraintSecurityHandler createSPNEGOSecurityHandler() throws Exception {
System.setProperty("javax.security.auth.useSubjectCredsOnly", "false");
System.setProperty("java.security.auth.login.config", "./config/kerberos/spnego.conf");
System.setProperty("java.security.krb5.conf", "./config/kerberos/krb5.conf");
System.setProperty("sun.security.krb5.debug", "true");
System.setProperty("sun.security.jgss.debug", "true");
System.setProperty("java.security.debug", "all");
String domainRealm = "EXAMPLE.COM";
CFWLoginService authorizationService = new CFWLoginService();
ConfigurableSpnegoLoginService loginService = new ConfigurableSpnegoLoginService(domainRealm, AuthorizationService.from(authorizationService, ""));
loginService.addBean(authorizationService);
loginService.setKeyTabPath(Paths.get("./config/kerberos/cfw.keytab"));
loginService.setServiceName("ldap");
loginService.setHostName("example.net");
server.addBean(loginService);
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
Constraint constraint = new Constraint();
constraint.setName(Constraint.__SPNEGO_AUTH);
constraint.setRoles(new String[]{domainRealm});
constraint.setAuthenticate(true);
ConstraintMapping mapping = new ConstraintMapping();
mapping.setPathSpec("/app/*");
mapping.setConstraint(constraint);
securityHandler.addConstraintMapping(mapping);
ConfigurableSpnegoAuthenticator authenticator = new ConfigurableSpnegoAuthenticator();
securityHandler.setAuthenticator(authenticator);
securityHandler.setLoginService(loginService);
return securityHandler;
}
While CFWLoginService is currently just a stub class extending AbstractLoginService :
public class CFWLoginService extends AbstractLoginService {
@Override
protected String[] loadRoleInfo(UserPrincipal user) {
System.out.println("loadRoleInfo:"+user.toString());
return null;
}
@Override
protected UserPrincipal loadUserInfo(String username) {
System.out.println("loadUserInfo:"+username);
UserPrincipal principal = new UserPrincipal(username, Credential.getCredential(""));
return principal;
}
}
As of now I end up with the following exception, so above CFWLoginService is never reached:
GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
But I think my question is answered so far. Thanks for the support and keep up the good work.
Regards Reto
If you are making a custom LoginService
, look at the HashLoginService
, it's probably the easiest one to follow (and it also uses AbstractLoginService
which does most of the heavy lifting).
Nulls are rarely appropriate for Constraints to work.
So make sure you are populating roles at a minimum. (the roles are used in the Constraint
you define)
Also, the UserPrincipal.authenticate()
methods need to function, so that means your Credential
needs to be sane.
Alternatively, if you want to make it even simpler, just implement a custom AuthorizationService
and skip the LoginService
delegation entirely.
package jetty;
import java.security.Principal;
import javax.security.auth.Subject;
import javax.servlet.http.HttpServletRequest;
import org.eclipse.jetty.security.DefaultUserIdentity;
import org.eclipse.jetty.security.authentication.AuthorizationService;
import org.eclipse.jetty.server.UserIdentity;
public class CustomAuthorizationService implements AuthorizationService
{
/**
* @param request the current HTTP request
* @param name the user name
* @return a {@link UserIdentity} to query for roles of the given user
*/
@Override
public UserIdentity getUserIdentity(HttpServletRequest request, String name)
{
Subject subject = ...; // TODO
Principal userPrincipal = ...; // TODO
String[] roles = ...; // TODO
return new DefaultUserIdentity(subject, userPrincipal, roles);
}
}
@xresch please have a look at https://www.roguelynn.com/words/explain-like-im-5-kerberos/.
Jetty's HttpClient
does the client-side steps indicated in the link above.
If you use a browser, the browser should do those steps as well.
In either case, the client will have a SGT (Service Granting Ticket) for the HTTP server you want to send requests to.
On the server-side, Jetty must be configured with SPNEGO, similarly to what you have done above, so that it can decrypt the SGT, typically configuring a keyTab for the service.
Note also that the service name and the service host play an important role here, but really depends on the Kerberos system you are using - you want to play with those, and see again the test case where the format must be serviceName/serviceHost
.
Then you make an HTTP request; client and server exchange a number (1 or more) HTTP requests to authenticate with each other; when they are happy, the server has the client user name, which is passed to AuthorizationService.getUserIdentity(...)
to get a UserIdentity
, so that it can fulfill the Servlet APIs.
What remains is for you to protect your resources and allow only certain roles to access them, typically in a web.xml
or using embedded code like shown in the test case linked above.
I'm closing this issue, as the OP's question appears to have been answered adequately - if this is not the case, please reopen with new information.
Jetty version 9.4.15
Java version 1.8
Question The Class SpnegoLoginService is marked as deprecated and it says it should be replaced with ConfigurableSpnegoLoginService. I started to implement it but had troubles as the Constructor asks for an instance of the interface AuthorizationService, that I don't know how to implement.
Do you have any example on how to use ConfigurableSpnegoLoginService and ConfigurableSpnegoAuthenticator?
My goal is to authenticate against Kerberos and get the principals username and other details like firstname, lastname and email.
Below is what I tried so far based on some code I found on this stackoverflow question: https://stackoverflow.com/questions/27427654/how-to-use-embedded-jetty-server-9-with-kerberos-authentication