【问题标题】:Basic authentication with Camel Jetty使用 Camel Jetty 进行基本身份验证
【发布时间】:2012-03-18 17:54:44
【问题描述】:

我想以编程方式为 Jetty 服务器实现基本身份验证,如 here 所示。为了方便起见,我在这里^C-^V'ing那个sn-p。

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();

现在的问题是,上述方法要求您拥有服务器的句柄。但是就我而言,由于我使用的是 Camel,因此我无法直接访问服务器。这就是我的管道的定义方式:

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

如何使链接身份验证解决方案适应我的情况?还是我必须遵循一些完全不同的方法?

请注意,我既是 Camel 新手,也是 Jetty 新手。任何帮助将不胜感激。谢谢。

附录:

This page 展示了如何使用 Spring XML 来实现,但是我们没有使用 Spring,所以这对我们没有用。

【问题讨论】:

    标签: java jetty basic-authentication apache-camel


    【解决方案1】:

    几天前我偶然发现了这个问题,我通过定义自己的 ConstraintSecurityHandler 实现解决了这个问题,该实现使用定制的 LoginService 来处理 BasicAuthenticator 所需的身份验证。由于我没有找到任何现有的能够处理 bean 管理的身份验证的 LoginService 实现,因此我需要提出这个解决方案。

    我将发布几乎完整的课程,除了必须保密的内部内容。

    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) 
            {
    
            }
        }
    }
    

    要将此处理程序添加到 Camel 的嵌入式 Jetty 服务器,您可以使用此处理程序定义一个端点,如下所示:

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

    其中jettyAuthHandler 是此处理程序的 bean 名称 - 如果您不使用 SSL,只需省略 sslContextParameters 参数。

    【讨论】:

    • 感谢罗曼分享这个。如果我使用你的代码来测试我的骆驼 http 功能可以吗?我有使用camel-http进行出站HTTP调用的运行环境。当我们计划迁移到 camel-http4 时,我希望有足够的真实生活环境的测试用例。对于测试用例,我使用 Basic Auth 托管 Jetty(使用您的代码)并在测试用例中运行我们的出站呼叫。如果您对使用您的代码有任何保留,请告诉我。
    • @Robin 随意使用代码。在我发布代码时,SO 在帖子上保留了 CC-BY-SA 许可证,但现在更改为 MIT license。因此,我认为我所有的旧帖子也都是麻省理工学院(不确定我是否有权这样做^^)
    • 非常感谢。我已经添加了 MIT 许可证(即使代码仅用于测试)。此外,您的代码甚至提出了使用 Jetty :: JAAS 库验证 X509 证书的登录服务的想法。看看我能不能用 Camel Jetty 让它运行。
    【解决方案2】:

    Camel 中的 JettyComponent 具有 getter/setter,您可以在 Java 代码中对其进行配置。

    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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-16
      • 1970-01-01
      • 2015-08-12
      • 2017-06-13
      • 2013-05-30
      • 2017-03-10
      • 2017-11-27
      • 2016-05-31
      相关资源
      最近更新 更多