Jetty Embedded:使用CORS + Basic身份验证(ConstraintSecurityHandler)

时间:2017-06-23 23:48:23

标签: java angular cors jetty basic-authentication

我在本地主机上安装了这个Jetty嵌入式服务器:8008并在localhost上有另一个Angular应用程序:4200访问它。能够在下面的代码中使用CORS,将CrossOriginFilter添加到此svrContext(这是我的webservices所在的位置)。

但是,如果我将此安全布尔值设置为 true ,我的Angular应用程序会给出如下消息:

OPTIONS http://localhost:8008/server/admin/session 401 (Unauthorized) scheduleTask @ zone.js:2263 webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.scheduleTask @ zone.js:410 onScheduleTask @ zone.js:300 webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.scheduleTask @ zone.js:404 webpackJsonp.../../../../zone.js/dist/zone.js.Zone.scheduleTask @ zone.js:235 webpackJsonp.../../../../zone.js/dist/zone.js.Zone.scheduleMacroTask @ zone.js:258 (anonymous) @ zone.js:2287 proto.(anonymous function) @ zone.js:1426 (anonymous) @ http.es5.js:1275 webpackJsonp.../../../../rxjs/Observable.js.Observable._trySubscribe @ Observable.js:57 webpackJsonp.../../../../rxjs/Observable.js.Observable.subscribe @ Observable.js:45 webpackJsonp.../../../../rxjs/operator/map.js.MapOperator.call @ map.js:54 webpackJsonp.../../../../rxjs/Observable.js.Observable.subscribe @ Observable.js:42 webpackJsonp.../../../../../src/app/login/login.component.ts.LoginComponent.loginAdmin @ login.component.ts:42 (anonymous) @ LoginComponent.html:3 handleEvent @ core.es5.js:12076 callWithDebugContext @ core.es5.js:13535 debugHandleEvent @ core.es5.js:13123 dispatchEvent @ core.es5.js:8688 (anonymous) @ core.es5.js:10850 schedulerFn @ core.es5.js:3647 webpackJsonp.../../../../rxjs/Subscriber.js.SafeSubscriber.__tryOrUnsub @ Subscriber.js:238 webpackJsonp.../../../../rxjs/Subscriber.js.SafeSubscriber.next @ Subscriber.js:185 webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber._next @ Subscriber.js:125 webpackJsonp.../../../../rxjs/Subscriber.js.Subscriber.next @ Subscriber.js:89 webpackJsonp.../../../../rxjs/Subject.js.Subject.next @ Subject.js:55 webpackJsonp.../../../core/@angular/core.es5.js.EventEmitter.emit @ core.es5.js:3621 webpackJsonp.../../../forms/@angular/forms.es5.js.FormGroupDirective.onSubmit @ forms.es5.js:4801 (anonymous) @ LoginComponent.html:3 handleEvent @ core.es5.js:12076 callWithDebugContext @ core.es5.js:13535 debugHandleEvent @ core.es5.js:13123 dispatchEvent @ core.es5.js:8688 (anonymous) @ core.es5.js:9299 (anonymous) @ platform-browser.es5.js:2668 webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask @ zone.js:424 onInvokeTask @ core.es5.js:3924 webpackJsonp.../../../../zone.js/dist/zone.js.ZoneDelegate.invokeTask @ zone.js:423 webpackJsonp.../../../../zone.js/dist/zone.js.Zone.runTask @ zone.js:191 ZoneTask.invoke @ zone.js:486 login:1 XMLHttpRequest cannot load http://localhost:8008/server/admin/session.
  

对预检请求的响应未通过访问控制检查:否   请求中存在“Access-Control-Allow-Origin”标头   资源。因此不允许来源“http://localhost:4200”   访问。响应的HTTP状态代码为401。

我错过了什么?我能够单独使用它们 - 具有相同原始应用程序的SecurtiyHandler和跨源请求(关闭基本身份验证)。

这是我的代码:

public class JettyLauncher {

    private static Server server;
    private static Boolean prod = true;
    private static String protocol = "https";
    private static String host = "localhost";
    private static int port = 8080;
    private static String staticContent = "";
    private static String services = "";
    private static Boolean secure = true;
    public static Resystoken tokenClass;
    private static String realm = "";
    private static Boolean logToFile = false;
    public static String url;
    public static String usuario;
    public static String senha;

    public static void main(String[] args) throws Exception {
        launch();
    }

    public static void launch() throws IOException, Exception{

        String buildPath = new File("").getAbsolutePath();
        System.out.println("* Build path: " + buildPath);

        prod = Boolean.valueOf(PropertySource.props.getProperty("resysclagem.launch.prod"));
        String sufixoProd = "";

        if(prod){
            System.out.println("* Production mode ON (Prod mode)");
            sufixoProd = ".prod";
        } else {
            System.out.println("* Not prod application (Dev mode)");
        }

        services = PropertySource.props.getProperty("resysclagem.launch.services");
        staticContent = PropertySource.props.getProperty("resysclagem.webapps");
        secure = Boolean.valueOf(PropertySource.props.getProperty("resysclagem.launch.security.basicauth"));
        realm = PropertySource.props.getProperty("resysclagem.realm.props");
        logToFile = Boolean.valueOf(PropertySource.props.getProperty("resysclagem.launch.logtofile"));

        tokenClass = new Resystoken(
                                    PropertySource.props.getProperty("resysclagem.launch.token.key"),
                                    Boolean.valueOf(PropertySource.props.getProperty("resysclagem.launch.security.requiretoken"))
                                   );

        url = PropertySource.props.getProperty("resysclagem.bd"+ sufixoProd +".url");
        usuario = PropertySource.props.getProperty("resysclagem.bd"+ sufixoProd +".usuario");
        senha = PropertySource.props.getProperty("resysclagem.bd"+ sufixoProd +".senha"); // pensar em em maneira de encriptar

        protocol = PropertySource.props.getProperty("resysclagem.launch"+ sufixoProd +".protocol");
        host = PropertySource.props.getProperty("resysclagem.launch"+ sufixoProd +".host");


        if(!host.equalsIgnoreCase("localhost")) {
            host = Inet4Address.getLocalHost().getHostAddress();
        }
        try {
            port = Integer.valueOf(PropertySource.props.getProperty("resysclagem.launch"+ sufixoProd +".port"));
        } catch(NumberFormatException nfe){
            String strPort = PropertySource.props.getProperty("resysclagem.launch"+ sufixoProd +".port");
            if(strPort.equalsIgnoreCase("heroku")){
                 port = Integer.valueOf(System.getenv("PORT"));
            }
        }


        server = new Server(port);
        HandlerList a = new HandlerList();

        HttpConfiguration http_config = new HttpConfiguration();
        SslContextFactory sslContextFactory = new SslContextFactory();
        if(protocol.equalsIgnoreCase("https")){
            port = port + 1;
            // https://github.com/eclipse/jetty.project/blob/jetty-9.3.x/examples/embedded/src/main/java/org/eclipse/jetty/embedded/LikeJettyXml.java
            // HTTP Configuration
            http_config.setSecureScheme("https");
            http_config.setSecurePort(port);    // port 8443
            http_config.setOutputBufferSize(32768);
            http_config.setRequestHeaderSize(8192);
            http_config.setResponseHeaderSize(8192);
            http_config.setSendServerVersion(true);
            http_config.setSendDateHeader(false);

            // SSL Context Factory            
            String keystorePath = buildPath + File.separator + "resources" + File.separator + "reskey"; 
            File f = new File(keystorePath);
            if(!f.exists()) { 
                throw new Exception("File doesn't exist at "+ keystorePath);
            }
            sslContextFactory.setKeyStorePath(keystorePath);
            String obf = Password.obfuscate("resysadmin*$");
            sslContextFactory.setKeyStorePassword(obf);
            sslContextFactory.setKeyManagerPassword(obf);
            sslContextFactory.setTrustStorePath("resources" + File.separator + "/reskey");
            sslContextFactory.setTrustStorePassword("resysadmin*$");
            sslContextFactory.setExcludeCipherSuites("SSL_RSA_WITH_DES_CBC_SHA",
                    "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA",
                    "SSL_RSA_EXPORT_WITH_RC4_40_MD5",
                    "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA",
                    "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA",
                    "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA");

            // SSL HTTP Configuration
            HttpConfiguration https_config = new HttpConfiguration(http_config);
            https_config.addCustomizer(new SecureRequestCustomizer());

            // SSL Connector
            ServerConnector sslConnector = new ServerConnector(server,
                new SslConnectionFactory(sslContextFactory,HttpVersion.HTTP_1_1.asString()),
                new HttpConnectionFactory(https_config));
            sslConnector.setPort(port);     // 8443
            server.addConnector(sslConnector);
        }

        //static content
        ResourceHandler resourceHandler= new ResourceHandler();
        resourceHandler.setResourceBase(staticContent);     // webapps
        resourceHandler.setDirectoriesListed(true);
        resourceHandler.setWelcomeFiles(new String[]{"index.html"});
        ContextHandler webContext = new ContextHandler("/");  /* the server uri path */
        webContext.setHandler(resourceHandler);

        //webservices
        ResourceConfig config = new ResourceConfig();
        config.register(MultiPartFeature.class);
        config.packages("resysclagem.ws.services");
        config.register(JacksonFeature.class);
        ServletHolder servlet = new ServletHolder(new ServletContainer(config));
        ServletContextHandler svrContext = new ServletContextHandler(server, services); //context path, services == /server
        svrContext.addServlet(servlet, "/*"); //server/endpoints
        svrContext.setInitParameter("jersey.config.server.provider.packages", "com.jersey.jaxb,com.fasterxml.jackson.jaxrs.json");
        svrContext.setInitParameter("com.sun.jersey.api.json.POJOMappingFeature", "true");
        /*
         * TODO aqui: posteriormente, autorizar apenas o domínio do módulo WEB a fazer alterações aqui
         */
        FilterHolder holder = new FilterHolder(new CrossOriginFilter());
        holder.setInitParameter("allowedMethods", "GET,POST,PUT,DELETE,HEAD,OPTIONS");
        //holder.setInitParameter("allowedOrigins", "http://localhost:4200");
        holder.setInitParameter("allowedHeaders", "Content-Type, Accept, X-Requested-With");
        svrContext.addFilter(holder, "/*", EnumSet.of(DispatcherType.REQUEST));

        a.addHandler(webContext); // index
        a.addHandler(svrContext);

        // basic authentication - ver resysclagem.realm.props
        if (secure){

            HashLoginService loginService = new HashLoginService("ResysRealm",
                                                                        realm);
            server.addBean(loginService);

            ConstraintSecurityHandler security = new ConstraintSecurityHandler();


            // This constraint requires authentication and in addition that an
            // authenticated user be a member of a given set of roles for
            // authorization purposes.
            Constraint constraint = new Constraint();
            constraint.setName("auth");
            constraint.setAuthenticate(true);
            constraint.setRoles(new String[] { "user", "admin" });

            // Binds a url pattern with the previously created constraint. The roles
            // for this constraing mapping are mined from the Constraint itself
            // although methods exist to declare and bind roles separately as well.
            ConstraintMapping mapping = new ConstraintMapping();
            mapping.setPathSpec("/*");
            mapping.setConstraint(constraint);
            security.setConstraintMappings(Collections.singletonList(mapping));
            security.setAuthenticator(new BasicAuthenticator());    //base64
            security.setLoginService(loginService);
            /*
            HashLoginService login = new HashLoginService();
            login.setName("Test Realm");
            login.setConfig("./resources/realm.properties");
            login.setHotReload(false);
            server.addBean(login);
            */

            security.setHandler(a);
            server.setHandler(security);            

        } else {
            System.out.println("* Warn: services and static content are not secured by authentication");
            server.setHandler(a);
        }

        try {
            if(logToFile){          
                System.out.println("* From here, output will be printed on log file instead of console.");
                PrintStream out = new PrintStream(new FileOutputStream("launchLog.txt"));
                System.setOut(out);
            }
            server.start();
            System.out.println("* Up at "+ host +":"+ port);
            server.join();
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
            throw e;
        }
    }

    public static boolean isRunning() {
        if (server == null){
            return false;
        }
        return server.isRunning();
    }

}

2 个答案:

答案 0 :(得分:2)

问题是安全处理程序会阻止所有未经身份验证的请求,包括 CORS预检消息,这很糟糕。

简而言之,Jetty不支持这种组合(HTTP Basic auth + CORS)。你必须编写一些自定义代码才能做到这一点。

答案 1 :(得分:0)

如果有其他人遇到这种废话。您需要在jetty.xml中添加一个Handler。这是Handler(https://github.com/datadidit/jaxrs-dynamic-security/blob/master/jetty-cors-handler/src/main/java/com/datadidit/cors/CORSHandler.java)的示例。