diff --git a/SECURITY-README.md b/SECURITY-README.md
new file mode 100644
index 00000000000..2eb1fd64128
--- /dev/null
+++ b/SECURITY-README.md
@@ -0,0 +1,43 @@
+
+
+# Shiro Authentication
+To connect to Zeppelin, users will be asked to enter their credentials. Once logged, a user has access to all notes including other users notes.
+This a a first step toward full security as implemented by this pull request (https://github.com/apache/incubator-zeppelin/pull/53).
+
+# Security setup
+1. Secure the HTTP channel: Comment the line "/** = anon" and uncomment the line "/** = authcBasic" in the file conf/shiro.ini. Read more about he shiro.ini file format at the following URL http://shiro.apache.org/configuration.html#Configuration-INISections.
+2. Secure the Websocket channel : Set to property "zeppelin.anonymous.allowed" to "false" in the file conf/zeppelin-site.xml. You can start by renaming conf/zeppelin-site.xml.template to conf/zeppelin-site.xml
+3. Start Zeppelin : bin/zeppelin.sh
+4. point your browser to http://localhost:8080
+5. Login using one of the user/password combinations defined in the conf/shiro.ini file.
+
+# Implementation notes
+## Vocabulary
+username, owner and principal are used interchangeably to designate the currently authenticated user
+## What are we securing ?
+Zeppelin is basically a web application that spawn remote interpreters to run commands and return HTML fragments to be displayed on the user browser.
+The scope of this PR is to require credentials to access Zeppelin. To achieve this, we use Apache Shiro.
+## HTTP Endpoint security
+Apache Shiro sits as a servlet filter between the browser and the exposed services and handles the required authentication without any programming required. (See Apache Shiro for more info).
+## Websocket security
+Securing the HTTP endpoints is not enough, since Zeppelin also communicates with the browser through websockets. To secure this channel, we take the following approach:
+1. The browser on startup requests a ticket through HTTP
+2. The Apache Shiro Servlet filter handles the user auth
+3. Once the user is authenticated, a ticket is assigned to this user and the ticket is returned to the browser
+
+All websockets communications require the username and ticket to be submitted by the browser. Upon receiving a websocket message, the server checks that the ticket received is the one assigned to the username through the HTTP request (step 3 above).
+
+
+
diff --git a/conf/shiro.ini b/conf/shiro.ini
new file mode 100644
index 00000000000..a592b4317dc
--- /dev/null
+++ b/conf/shiro.ini
@@ -0,0 +1,33 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+[users]
+# List of users with their password allowed to access Zeppelin.
+# To use a different strategy (LDAP / Database / ...) check the shiro doc at http://shiro.apache.org/configuration.html#Configuration-INISections
+admin = password1
+user1 = password2
+user2 = password3
+
+
+[urls]
+
+# anon means the access is anonymous.
+# authcBasic means Basic Auth Security
+# To enfore security, comment the line below and uncomment the next one
+/** = anon
+#/** = authcBasic
+
diff --git a/conf/zeppelin-site.xml.template b/conf/zeppelin-site.xml.template
index b6aca75d626..8232be58d6c 100755
--- a/conf/zeppelin-site.xml.template
+++ b/conf/zeppelin-site.xml.template
@@ -180,5 +180,11 @@
Allowed sources for REST and WebSocket requests (i.e. http://onehost:8080,http://otherhost.com). If you leave * you are vulnerable to https://issues.apache.org/jira/browse/ZEPPELIN-173
+
+ zeppelin.anonymous.allowed
+ true
+ Anonymous user allowed by default
+
+
diff --git a/pom.xml b/pom.xml
index 5e492fab3ae..08f4b5044f4 100755
--- a/pom.xml
+++ b/pom.xml
@@ -208,6 +208,18 @@
4.11test
+
+
+
+ org.apache.shiro
+ shiro-core
+ 1.2.3
+
+
+ org.apache.shiro
+ shiro-web
+ 1.2.3
+
diff --git a/zeppelin-distribution/src/bin_license/LICENSE b/zeppelin-distribution/src/bin_license/LICENSE
index 52ce3ebc7f2..86600b23fe0 100644
--- a/zeppelin-distribution/src/bin_license/LICENSE
+++ b/zeppelin-distribution/src/bin_license/LICENSE
@@ -91,6 +91,8 @@ The following components are provided under Apache License.
(Apache 2.0) Lucene Suggest (org.apache.lucene:lucene-suggest:5.3.1 - http://lucene.apache.org/lucene-parent/lucene-suggest)
(Apache 2.0) Elasticsearch: Core (org.elasticsearch:elasticsearch:2.1.0 - http://nexus.sonatype.org/oss-repository-hosting.html/parent/elasticsearch)
(Apache 2.0) Joda convert (org.joda:joda-convert:1.2 - http://joda-convert.sourceforge.net)
+ (Apache 2.0) Shiro Core (org.apache.shiro:shiro-core:1.2.3 - https://shiro.apache.org)
+ (Apache 2.0) Shiro Web (org.apache.shiro:shiro-web:1.2.3 - https://shiro.apache.org)
(Apache 2.0) SnakeYAML (org.yaml:snakeyaml:1.15 - http://www.snakeyaml.org)
diff --git a/zeppelin-server/pom.xml b/zeppelin-server/pom.xml
index e77ee6ca38d..73e878a58f5 100644
--- a/zeppelin-server/pom.xml
+++ b/zeppelin-server/pom.xml
@@ -269,6 +269,16 @@
1.9.0test
+
+
+
+ org.apache.shiro
+ shiro-core
+
+
+ org.apache.shiro
+ shiro-web
+
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
new file mode 100644
index 00000000000..d6f3dec05d4
--- /dev/null
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/rest/SecurityRestApi.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.zeppelin.rest;
+
+import org.apache.zeppelin.conf.ZeppelinConfiguration;
+import org.apache.zeppelin.server.JsonResponse;
+import org.apache.zeppelin.ticket.TicketContainer;
+import org.apache.zeppelin.utils.SecurityUtils;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Response;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Zeppelin security rest api endpoint.
+ *
+ */
+@Path("/security")
+@Produces("application/json")
+public class SecurityRestApi {
+ /**
+ * Required by Swagger.
+ */
+ public SecurityRestApi() {
+ super();
+ }
+
+ /**
+ * Get ticket
+ * Returns username & ticket
+ * for anonymous access, username is always anonymous.
+ * After getting this ticket, access through websockets become safe
+ *
+ * @return 200 response
+ */
+ @GET
+ @Path("ticket")
+ public Response ticket() {
+ ZeppelinConfiguration conf = ZeppelinConfiguration.create();
+ String principal = SecurityUtils.getPrincipal();
+ JsonResponse response;
+ // ticket set to anonymous for anonymous user. Simplify testing.
+ String ticket;
+ if ("anonymous".equals(principal))
+ ticket = "anonymous";
+ else
+ ticket = TicketContainer.instance.getTicket(principal);
+
+ Map data = new HashMap<>();
+ data.put("principal", principal);
+ data.put("ticket", ticket);
+
+ response = new JsonResponse(Response.Status.OK, "", data);
+ return response.build();
+ }
+}
diff --git a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
index fd115ee18cb..bd2d2c89a6f 100644
--- a/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
+++ b/zeppelin-server/src/main/java/org/apache/zeppelin/server/ZeppelinServer.java
@@ -36,6 +36,7 @@
import org.apache.zeppelin.notebook.repo.NotebookRepoSync;
import org.apache.zeppelin.rest.InterpreterRestApi;
import org.apache.zeppelin.rest.NotebookRestApi;
+import org.apache.zeppelin.rest.SecurityRestApi;
import org.apache.zeppelin.rest.ZeppelinRestApi;
import org.apache.zeppelin.scheduler.SchedulerFactory;
import org.apache.zeppelin.search.SearchService;
@@ -226,6 +227,12 @@ private static ServletContextHandler setupRestApiContextHandler(ZeppelinConfigur
cxfContext.addFilter(new FilterHolder(CorsFilter.class), "/*",
EnumSet.allOf(DispatcherType.class));
+
+ cxfContext.addFilter(org.apache.shiro.web.servlet.ShiroFilter.class, "/*",
+ EnumSet.allOf(DispatcherType.class));
+
+ cxfContext.addEventListener(new org.apache.shiro.web.env.EnvironmentLoaderListener());
+
return cxfContext;
}
@@ -273,6 +280,9 @@ public Set