简体   繁体   中英

Basic authentication with Camel Jetty

I want to implement basic authentication for Jetty server programmatically, as shown here . For the sake of convenience, I am ^C-^V'ing that snippet here.

import org.mortbay.jetty.security.*;

Server server = new Server();

Connector connector = new SelectChannelConnector();
connector.setPort(8080);
server.setConnectors(new Connector[]{connector});

Constraint constraint = new Constraint();
constraint.setName(Constraint.__BASIC_AUTH);;
constraint.setRoles(new String[]{"user","admin","moderator"});
constraint.setAuthenticate(true);

ConstraintMapping cm = new ConstraintMapping();
cm.setConstraint(constraint);
cm.setPathSpec("/*");

SecurityHandler sh = new SecurityHandler();
sh.setUserRealm(new HashUserRealm("MyRealm",System.getProperty("jetty.home")+"/etc/realm.properties"));
sh.setConstraintMappings(new ConstraintMapping[]{cm});

WebAppContext webappcontext = new WebAppContext();
webappcontext.setContextPath("/mywebapp");
webappcontext.setWar("./path/to/my/war/orExplodedwar");
webappcontext.addHandler(sh);

HandlerCollection handlers= new HandlerCollection();
handlers.setHandlers(new Handler[]{webappcontext, new DefaultHandler()});

server.setHandler(handlers);
server.start();
server.join();

Now the problem is that the above approach requires you to have a handle to the server. However in my case, since I am using Camel, I do not have a direct access to the server. This is how my pipeline is defined:

from("jetty:http://localhost:8080/documents_in?matchOnUriPrefix=true").
  process(new MyProcessor());

How do I adapt the linked authentication solution to my case? Or do I have to follow some completely different method?

Please note that I am both a Camel and Jetty novice. Any help will be greatly appreciated. Thanks.

Addendum:

This page shows how to do it with Spring XML, however we are not using Spring, so that's of no use to us.

I stumbled across this problem a couple of days ago and I solved this issue by defining an own implementation of ConstraintSecurityHandler which uses a customized LoginService which takes care of the authentication BasicAuthenticator requires. As I did not find any existing LoginService implementation that is capable of dealing with bean-managed authentication, I needed to come up with this solution.

I'll post the almost complete class except for the internal stuff which has to be kept private.

import java.security.Principal;

import javax.annotation.Resource;
import javax.security.auth.Subject;

import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.DefaultIdentityService;
import org.eclipse.jetty.security.IdentityService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.MappedLoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.UserIdentity;
import org.eclipse.jetty.util.security.Constraint;
import org.eclipse.jetty.util.security.Credential;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;

/**
 * <p>
 * Sets up a basic authentication mechanism for REST based services exposed via
 * Jetty for our REST API (http(s)://server:port/api/v1/...).
 * </p>
 * <p>
 * It moreover defines a login service which is capable of using an internal
 * persistence layer for authenticating a user and his credentials received via
 * a challenge response against a user entity retrieved via the persistence 
 * layer.
 * </p>
 */
public class JettyBasicAuthAuthorizationHandler extends ConstraintSecurityHandler
{ 
    /** The logger of this class **/
    private static final Logger logger = 
            LoggerFactory.getLogger(JettyBasicAuthAuthorizationHandler.class);

    /** The persistence service to retrieve the user informations from **/
    @Resource
    private ISomePersistenceService persistenceService;

    private final String[] roles = new String[] {"user"};

    /**
     * <p>
     * Initializes a Jetty based Basic Authentication mechanism.
     * </p>
     */
    public JettyBasicAuthAuthorizationHandler()
    {
        super();

        // Specifies the challenge to be of type BASIC and that users have
        // to fulfill one of the roles listed in roles. Moreover authentication
        // is required
        Constraint constraint = new Constraint();
        constraint.setName(Constraint.__BASIC_AUTH);
        constraint.setRoles(this.roles);
        constraint.setAuthenticate(true);

        // Map the defined constraints from above to the services provided via 
        // our REST API 
        ConstraintMapping cm = new ConstraintMapping();
        cm.setConstraint(constraint);
        cm.setPathSpec("/api/v1/*");

        // BasicAuthenticator takes care of sending a challenge to the caller
        // and calls our login service in case of a challenge response to
        // evaluate if the user is permitted to use the service.
        // The realm name defines the name of the login service which should be
        // used for authentication.
        BasicAuthenticator basic = new BasicAuthenticator();
        this.setAuthenticator(basic);
        this.addConstraintMapping(cm);
        this.setRealmName("REST");
        this.setLoginService(new BeanManagedLoginService("REST"));

        logger.debug("JettyBasicAuthAuthorizationHandler created!");
    }

    /**
     * <p>
     * Implements a bean managed login service where an authentication response
     * is propagated to a business layer bean which retrieves the user and 
     * credentials from a backing data store.
     * </p>
     */
    class BeanManagedLoginService implements LoginService
    {       
        /** An identity service used to create a UserIdentity object for us **/
        private IdentityService identityService = new DefaultIdentityService();

        private String name = "REST";

        /**
         * <p>
         * Initializes a new instance.
         * </p>
         */
        public BeanManagedLoginService()
        {

        }

        /**
         * <p>
         * Initializes a new instance and sets the realm name this login service 
         * will work for.
         * </p>
         * 
         * @param name The name of this login service (also known as the realm it
         *             will work for)
         */
        public BeanManagedLoginService(String name)
        {
            this.name = name;
        }

        /**
         * <p>
         * Returns the name of the login service (the realm name)
         * </p>
         * 
         * @return Get the name of the login service (aka Realm name)
         */
        @Override
        public String getName() 
        {
            return this.name;
        }

        /**
         * <p>
         * Logs in a user by checking the username with known users and 
         * comparing the credentials with the stored ones. If the user could not
         * be authenticated successfully an unauthenticated user identity will 
         * be returned.
         * </p>
         * 
         * @param username The user name as sent by the ChallengeResponse
         * @param credentials The credentials provided in the ChallengeResponse
         * 
         * @return If the user could be authenticated successfully a valid 
         * {@link UserIdentity}, else an unauthorized user identity
         */
        @Override
        public UserIdentity login(String username, Object credentials) 
        {
            if (logger.isDebugEnabled())
                logger.debug("received login request for user: '{}' with credentials: '{}'!", 
                    username, credentials);

            // check if the username is valid
            if (!Strings.isNullOrEmpty(username))
            {
                String password = credentials.toString();

                // retrieve the user from the business layer
                final UserEntity sue = persistenceService.findUser(username);
                if (sue == null)
                {
                    if (logger.isErrorEnabled())
                        logger.error("No User could be found for UserId '{}'. The UserKey (which was not checked) is '{}'",
                            username, password);
                    return UserIdentity.UNAUTHENTICATED_IDENTITY;
                }
                // check whether the password matches the one in the user entity
                // found for the user id
                if (password.equals(sue.getUserKey())) 
                {
                    // the user could be successfully authenticated
                    if (logger.isDebugEnabled())
                        logger.debug("UserKey {} of User {} was successfully authenticated",
                            sue.getUserKey(), sue.getUserId());

                    if (logger.isDebugEnabled())
                        logger.debug("User '{}'/'{}' works for '{}'", 
                                userId, userName, sue.getCompany().getName());
                    return this.createIdentityForUser(username, password);
                } 
                else 
                {
                    // the password set in the request and the one stored in the 
                    // user entity do not match
                    if (logger.isErrorEnabled())
                        logger.error(
                            "User {} could not be authenticated. The UserKey in the user entity is {} but the UserKey in the request was {}",
                            new Object[] { username, sue.getUserKey(), password });
                    return UserIdentity.UNAUTHENTICATED_IDENTITY;
                }               
            }
            else
            {
                if (logger.isErrorEnabled())
                    logger.error("Username is empty and therefore could not get authenticated correctly");
                return UserIdentity.UNAUTHENTICATED_IDENTITY;
            }
        }

        /**
         * <p>
         * Creates a UserIdentity object for a successfully authenticated user.
         * </p>
         * 
         * @param username The name of the authenticated user
         * @param password The password of the authenticated user
         * 
         * @return A valid UserIdentity object
         */
        private UserIdentity createIdentityForUser(String username, String password)
        {
            // create a principal object needed for the user identity
            Credential cred = Credential.getCredential(password);
            // a principal is basically an identity of a real person 
            // (subject). So a user can have multiple principals
            Principal userPrincipal = new MappedLoginService.KnownUser(username, cred);

            // a subject collects all data necessary to identify a certain 
            // person. It may store multiple identities and passwords or 
            // cryptographic keys
            Subject subject = new Subject();
            // add a Principal and credential to the Subject
            subject.getPrincipals().add(userPrincipal);
            subject.getPrivateCredentials().add(cred);
            subject.setReadOnly();

            return this.identityService.newUserIdentity(subject, userPrincipal, roles);
        }

        /**
         * <p>
         * Validate just checks if a user identity is still valid.
         * </p>
         */
        @Override
        public boolean validate(UserIdentity user) 
        {
            return true;
        }

        @Override
        public IdentityService getIdentityService() 
        {
            return this.identityService;
        }

        @Override
        public void setIdentityService(IdentityService service) 
        {
            this.identityService = service;
        }

        @Override
        public void logout(UserIdentity user) 
        {

        }
    }
}

To add this handler to Camel's embedded Jetty server you can define an endpoint using this handler like this:

jetty:https://your-server:port/api/v1/yourService?sslContextParameters=#sslContextParameters&handlers=#jettyAuthHandler

where jettyAuthHandler is the bean name of this handler - if you don't use SSL just omit the sslContextParameters parameter.

The JettyComponent in Camel has getter/setter where you can configure this in Java code.

JettyComponent jetty = new JettyComponent();
// use getter/setter to configure
// add component to Camel
camelContext.addComponent("jetty", jetty);
// after this you can add the routes and whatnot

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM