You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@gearpump.apache.org by hu...@apache.org on 2016/04/26 11:42:14 UTC

[04/49] incubator-gearpump git commit: fix #1981, Support OAuth2 Social login

fix #1981, Support OAuth2 Social login


Project: http://git-wip-us.apache.org/repos/asf/incubator-gearpump/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-gearpump/commit/099842ad
Tree: http://git-wip-us.apache.org/repos/asf/incubator-gearpump/tree/099842ad
Diff: http://git-wip-us.apache.org/repos/asf/incubator-gearpump/diff/099842ad

Branch: refs/heads/master
Commit: 099842ada64d64a5286fdc45ee9b634c5e82db62
Parents: 95d3c61
Author: Sean Zhong <cl...@gmail.com>
Authored: Tue Mar 15 12:01:23 2016 +0800
Committer: manuzhang <ow...@gmail.com>
Committed: Tue Apr 26 14:22:29 2016 +0800

----------------------------------------------------------------------
 conf/gear.conf                                  | 115 ++++++-
 core/src/main/resources/geardefault.conf        | 116 ++++++-
 .../serializer/GearpumpSerialization.scala      |   2 +-
 .../main/scala/io/gearpump/util/Constants.scala |  14 +-
 docs/deployment-ui-authentication.md            | 309 +++++++++++++++++--
 docs/dev-rest-api.md                            |  52 +++-
 project/Build.scala                             |   3 +
 services/dashboard/icons/google.png             | Bin 0 -> 885 bytes
 services/dashboard/icons/uaa.png                | Bin 0 -> 546 bytes
 services/dashboard/login/login.html             |  14 +
 services/dashboard/login/login.js               |  78 +++--
 .../io/gearpump/services/RestServices.scala     |   2 +-
 .../io/gearpump/services/SecurityService.scala  | 111 +++++--
 .../security/oauth2/OAuth2Authenticator.scala   | 144 +++++++++
 .../oauth2/impl/BaseOAuth2Authenticator.scala   | 217 +++++++++++++
 .../CloudFoundryUAAOAuth2Authenticator.scala    | 137 ++++++++
 .../oauth2/impl/GoogleOAuth2Authenticator.scala | 100 ++++++
 ...CloudFoundryUAAOAuth2AuthenticatorSpec.scala | 130 ++++++++
 .../oauth2/GoogleOAuth2AuthenticatorSpec.scala  | 153 +++++++++
 .../security/oauth2/MockOAuth2Server.scala      |  66 ++++
 20 files changed, 1685 insertions(+), 78 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/conf/gear.conf
----------------------------------------------------------------------
diff --git a/conf/gear.conf b/conf/gear.conf
index e5898e9..84694a8 100644
--- a/conf/gear.conf
+++ b/conf/gear.conf
@@ -62,7 +62,10 @@ gearpump {
   ### When the resource cannot be allocated in the timeout, then
   ### the appmaster will shutdown itself.
   resource-allocation-timeout-seconds = 120
-
+  
+  ##
+  ## Executor share same process of worker
+  worker.executor-share-same-jvm-as-worker = false
 
   ###########################
   ### Change the dispather for tasks
@@ -178,7 +181,7 @@ gearpump {
   ### For shared NFS folder, use file:///your_nfs_mapping_directory
   ### jarstore.rootpath = "jarstore/" will points to relative directory where master is started.
   ### jarstore.rootpath = "/jarstore/" will points to absolute directory on master server
-  # jarstore.rootpath = "jarstore/"
+  jarstore.rootpath = "jarstore/"
 
   #########################
   ### Scheduller for master, it will use this scheduler to schedule resource for
@@ -376,10 +379,19 @@ gearpump-ui {
   gearpump.ui-security {
 
     ## Whether enable authentication for UI Server
-    authentication-enabled = true
-
-    ## What authenticator to use. The class must implement interface
-    ## io.gearpump.security.Authenticator
+    authentication-enabled = false
+
+    ## User-Password based authenticator
+    ##
+    ## User-Password based authenticator is always enabled when
+    ## gearpump.ui-security.authentication-enabled = true
+    ##
+    ## With User-Password based authenticator inplace, user can still enable auxiliary
+    ## authentication channel like OAuth2.
+    ##
+    ## User can replace this with a custom User-Password based authenticator,
+    ## which implements interface io.gearpump.security.Authenticator
+    ##
     authenticator = "io.gearpump.security.ConfigFileBasedAuthenticator"
 
     ## Configuration options for authenticator io.gearpump.security.ConfigFileBasedAuthenticator
@@ -408,6 +420,97 @@ gearpump-ui {
         "guest" = "ws+2Dy/FHX4cBb3uKGTR64kZWlWbC91XZRRoew=="
       }
     }
+
+    ## Whether to enable auxiliary OAuth2 Authentication channel.
+    ##
+    ## NOTE: This requires config {{{gearpump.ui-security.authentication-enabled = true}}}
+    ##
+    ## NOTE: User-Password based authenticator will also be enabled.
+    ##
+    ## NOTE: OAuth2 authentication requires that the Gearpump server can directly access the OAuth2 server.
+    ## Please make sure you have configured web proxy properly if applies.
+    ## To configure http proxy on Windows:
+    ## {{{
+    ## > set JAVA_OPTS=-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088
+    ## > bin\services
+    ## }}}
+    ##
+    ## To configure http proxy on Linux:
+    ## {{{
+    ## $ export JAVA_OPTS="-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088"
+    ## $ bin/services
+    ## }}}
+    oauth2-authenticator-enabled = true
+    oauth2-authenticators {
+      ## Please modify the list if you have customized OAuth2 provider, like Facebook, Twitter...
+
+      ## OAuth2 Authenticator with Google Plus+
+      ##
+      ## For steps to enable OAuth2 Authentication on Google, please view docs/deployment-ui-authentication.md
+      ##
+      "google" {
+        "class" = "io.gearpump.services.security.oauth2.impl.GoogleOAuth2Authenticator"
+
+        ## Please replace "127.0.0.1:8090" with your address of UI service.
+        "callback" = "http://127.0.0.1:8090/login/oauth2/google/callback"
+
+        ## Client Id and client secret you applied on Google.
+        ##
+        ## !!NOTE!! Replace clientID and clientSecret with your own application to avoid
+        ## potential privacy leakage, the values set here represents as a test application.
+        "clientid" = "170234147043-a1tag68jtq6ab4bi11jvsj7vbaqcmhkt.apps.googleusercontent.com"
+        "clientsecret" = "ioeWLLDipz2S7aTDXym2-obe"
+
+        ## The default role we assign to user when user get authenticated by Google.
+        ##
+        ## TODO: should allow some user group have a different role, like admin.
+        ##
+        ## Available values: guest, user, admin, with:
+        ##   1. guest can only view the application status,
+        ##   2. user can submit and modify application.
+        ##   3. admin can manage the cluster resource, like adding or removing machines.
+        "default-userrole" = "guest"
+
+        ## Login icon disiplayed on UI server frontend
+        icon = "/icons/google.png"
+      }
+
+      ## OAuth2 Authenticator for CloudFoundry UAA server (https://github.com/cloudfoundry/uaa/).
+      ##
+      ## For steps to enable OAuth2 Authentication for UAA, please view docs/deployment-ui-authentication.md
+      ##
+      "cloudfoundryuaa" {
+        "class" = "io.gearpump.services.security.oauth2.impl.CloudFoundryUAAOAuth2Authenticator"
+
+        ## Please replace "127.0.0.1:8090" with your address of UI service.
+        "callback" = "http://127.0.0.1:8090/login/oauth2/cloudfoundryuaa/callback"
+
+        ## Client Id and client secret you applied on Google.
+        ##
+        ## !!NOTE!! Replace clientID and clientSecret with your own application to avoid
+        ## potential privacy leakage, the values set here serves as a test application.
+        "clientid" = "gearpump_test2"
+        "clientsecret" = "gearpump_test2"
+
+        ## The default role we assign to user when user get authenticated by UAA.
+        ##
+        ## TODO: should allow some user group have a different role, like admin.
+        ##
+        ## Available values: guest, user, admin, with:
+        ##   1. guest can only view the application status,
+        ##   2. user can submit and modify application.
+        ##   3. admin can manage the cluster resource, like adding or removing machines.
+        "default-userrole" = "guest"
+
+        ## Login icon disiplayed on UI server frontend
+        icon = "/icons/uaa.png"
+
+        ## The hostname of cloudfoudry UAA server prefixed by "http://" or "https://"
+        ## !!NOTE!! Please relace uaahost with your actual Cloudfounudry UAA server, the
+        ## value set here serves as an example.
+        uaahost = "http://login.gearpump.gotapaas.eu"
+      }
+    }
   }
 }
 

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/core/src/main/resources/geardefault.conf
----------------------------------------------------------------------
diff --git a/core/src/main/resources/geardefault.conf b/core/src/main/resources/geardefault.conf
index 065e5e0..16bd8c0 100644
--- a/core/src/main/resources/geardefault.conf
+++ b/core/src/main/resources/geardefault.conf
@@ -99,7 +99,6 @@ gearpump {
   ### If you want to use metrics, please change
   ###########################
 
-
   ### Flag to enable metrics
   metrics {
     enabled = false
@@ -370,8 +369,17 @@ gearpump-ui {
     ## Whether enable authentication for UI Server
     authentication-enabled = false
 
-    ## What authenticator to use. The class must implement interface
-    ## io.gearpump.security.Authenticator
+    ## User-Password based authenticator
+    ##
+    ## User-Password based authenticator is always enabled when
+    ## gearpump.ui-security.authentication-enabled = true
+    ##
+    ## With User-Password based authenticator inplace, user can still enable auxiliary
+    ## authentication channel like OAuth2.
+    ##
+    ## User can replace this with a custom User-Password based authenticator,
+    ## which implements interface io.gearpump.security.Authenticator
+    ##
     authenticator = "io.gearpump.security.ConfigFileBasedAuthenticator"
 
     ## Configuration options for authenticator io.gearpump.security.ConfigFileBasedAuthenticator
@@ -383,7 +391,8 @@ gearpump-ui {
       ## Admin users have super permission to do everything
       admins = {
         ## Default Admin. Username: admin, password: admin
-        # "admin" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA=="
+        ## !!! Please replace this builtin account for production cluster for security reason. !!!
+        "admin" = "AeGxGOxlU8QENdOXejCeLxy+isrCv0TrS37HwA=="
       }
 
       ## normal user have special permission for certain operations.
@@ -395,7 +404,100 @@ gearpump-ui {
       ## a running application.
       guests = {
         ## Default guest. Username: guest, Password: guest
-        #"guest" = "ws+2Dy/FHX4cBb3uKGTR64kZWlWbC91XZRRoew=="
+        ## !!! Please replace this builtin account for production cluster for security reason. !!!
+        "guest" = "ws+2Dy/FHX4cBb3uKGTR64kZWlWbC91XZRRoew=="
+      }
+    }
+
+    ## Whether to enable auxiliary OAuth2 Authentication channel.
+    ##
+    ## NOTE: This requires config {{{gearpump.ui-security.authentication-enabled = true}}}
+    ##
+    ## NOTE: User-Password based authenticator will also be enabled.
+    ##
+    ## NOTE: OAuth2 authentication requires that the Gearpump server can directly access the OAuth2 server.
+    ## Please make sure you have configured web proxy properly if applies.
+    ## To configure http proxy on Windows:
+    ## {{{
+    ## > set JAVA_OPTS=-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088
+    ## > bin\services
+    ## }}}
+    ##
+    ## To configure http proxy on Linux:
+    ## {{{
+    ## $ export JAVA_OPTS="-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088"
+    ## $ bin/services
+    ## }}}
+
+    oauth2-authenticator-enabled = false
+    oauth2-authenticators {
+      ## Please modify the list if you have customized OAuth2 provider, like Facebook, Twitter...
+
+      ## OAuth2 Authenticator with Google Plus+
+      ##
+      ## For steps to enable OAuth2 Authentication on Google, please view docs/deployment-ui-authentication.md
+      ##
+      "google" {
+        "class" = "io.gearpump.services.security.oauth2.impl.GoogleOAuth2Authenticator"
+
+        ## Please replace "127.0.0.1:8090" with your address of UI service.
+        "callback" = "http://127.0.0.1:8090/login/oauth2/google/callback"
+
+        ## Client Id and client secret you applied on Google.
+        ##
+        ## !!NOTE!! Replace clientID and clientSecret with your own application to avoid
+        ## potential privacy leakage, the values set here represents as a test application.
+        "clientid" = "170234147043-a1tag68jtq6ab4bi11jvsj7vbaqcmhkt.apps.googleusercontent.com"
+        "clientsecret" = "ioeWLLDipz2S7aTDXym2-obe"
+
+        ## The default role we assign to user when user get authenticated by Google.
+        ##
+        ## TODO: should allow some user group have a different role, like admin.
+        ##
+        ## Available values: guest, user, admin, with:
+        ##   1. guest can only view the application status,
+        ##   2. user can submit and modify application.
+        ##   3. admin can manage the cluster resource, like adding or removing machines.
+        "default-userrole" = "guest"
+
+        ## Login icon disiplayed on UI server frontend
+        icon = "/icons/google.png"
+      }
+
+      ## OAuth2 Authenticator for CloudFoundry UAA server (https://github.com/cloudfoundry/uaa/).
+      ##
+      ## For steps to enable OAuth2 Authentication for UAA, please view docs/deployment-ui-authentication.md
+      ##
+      "cloudfoundryuaa" {
+        "class" = "io.gearpump.services.security.oauth2.impl.CloudFoundryUAAOAuth2Authenticator"
+
+        ## Please replace "127.0.0.1:8090" with your address of UI service.
+        "callback" = "http://127.0.0.1:8090/login/oauth2/cloudfoundryuaa/callback"
+
+        ## Client Id and client secret you applied on Google.
+        ##
+        ## !!NOTE!! Replace clientID and clientSecret with your own application to avoid
+        ## potential privacy leakage, the values set here serves as a test application.
+        "clientid" = "gearpump_test2"
+        "clientsecret" = "gearpump_test2"
+
+        ## The default role we assign to user when user get authenticated by UAA.
+        ##
+        ## TODO: should allow some user group have a different role, like admin.
+        ##
+        ## Available values: guest, user, admin, with:
+        ##   1. guest can only view the application status,
+        ##   2. user can submit and modify application.
+        ##   3. admin can manage the cluster resource, like adding or removing machines.
+        "default-userrole" = "guest"
+
+        ## Login icon disiplayed on UI server frontend
+        icon = "/icons/uaa.png"
+
+        ## The hostname of cloudfoudry UAA server prefixed by "http://" or "https://"
+        ## !!NOTE!! Please relace uaahost with your actual Cloudfounudry UAA server, the
+        ## value set here serves as an example.
+        uaahost = "http://login.gearpump.gotapaas.eu"
       }
     }
   }
@@ -447,8 +549,8 @@ akka {
           httpOnly = true
         }
 
-        ## Session lifetime. Default value is about 1 month
-        maxAgeSeconds = 2592000
+        ## Session lifetime. Default value is about 1 week
+        maxAgeSeconds = 604800
         encryptData = true
       }
     }

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/core/src/main/scala/io/gearpump/serializer/GearpumpSerialization.scala
----------------------------------------------------------------------
diff --git a/core/src/main/scala/io/gearpump/serializer/GearpumpSerialization.scala b/core/src/main/scala/io/gearpump/serializer/GearpumpSerialization.scala
index 3f7a042..41ccaa4 100644
--- a/core/src/main/scala/io/gearpump/serializer/GearpumpSerialization.scala
+++ b/core/src/main/scala/io/gearpump/serializer/GearpumpSerialization.scala
@@ -53,6 +53,6 @@ class GearpumpSerialization(config: Config) {
 
   private final def configToMap(config : Config, path: String) = {
     import scala.collection.JavaConverters._
-    config.getConfig(path).root.unwrapped.asScala.toMap map { case (k, v) ⇒ k -> v.toString }
+    config.getConfig(path).root.unwrapped.asScala.toMap map { case (k, v) => k -> v.toString }
   }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/core/src/main/scala/io/gearpump/util/Constants.scala
----------------------------------------------------------------------
diff --git a/core/src/main/scala/io/gearpump/util/Constants.scala b/core/src/main/scala/io/gearpump/util/Constants.scala
index 7d066e9..084d99b 100644
--- a/core/src/main/scala/io/gearpump/util/Constants.scala
+++ b/core/src/main/scala/io/gearpump/util/Constants.scala
@@ -147,8 +147,20 @@ object Constants {
   val GEARPUMP_METRICS_AGGREGATORS = "gearpump.metrics.akka.metrics-aggregator-class"
 
   val GEARPUMP_UI_SECURITY = "gearpump.ui-security"
-  val GEARPUMP_UI_SECURITY_ENABLED = "gearpump.ui-security.authentication-enabled"
+  val GEARPUMP_UI_SECURITY_AUTHENTICATION_ENABLED = "gearpump.ui-security.authentication-enabled"
   val GEARPUMP_UI_AUTHENTICATOR_CLASS = "gearpump.ui-security.authenticator"
+  // OAuth Authentication Factory for UI server.
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_ENABLED = "gearpump.ui-security.oauth2-authenticator-enabled"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATORS = "gearpump.ui-security.oauth2-authenticators"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CLASS = "class"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CALLBACK = "callback"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CLIENT_ID = "clientid"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CLIENT_SECRET = "clientsecret"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_DEFAULT_USER_ROLE = "default-userrole"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_AUTHORIZATION_CODE = "code"
+  val GEARPUMP_UI_OAUTH2_AUTHENTICATOR_ACCESS_TOKEN = "accesstoken"
 
   val PREFER_IPV4 = "java.net.preferIPv4Stack"
+
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/docs/deployment-ui-authentication.md
----------------------------------------------------------------------
diff --git a/docs/deployment-ui-authentication.md b/docs/deployment-ui-authentication.md
index 0ef44a0..ac5a511 100644
--- a/docs/deployment-ui-authentication.md
+++ b/docs/deployment-ui-authentication.md
@@ -3,6 +3,9 @@ layout: global
 title: UI Dashboard Authentication and Authorization
 ---
 
+## What is this about?
+
+
 ## How to enable UI authentication?
 
 1. Change config file gear.conf, find entry `gearpump-ui.gearpump.ui-security.authentication-enabled`, change the value to true
@@ -12,8 +15,36 @@ title: UI Dashboard Authentication and Authorization
    ```
    
    Restart the UI dashboard, and then the UI authentication is enabled. It will prompt for user name and password.
-   
-## How to add or remove user?
+
+## How many authentication methods Gearpump UI server support?
+
+Currently, It supports:
+
+1. Username-Password based authentication and
+2. OAuth2 based authentication.
+
+User-Password based authentication is enabled when `gearpump-ui.gearpump.ui-security.authentication-enabled`,
+ and **CANNOT** be disabled.
+
+UI server admin can also choose to enable **auxiliary** OAuth2 authentication channel.
+
+## User-Password based authentication
+
+   User-Password based authentication covers all authentication scenarios which requires
+   user to enter an explicit username and password.
+
+   Gearpump provides a built-in ConfigFileBasedAuthenticator which verify user name and password
+   against password hashcode stored in config files.
+
+   However, developer can choose to extends the ```io.gearpump.security.Authenticator``` to provide a custom
+   User-Password based authenticator, to support LDAP, Kerberos, and Database-based authentication...
+
+### ConfigFileBasedAuthenticator: built-in User-Password Authenticator
+
+ConfigFileBasedAuthenticator store all user name and password hashcode in configuration file gear.conf. Here
+is the steps to configure ConfigFileBasedAuthenticator.
+
+#### How to add or remove user?
 
 For the default authentication plugin, it has three categories of users: admins, users, and guests.
 
@@ -59,33 +90,275 @@ Suppose we want to add user jerry as an administrator, here are the steps:
 
 5. See description at `conf/gear.conf` to find more information.   
    
-## What is the default user and password?
+#### What is the default user and password?
 
-Gearpump distribution is shipped with two default users:
+For ConfigFileBasedAuthenticator, Gearpump distribution is shipped with two default users:
 
 1. username: admin, password: admin
 2. username: guest, password: guest
 
-User `admin:admin` has unlimited permissions, while guest can only view the application status. Guest account cannot 
-submit or kill the application by UI console.
+User `admin` has unlimited permissions, while `guest` can only view the application status.
 
-For security reason, you need to remove the default user "admin" and "guest" for production cluster.
+For security reason, you need to remove the default users `admin` and `guest` for cluster in production.
 
-## Is this secure?
+#### Is this secure?
 
 Firstly, we will NOT store any user password in any way so only the user himself knows the password. 
-We will use one-way sha1 digest to verify the user input password. As it is a one-way hashing,
-so generally it is safe.
+We will use one-way hash digest to verify the user input password.
+
+### How to develop a custom User-Password Authenticator for LDAP, Database, and etc..
+
+If developer choose to define his/her own User-Password based authenticator, it is required that user
+    modify configuration option:
+
+```
+## Replace "io.gearpump.security.CustomAuthenticator" with your real authenticator class.
+gearpump.ui-security.authenticator = "io.gearpump.security.CustomAuthenticator"
+```
+
+Make sure CustomAuthenticator extends interface:
+```scala
+trait Authenticator {
+
+  def authenticate(user: String, password: String, ec: ExecutionContext): Future[AuthenticationResult]
+}
+```
+
+## OAuth2 based authentication
+
+OAuth2 based authentication is commonly use to achieve social login with social network account.
+
+Gearpump provides generic OAuth2 Authentication support which allow user to extend to support new authentication sources.
+
+Basically, OAuth2 based Authentication contains these steps:
+ 1. User accesses Gearpump UI website, and choose to login with OAuth2 server.
+ 2. Gearpump UI website redirects user to OAuth2 server domain authorization endpoint.
+ 3. End user complete the authorization in the domain of OAuth2 server.
+ 4. OAuth2 server redirects user back to Gearpump UI server.
+ 5. Gearpump UI server verify the tokens and extract credentials from query
+ parameters and form fields.
+
+### Terminologies
+
+For terms like client Id, and client secret, please refers to guide [RFC 6749](https://tools.ietf.org/html/rfc6749)
+
+### Enable web proxy for UI server
+
+To enable OAuth2 authentication, the Gearpump UI server should have network access to OAuth2 server, as
+ some requests are initiated directly inside Gearpump UI server. So, if you are behind a firewall, make
+ sure you have configured the proxy properly for UI server.
+
+#### If you are on Windows
+
+```bash
+  > set JAVA_OPTS=-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088
+  > bin\services
+```
+
+#### If you are on Linux
+
+```bash
+  $ export JAVA_OPTS="-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088"
+  $ bin/services
+```
+
+### Google Plus OAuth2 Authenticator
+
+Google Plus OAuth2 Authenticator does authentication with Google OAuth2 service. It extracts the email address
+from Google user profile as credentials.
+
+To use Google OAuth2 Authenticator, there are several steps:
+
+1. Register your application (Gearpump UI server here) as an application to Google developer console.
+2. Configure the Google OAuth2 information in gear.conf
+3. Configure network proxy for Gearpump UI server if applies.
+
+#### Step1: Register your website as an OAuth2 Application on Google
+
+1. Create an application representing your website at [https://console.developers.google.com](https://console.developers.google.com)
+2. In "API Manager" of your created application, enable API "Google+ API"
+3. Create OAuth client ID for this application. In "Credentials" tab of "API Manager",
+choose "Create credentials", and then select OAuth client ID. Follow the wizard
+to set callback URL, and generate client ID, and client Secret.
+
+**NOTE:** Callback URL is NOT optional.
+
+#### Step2: Configure the OAuth2 information in gear.conf
+
+1. Enable OAuth2 authentication by setting `gearpump.ui-security.oauth2-authenticator-enabled`
+as true.
+2. Configure section `gearpump.ui-security.oauth2-authenticators.google` in gear.conf. Please make sure
+class name, client ID, client Secret, and callback URL are set properly.
+
+**NOTE:** Callback URL set here should match what is configured on Google in step1.
+
+#### Step3: Configure the network proxy if applies.
+
+To enable OAuth2 authentication, the Gearpump UI server should have network access to Google service, as
+ some requests are initiated directly inside Gearpump UI server. So, if you are behind a firewall, make
+ sure you have configured the proxy properly for UI server.
+
+For guide of how to configure web proxy for UI server, please refer to section "Enable web proxy for UI server" above.
+
+#### Step4: Restart the UI server and try to click the Google login icon on UI server.
+
+### CloudFoundry UAA server OAuth2 Authenticator
+
+CloudFoundryUaaAuthenticator does authentication by using CloudFoundry UAA OAuth2 service. It extracts the email address
+ from Google user profile as credentials.
+
+For what is UAA (User Account and Authentication Service), please see guide: [UAA](https://github.com/cloudfoundry/uaa)
+
+To use Google OAuth2 Authenticator, there are several steps:
+
+1. Register your application (Gearpump UI server here) as an application to UAA with helper tool `uaac`.
+2. Configure the Google OAuth2 information in gear.conf
+3. Configure network proxy for Gearpump UI server if applies.
+
+#### Step1: Register your application to UAA with `uaac`
+
+1. Check tutorial on uaac at [https://docs.cloudfoundry.org/adminguide/uaa-user-management.html](https://docs.cloudfoundry.org/adminguide/uaa-user-management.html)
+2. Open a bash shell, and login in as user admin by
 
-1. Digest flow(from original password to digest):
- 
    ```
-   random salt byte array of length 8 -> byte array of (salt + sha1(salt, password)) -> base64Encode
+     uaac token client get admin -s MyAdminPassword
    ```
-   
-2. Verification user input password with stored digest:
- 
+3. Create a new Application (Client) in UAA,
+   ```
+    uaac client add [your_client_id]
+      --scope openid
+      --authorized_grant_types "authorization_code client_credentials refresh_token"
+      --authorities openid
+      --redirect_uri [your_redirect_url]
+      --autoapprove true
+      --secret [your_client_secret]
+   ```
+#### Step2: Configure the OAuth2 information in gear.conf
+
+1. Enable OAuth2 authentication by setting `gearpump.ui-security.oauth2-authenticator-enabled` as true.
+2. Navigate to section `gearpump.ui-security.oauth2-authenticators.cloudfoundryuaa`
+3. Config gear.conf `gearpump.ui-security.oauth2-authenticators.cloudfoundryuaa` section.
+Please make sure class name, client ID, client Secret, and callback URL are set properly.
+
+**NOTE:** The callback URL here should matche what you set on CloudFoundry UAA in step1.
+
+#### Step3: Configure network proxy for Gearpump UI server if applies
+
+To enable OAuth2 authentication, the Gearpump UI server should have network access to Google service, as
+ some requests are initiated directly inside Gearpump UI server. So, if you are behind a firewall, make
+ sure you have configured the proxy properly for UI server.
+
+For guide of how to configure web proxy for UI server, please refer to please refer to section "Enable web proxy for UI server" above.
+
+#### Step4: Restart the UI server and try to click the CloudFoundry login icon on UI server.
+
+#### Extends OAuth2Authenticator to support new Authorization service like Facebook, or Twitter.
+
+You can follow the Google OAuth2 example code to define a custom OAuth2Authenticator. Basically, the steps includes:
+
+1. Define an OAuth2Authenticator implementation.
+
+   ```scala
+   /**
+    *
+    * Uses OAuth2 social-login as the mechanism for authentication.
+    * @see [[https://tools.ietf.org/html/rfc6749]] to find what is OAuth2, and how it works.
+    *
+    * Basically flow for OAuth2 Authentication:
+    * 1. User accesses Gearpump UI website, and choose to login with OAuth2 server.
+    * 2. Gearpump UI website redirects user to OAuth2 server domain authorization endpoint.
+    * 3. End user complete the authorization in the domain of OAuth2 server.
+    * 4. OAuth2 server redirects user back to Gearpump UI server.
+    * 5. Gearpump UI server verify the tokens and extract credentials from query
+    * parameters and form fields.
+    *
+    * @note '''Thread-safety''' is a MUST requirement. Developer need to ensure the sub-class is thread-safe.
+    * Sub-class should have a parameterless constructor.
+    *
+    * @note OAuth2 Authenticator requires access of Internet. Please make sure HTTP proxy are
+    * set properly if applied.
+    *
+    * @example Config proxy when UI server is started on Windows:
+    * {{{
+    *   > set JAVA_OPTS=-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088
+    *   > bin\services
+    * }}}
+    *
+    * @example Config proxy when UI server is started on Linux:
+    * {{{
+    *   $ export JAVA_OPTS="-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088"
+    *   $ bin/services
+    * }}}
+    *
+    */
+   trait OAuth2Authenticator {
+
+     /**
+      * Inits authenticator with config which contains client ID, client secret, and etc..
+      *
+      * Typically, the client key and client secret is provided by OAuth2 Authorization server when user
+      * register an application there.
+      * @see [[https://tools.ietf.org/html/rfc6749]] for definition of client, client Id,
+      * and client secret.
+      *
+      * See [[https://developer.github.com/v3/oauth/]] for an actual example of how Github
+      * use client key, and client secret.
+      *
+      * @note '''Thread-Safety''': Framework ensures this call is synchronized.
+      *
+      * @param config Client Id, client secret, callback URL and etc..
+      */
+     def init(config: Config): Unit
+
+     /**
+      * Returns the OAuth Authorization URL so for redirection to that address to do OAuth2
+      * authorization.
+      *
+      * @note '''Thread-Safety''': This can be called in a multi-thread environment. Developer
+      *      need to ensure thread safety.
+      */
+     def getAuthorizationUrl: String
+
+     /**
+      * After authorization, OAuth2 server redirects user back with tokens. This verify the
+      * tokens, retrieve the profiles, and return [[UserSession]] information.
+      *
+      * @note This is an Async call.
+      * @note This call requires external internet access.
+      * @note '''Thread-Safety''': This can be called in a multi-thread environment. Developer
+      *      need to ensure thread safety.
+      *
+      * @param parameters HTTP Query and Post parameters, which typically contains Authorization code.
+      * @return UserSession if pass authentication.
+      */
+     def authenticate(parameters: Map[String, String]): Future[UserSession]
+
+     /**
+      * Clean resource
+      */
+     def close(): Unit
+   }
+
    ```
-   base64Decode -> extract salt -> do sha1(salt, password) -> generate digest: salt + sha1 ->
-   compare the generated digest with the stored digest.
+
+2. Add an configuration entry under `gearpump.ui-security.oauth2-authenticators`. For example:
+
+   ```
+    ## name of this authenticator
+   "socialnetworkx" {
+     "class" = "io.gearpump.services.security.oauth2.impl.SocialNetworkXAuthenticator"
+
+     ## Please make sure this URL matches the name
+     "callback" = "http://127.0.0.1:8090/login/oauth2/socialnetworkx/callback"
+
+     "clientId" = "gearpump_test2"
+     "clientSecret" = "gearpump_test2"
+     "defaultUserRole" = "guest"
+
+     ## Make sure socialnetworkx.png exists under dashboard/icons
+     icon = "/icons/socialnetworkx.png"
+   }
    ```
+   The configuration entry is supposed to be used by class `SocialNetworkXAuthenticator`.
+
+

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/docs/dev-rest-api.md
----------------------------------------------------------------------
diff --git a/docs/dev-rest-api.md b/docs/dev-rest-api.md
index df514bc..36eeb0b 100644
--- a/docs/dev-rest-api.md
+++ b/docs/dev-rest-api.md
@@ -12,6 +12,9 @@ To disable Authentication, you can set `gearpump-ui.gearpump.ui-security.authent
 in gear.conf, please check [UI Authentication](deployment-ui-authentication.html) for details.
 
 ### How to authenticate if Authentication is enabled.
+
+#### For User-Password based authentication
+
 If Authentication is enabled, then you need to login before calling REST API.
 
 ```
@@ -28,9 +31,56 @@ curl --cookie outputAuthenticationCookie.txt http://127.0.0.1/api/v1.0/master
 
 for more information, please check [UI Authentication](deployment-ui-authentication.html).
 
+#### For OAuth2 based authentication
 
-## Query version
+For OAuth2 based authentication, it requires you to have an access token in place.
+
+Different OAuth2 service provider have different way to return an access token.
+
+**For Google**, you can refer to [OAuth Doc](https://developers.google.com/identity/protocols/OAuth2).
+
+**For CloudFoundry UAA**, you can use the uaac command to get the access token.
+
+```
+$ uaac target http://login.gearpump.gotapaas.eu/
+$ uaac token get <user_email_address>
+
+### Find access token
+$ uaac context
+
+[0]*[http://login.gearpump.gotapaas.eu]
+
+  [0]*[<user_email_address>]
+      user_id: 34e33a79-42c6-479b-a8c1-8c471ff027fb
+      client_id: cf
+      token_type: bearer
+      access_token: eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI
+      expires_in: 599
+      scope: password.write openid cloud_controller.write cloud_controller.read
+      jti: 74ea49e4-1001-4757-9f8d-a66e52a27557
+```
+
+For more information on uaac, please check [UAAC guide](https://docs.cloudfoundry.org/adminguide/uaa-user-management.html)
 
+Now, we have the access token, then let's login to Gearpump UI server with this access token:
+
+```
+## Please replace cloudfoundryuaa with actual OAuth2 service name you have configured in gear.conf
+curl  -X POST  --data accesstoken=eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI --cookie-jar outputAuthenticationCookie.txt http://127.0.0.1:8090/login/oauth2/cloudfoundryuaa/accesstoken
+```
+
+This will use user  `user_email_address` to login, and store the authentication cookie to file outputAuthenticationCookie.txt.
+
+In All subsequent Rest API calls, you need to add the authentication cookie. For example
+
+```
+curl --cookie outputAuthenticationCookie.txt http://127.0.0.1/api/v1.0/master
+```
+
+**NOTE:** You can default the default permission level for OAuth2 user. for more information,
+please check [UI Authentication](deployment-ui-authentication.html).
+
+## Query version
 
 ### GET version
 

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/project/Build.scala
----------------------------------------------------------------------
diff --git a/project/Build.scala b/project/Build.scala
index eae876b..0d08718 100644
--- a/project/Build.scala
+++ b/project/Build.scala
@@ -278,6 +278,9 @@ object Build extends sbt.Build {
       "org.scalatest" %% "scalatest" % scalaTestVersion % "test",
       "com.lihaoyi" %% "upickle" % upickleVersion,
       "com.softwaremill" %% "akka-http-session" % "0.1.4",
+      "com.typesafe.akka" %% "akka-http-spray-json-experimental"% "1.0",
+      "com.github.scribejava" % "scribejava-apis" % "2.4.0",
+      "com.ning" % "async-http-client" % "1.9.33",
       "org.webjars" % "angularjs" % "1.4.9",
       "org.webjars.npm" % "angular-touch" % "1.5.0", // angular 1.5 breaks ui-select, but we need ng-touch 1.5
       "org.webjars" % "angular-ui-router" % "0.2.15",

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/dashboard/icons/google.png
----------------------------------------------------------------------
diff --git a/services/dashboard/icons/google.png b/services/dashboard/icons/google.png
new file mode 100644
index 0000000..d25b94a
Binary files /dev/null and b/services/dashboard/icons/google.png differ

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/dashboard/icons/uaa.png
----------------------------------------------------------------------
diff --git a/services/dashboard/icons/uaa.png b/services/dashboard/icons/uaa.png
new file mode 100644
index 0000000..c16083f
Binary files /dev/null and b/services/dashboard/icons/uaa.png differ

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/dashboard/login/login.html
----------------------------------------------------------------------
diff --git a/services/dashboard/login/login.html b/services/dashboard/login/login.html
index aad51e6..d80984d 100644
--- a/services/dashboard/login/login.html
+++ b/services/dashboard/login/login.html
@@ -41,6 +41,18 @@
       border-bottom-right-radius: 0;
     }
 
+    .social-login {
+
+      position: relative;
+      font-size: 14px;
+      height: auto;
+
+      margin-top: 10px;
+      margin-bottom: 10px;
+      border-bottom-left-radius: 0;
+      border-bottom-right-radius: 0;
+    }
+
     .welcome-message {
       color: #ffffff;
 
@@ -88,6 +100,8 @@
         <input type="text" class="form-control" name="username" placeholder="User Name" required autofocus />
         <input type="password" class="form-control" name="password" placeholder="Password" required />
         <button class="btn btn-lg btn-primary btn-block" type="button" onclick="login()">Sign In</button>
+
+        <div class="social-login" id="social_login"></div>
         <div class="error" id="error"></div>
 
         <a href="#" class="pull-left" style="margin-top: 15px;"><!--Need help? --></a>

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/dashboard/login/login.js
----------------------------------------------------------------------
diff --git a/services/dashboard/login/login.js b/services/dashboard/login/login.js
index 3c8d8a6..5a520f9 100644
--- a/services/dashboard/login/login.js
+++ b/services/dashboard/login/login.js
@@ -9,35 +9,67 @@
  */
 function login() {
 
-    var loginUrl = $("#loginUrl").attr('href');
-    var index = $("#index").attr('href');
-
-    $.post(loginUrl, $("#loginForm").serialize() ).done(
-      function(msg) {
-          var user = $.parseJSON(msg);
-          $.cookie("username", user.user, { expires: 365, path: '/' });
-          // clear the errors
-          $("#error").text("");
-          // redirect to index.html
-          $(location).attr('href', index);
-      }
-    )
-    .fail( function(xhr, textStatus, errorThrown) {
-       var elem = $("#error");
-            elem.html(xhr.responseText);
-            elem.text(textStatus + "(" + xhr.status + "): " + elem.text());
-    });
+  var loginUrl = $("#loginUrl").attr('href');
+  var index = $("#index").attr('href');
+
+  $.post(loginUrl, $("#loginForm").serialize()).done(
+    function (msg) {
+      var user = $.parseJSON(msg);
+      // clear the errors
+      $("#error").text("");
+      // redirect to index.html
+      $(location).attr('href', index);
+    }
+  )
+  .fail(function (xhr, textStatus, errorThrown) {
+    var elem = $("#error");
+    elem.html(xhr.responseText);
+    elem.text(textStatus + "(" + xhr.status + "): " + elem.text());
+  });
 }
 
 /**
  * call rest service /logout to clear the session tokens.
  */
 function logout() {
-    var logoutUrl = $("#logoutUrl").attr('href');
-    $.post(logoutUrl)
+  var logoutUrl = $("#logoutUrl").attr('href');
+  $.post(logoutUrl)
 }
 
-$(document).ready(function() {
-    // Send a initial logout to clear the sessions.
-    logout();
+function displaySocialLoginIcons() {
+  var loginUrl = $("#loginUrl").attr('href');
+  var oauth2Root = loginUrl + "/oauth2";
+  var providersUrl = oauth2Root + "/providers";
+
+  var socialLogin = $("#social_login");
+
+  $.get(providersUrl).done(
+    function (msg) {
+      var providers = $.parseJSON(msg);
+      console.log(providers);
+
+      var body = "";
+
+      for (var provider in providers) {
+        var icon = providers[provider];
+        body += "<a href=" + oauth2Root + "/" + provider + "/" + "authorize>";
+        body += "<img src=" + icon + " alt=" + provider + "/> ";
+        body += "</a>";
+      }
+
+      if (body != "") {
+        body = "Social login: " + body
+      }
+
+      socialLogin.html(body);
+    }
+  )
+}
+
+$(document).ready(function () {
+  // Send a initial logout to clear the sessions.
+  logout();
+
+  // Fetch and display social login icons.
+  displaySocialLoginIcons()
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/main/scala/io/gearpump/services/RestServices.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/main/scala/io/gearpump/services/RestServices.scala b/services/jvm/src/main/scala/io/gearpump/services/RestServices.scala
index bf6a7fa..f2761d6 100644
--- a/services/jvm/src/main/scala/io/gearpump/services/RestServices.scala
+++ b/services/jvm/src/main/scala/io/gearpump/services/RestServices.scala
@@ -42,7 +42,7 @@ class RestServices(master: ActorRef, mat: ActorMaterializer, system: ActorSystem
 
   private val LOG = LogUtil.getLogger(getClass)
 
-  private val securityEnabled = config.getBoolean(Constants.GEARPUMP_UI_SECURITY_ENABLED)
+  private val securityEnabled = config.getBoolean(Constants.GEARPUMP_UI_SECURITY_AUTHENTICATION_ENABLED)
 
   private val supervisorPath = system.settings.config.getString(Constants.GEARPUMP_SERVICE_SUPERVISOR_PATH)
 

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/main/scala/io/gearpump/services/SecurityService.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/main/scala/io/gearpump/services/SecurityService.scala b/services/jvm/src/main/scala/io/gearpump/services/SecurityService.scala
index 317273c..387ff45 100644
--- a/services/jvm/src/main/scala/io/gearpump/services/SecurityService.scala
+++ b/services/jvm/src/main/scala/io/gearpump/services/SecurityService.scala
@@ -19,8 +19,8 @@
 package io.gearpump.services
 
 import akka.actor.{ActorSystem}
-import akka.http.scaladsl.model.RemoteAddress
-import akka.http.scaladsl.model.headers.HttpChallenge
+import akka.http.scaladsl.model.{Uri, StatusCodes, RemoteAddress}
+import akka.http.scaladsl.model.headers.{HttpCookiePair, HttpCookie, HttpChallenge}
 import akka.http.scaladsl.server.AuthenticationFailedRejection.{CredentialsMissing}
 import akka.http.scaladsl.server._
 import akka.http.scaladsl.server.Directives._
@@ -31,10 +31,12 @@ import com.softwaremill.session.SessionDirectives._
 import com.softwaremill.session._
 import com.typesafe.config.Config
 import io.gearpump.services.SecurityService.{User, UserSession}
+import io.gearpump.services.security.oauth2.OAuth2Authenticator
 import io.gearpump.util.{Constants, LogUtil}
 import upickle.default.{write}
 import io.gearpump.security.{Authenticator => BaseAuthenticator}
 import scala.concurrent.{ExecutionContext, Future}
+import scala.util.{Failure, Success}
 
 /**
  * When user cannot be authenticated, will reject with 401 AuthenticationFailedRejection
@@ -71,6 +73,23 @@ class SecurityService(inner: RouteService, implicit val system: ActorSystem) ext
     authenticator
   }
 
+  private def configToMap(config : Config, path: String) = {
+    import scala.collection.JavaConverters._
+    config.getConfig(path).root.unwrapped.asScala.toMap map { case (k, v) => k -> v.toString }
+  }
+
+  private val oauth2Providers: Map[String, String] = {
+    if (config.getBoolean(Constants.GEARPUMP_UI_OAUTH2_AUTHENTICATOR_ENABLED)) {
+      val map = configToMap(config, Constants.GEARPUMP_UI_OAUTH2_AUTHENTICATORS)
+      map.keys.toList.map { key =>
+        val iconPath = config.getString(s"${Constants.GEARPUMP_UI_OAUTH2_AUTHENTICATORS}.$key.icon")
+        (key, iconPath)
+      }.toMap
+    } else {
+      Map.empty[String, String]
+    }
+  }
+
   private def authenticate(user: String, pass: String)(implicit ec: ExecutionContext): Future[Option[UserSession]] = {
     authenticator.authenticate(user, pass, ec).map{ result =>
       if (result.authenticated) {
@@ -97,11 +116,18 @@ class SecurityService(inner: RouteService, implicit val system: ActorSystem) ext
     }
   }
 
-  private def login(session: UserSession, ip: String): Route = {
-    setSession(session) { ctx =>
+  private def login(session: UserSession, ip: String, redirectToRoot: Boolean = false): Route = {
+    setSession(session) {
       val user = session.user
-      LOG.info(s"user $user login from $ip")
-      ctx.complete(write(new User(user)))
+      val maxAgeMs = 1000 * sessionConfig.clientSessionMaxAgeSeconds.getOrElse(24 * 3600L) // default 1 day
+      setCookie(HttpCookie.fromPair(HttpCookiePair("username", user), path = Some("/"), maxAge = Some(maxAgeMs))) {
+        LOG.info(s"user $user login from $ip")
+        if (redirectToRoot) {
+          redirect(Uri("/"), StatusCodes.TemporaryRedirect)
+        } else {
+          complete(write(new User(user)))
+        }
+      }
     }
   }
 
@@ -129,7 +155,6 @@ class SecurityService(inner: RouteService, implicit val system: ActorSystem) ext
     }
   }
 
-
   private val unknownIp: Directive1[RemoteAddress] = {
     Directive[Tuple1[RemoteAddress]]{ inner =>
       inner(new Tuple1(RemoteAddress.Unknown))
@@ -141,22 +166,68 @@ class SecurityService(inner: RouteService, implicit val system: ActorSystem) ext
     extractExecutionContext{implicit ec: ExecutionContext =>
     extractMaterializer{implicit mat: Materializer =>
     (extractClientIP | unknownIp) { ip =>
-     path("login") {
-        get {
-          pathEndOrSingleSlash {
+     pathPrefix("login") {
+       pathEndOrSingleSlash {
+          get {
             getFromResource("login/login.html")
+          } ~
+          post {
+            // Guest account don't have permission to submit new application in UI
+            formField(FieldMagnet('username.as[String])) {user: String =>
+              formFields(FieldMagnet('password.as[String])) {pass: String =>
+                val result = authenticate(user, pass)
+                onSuccess(result){
+                  case Some(session) =>
+                    login(session, ip.toString)
+                  case None =>
+                    authenticationFailed
+                }
+              }
+            }
           }
         } ~
-        post {
-          // Guest account don't have permission to submit new application in UI
-          formField(FieldMagnet('username.as[String])) {user: String =>
-            formFields(FieldMagnet('password.as[String])) {pass: String =>
-              val result = authenticate(user, pass)
-              onSuccess(result){
-                case Some(session) =>
-                  login(session, ip.toString)
-                case None =>
-                  authenticationFailed
+        path ("oauth2" / "providers") {
+          // respond with a list of OAuth2 providers.
+          complete(write(oauth2Providers))
+        } ~
+        // Support OAUTH Authentication
+        pathPrefix ("oauth2"/ Segment) {providerName =>
+        // Resolve OAUTH Authentication Provider
+        val oauthService = OAuth2Authenticator.get(config, providerName)
+
+          if (oauthService == null) {
+            // OAuth2 is disabled.
+            complete(StatusCodes.NotFound)
+          } else {
+
+            def loginWithOAuth2Parameters(parameters: Map[String, String]): Route = {
+              val result = oauthService.authenticate(parameters)
+              onComplete(result){
+                case Success(session) =>
+                  login(session, ip.toString, redirectToRoot = true)
+                case Failure(ex) => {
+                  LOG.info(s"Failed to login user from ${ip.toString}", ex)
+                  failWith(ex)
+                }
+              }
+            }
+
+            path ("authorize") {
+              // redirect to OAuth2 service provider for authorization.
+              redirect(Uri(oauthService.getAuthorizationUrl), StatusCodes.TemporaryRedirect)
+            } ~
+            path ("accesstoken") {
+              post {
+                // Guest account don't have permission to submit new application in UI
+                formField(FieldMagnet('accesstoken.as[String])) {accesstoken: String =>
+                  loginWithOAuth2Parameters(Map("accesstoken" -> accesstoken))
+                }
+              }
+            } ~
+            path("callback") {
+              // Login with authorization code or access token.
+              parameterMap {parameters =>
+                loginWithOAuth2Parameters(parameters)
               }
             }
           }

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/OAuth2Authenticator.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/OAuth2Authenticator.scala b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/OAuth2Authenticator.scala
new file mode 100644
index 0000000..f44d67a
--- /dev/null
+++ b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/OAuth2Authenticator.scala
@@ -0,0 +1,144 @@
+/*
+ * 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 io.gearpump.services.security.oauth2
+
+import com.typesafe.config.Config
+import io.gearpump.services.SecurityService.UserSession
+import io.gearpump.util.Constants
+import io.gearpump.util.Constants._
+import scala.concurrent.Future
+
+/**
+ *
+ * Uses OAuth2 social-login as the mechanism for authentication.
+ * @see [[https://tools.ietf.org/html/rfc6749]] to find what is OAuth2, and how it works.
+ *
+ * Basically flow for OAuth2 Authentication:
+ * 1. User accesses Gearpump UI website, and choose to login with OAuth2 server.
+ * 2. Gearpump UI website redirects user to OAuth2 server domain authorization endpoint.
+ * 3. End user complete the authorization in the domain of OAuth2 server.
+ * 4. OAuth2 server redirects user back to Gearpump UI server.
+ * 5. Gearpump UI server verify the tokens and extract credentials from query
+ * parameters and form fields.
+ *
+ * @note '''Thread-safety''' is a MUST requirement. Developer need to ensure the sub-class is thread-safe.
+ * Sub-class should have a parameterless constructor.
+ *
+ * @note OAuth2 Authenticator requires access of Internet. Please make sure HTTP proxy are
+ * set properly if applied.
+ *
+ * @example Config proxy when UI server is started on Windows:
+ * {{{
+ *   > set JAVA_OPTS=-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088
+ *   > bin\services
+ * }}}
+ *
+ * @example Config proxy when UI server is started on Linux:
+ * {{{
+ *   $ export JAVA_OPTS="-Dhttp.proxyHost=xx.com -Dhttp.proxyPort=8088 -Dhttps.proxyHost=xx.com -Dhttps.proxyPort=8088"
+ *   $ bin/services
+ * }}}
+ *
+ */
+trait OAuth2Authenticator {
+
+  /**
+   * Inits authenticator with config which contains client ID, client secret, and etc..
+   *
+   * Typically, the client key and client secret is provided by OAuth2 Authorization server when user
+   * register an application there.
+   * @see [[https://tools.ietf.org/html/rfc6749]] for definition of client, client Id,
+   * and client secret.
+   *
+   * See [[https://developer.github.com/v3/oauth/]] for an actual example of how Github
+   * use client key, and client secret.
+   *
+   * @note '''Thread-Safety''': Framework ensures this call is synchronized.
+   *
+   * @param config Client Id, client secret, callback URL and etc..
+   */
+  def init(config: Config): Unit
+
+  /**
+   * Returns the OAuth Authorization URL so for redirection to that address to do OAuth2
+   * authorization.
+   *
+   * @note '''Thread-Safety''': This can be called in a multi-thread environment. Developer
+   *      need to ensure thread safety.
+   */
+  def getAuthorizationUrl: String
+
+  /**
+   * After authorization, OAuth2 server redirects user back with tokens. This verify the
+   * tokens, retrieve the profiles, and return [[UserSession]] information.
+   *
+   * @note This is an Async call.
+   * @note This call requires external internet access.
+   * @note '''Thread-Safety''': This can be called in a multi-thread environment. Developer
+   *      need to ensure thread safety.
+   *
+   * @param parameters HTTP Query and Post parameters, which typically contains Authorization code.
+   * @return UserSession if authentication pass.
+   */
+  def authenticate(parameters: Map[String, String]): Future[UserSession]
+
+  /**
+   * Clean resource
+   */
+  def close(): Unit
+}
+
+object OAuth2Authenticator {
+
+  // Serves as a quick immutable lookup cache
+  private var providers = Map.empty[String, OAuth2Authenticator]
+
+  /**
+   * Load Authenticator from [[Constants.GEARPUMP_UI_OAUTH2_AUTHENTICATORS]]
+   *
+   * @param provider, Name for the OAuth2 Authentication Service.
+   * @return Returns null if the OAuth2 Authentication is disabled.
+   */
+  def get(config: Config, provider: String): OAuth2Authenticator = {
+
+    if (providers.contains(provider)) {
+      providers(provider)
+    } else {
+      val path = s"${Constants.GEARPUMP_UI_OAUTH2_AUTHENTICATORS}.$provider"
+      val enabled = config.getBoolean(Constants.GEARPUMP_UI_OAUTH2_AUTHENTICATOR_ENABLED)
+      if (enabled && config.hasPath(path)) {
+        this.synchronized {
+          if (providers.contains(provider)) {
+            providers(provider)
+          } else {
+            val authenticatorConfig = config.getConfig(path)
+            val authenticatorClass = authenticatorConfig.getString(GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CLASS)
+            val clazz = Thread.currentThread().getContextClassLoader.loadClass(authenticatorClass)
+            val authenticator = clazz.newInstance().asInstanceOf[OAuth2Authenticator]
+            authenticator.init(authenticatorConfig)
+            providers += provider -> authenticator
+            authenticator
+          }
+        }
+      } else {
+        null
+      }
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/BaseOAuth2Authenticator.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/BaseOAuth2Authenticator.scala b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/BaseOAuth2Authenticator.scala
new file mode 100644
index 0000000..851053d
--- /dev/null
+++ b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/BaseOAuth2Authenticator.scala
@@ -0,0 +1,217 @@
+/*
+ * 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 io.gearpump.services.security.oauth2.impl
+
+import java.util.concurrent.atomic.AtomicBoolean
+
+import com.github.scribejava.core.builder.ServiceBuilderAsync
+import com.github.scribejava.core.builder.api.DefaultApi20
+import com.github.scribejava.core.model._
+import com.github.scribejava.core.oauth.OAuth20Service
+import com.github.scribejava.core.utils.OAuthEncoder
+import com.ning.http.client.AsyncHttpClientConfig
+import com.typesafe.config.Config
+import io.gearpump.security.Authenticator
+import io.gearpump.services.SecurityService.UserSession
+import io.gearpump.services.security.oauth2.OAuth2Authenticator
+import io.gearpump.services.security.oauth2.impl.BaseOAuth2Authenticator.BaseApi20
+import io.gearpump.util.Constants._
+import io.gearpump.util.Util
+
+import scala.collection.mutable.StringBuilder
+import scala.concurrent.{Future, Promise}
+
+/**
+ * Uses Ning AsyncClient to connect to OAuth2 service.
+ *
+ * @see [[OAuth2Authenticator]] for more API information.
+ */
+abstract class BaseOAuth2Authenticator extends OAuth2Authenticator {
+
+  // Authorize Url for end user to authorize
+  protected def authorizeUrl: String
+
+  // Used to fetch the Access Token.
+  protected def accessTokenEndpoint: String
+
+  // Protected resource Url to get the user profile
+  protected def protectedResourceUrl: String
+
+  // Extracts the username information from response of protectedResourceUrl
+  protected def extractUserName(body: String): String
+
+  // Scope required to access protectedResourceUrl
+  protected def scope: String
+
+  // OAuth2 endpoint definition for ScribeJava.
+  protected def oauth2Api(): DefaultApi20 = {
+    new BaseApi20(authorizeUrl, accessTokenEndpoint)
+  }
+
+  private var oauthService: OAuth20Service = null
+
+  private var defaultPermissionLevel = Authenticator.Guest.permissionLevel
+
+  // Synchronization ensured by the caller
+  override def init(config: Config): Unit = {
+    if (this.oauthService == null) {
+      val callback = config.getString(GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CALLBACK)
+      val clientId = config.getString(GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CLIENT_ID)
+      val clientSecret = config.getString(GEARPUMP_UI_OAUTH2_AUTHENTICATOR_CLIENT_SECRET)
+      defaultPermissionLevel = {
+        val role = config.getString(GEARPUMP_UI_OAUTH2_AUTHENTICATOR_DEFAULT_USER_ROLE)
+        role match {
+          case "guest" => Authenticator.Guest.permissionLevel
+          case "user" => Authenticator.User.permissionLevel
+          case "admin" => Authenticator.Admin.permissionLevel
+          case _ => Authenticator.UnAuthenticated.permissionLevel
+        }
+      }
+      this.oauthService = buildOAuth2Service(clientId, clientSecret, callback)
+    }
+  }
+
+  private val isClosed: AtomicBoolean = new AtomicBoolean(false)
+
+  override def close(): Unit = {
+    if (isClosed.compareAndSet(false, true)) {
+      if (null != oauthService && null != oauthService.getAsyncHttpClient()) {
+        oauthService.getAsyncHttpClient().close()
+      }
+    }
+  }
+
+  override def getAuthorizationUrl(): String = {
+    oauthService.getAuthorizationUrl()
+  }
+
+  override def authenticate(parameters: Map[String, String]): Future[UserSession] = {
+
+    val promise = Promise[UserSession]()
+    val code = parameters.get(GEARPUMP_UI_OAUTH2_AUTHENTICATOR_AUTHORIZATION_CODE)
+    val accessToken = parameters.get(GEARPUMP_UI_OAUTH2_AUTHENTICATOR_ACCESS_TOKEN)
+
+    def authenticateWithAccessToken(accessToken: OAuth2AccessToken): Unit = {
+
+      val request = new OAuthRequestAsync(Verb.GET, protectedResourceUrl, oauthService)
+      oauthService.signRequest(accessToken, request)
+      request.sendAsync {
+        new OAuthAsyncRequestCallback[Response] {
+          override def onCompleted(response: Response): Unit = {
+            try {
+              val user = extractUserName(response.getBody)
+              promise.success(new UserSession(user, defaultPermissionLevel))
+            } catch {
+              case ex: Throwable =>
+                promise.failure(ex)
+            }
+          }
+
+          override def onThrowable(throwable: Throwable): Unit = {
+            promise.failure(throwable)
+          }
+        }
+      }
+    }
+
+    def authenticateWithAuthorizationCode(code: String): Unit = {
+      oauthService.getAccessTokenAsync(code,
+
+        new OAuthAsyncRequestCallback[OAuth2AccessToken] {
+          override def onCompleted(accessToken: OAuth2AccessToken): Unit = {
+            authenticateWithAccessToken(accessToken)
+          }
+
+          override def onThrowable(throwable: Throwable): Unit = {
+            promise.failure(throwable)
+          }
+        })
+    }
+
+    if (accessToken.isDefined) {
+      authenticateWithAccessToken(new OAuth2AccessToken(accessToken.get))
+    }
+    else if (code.isDefined) {
+      authenticateWithAuthorizationCode(code.get)
+    } else {
+      // Fails authentication if code not exist
+      promise.failure(new Exception("Fail to authenticate user as there is no code parameter in URL"))
+    }
+
+    promise.future
+  }
+
+  private def buildOAuth2Service(clientId: String, clientSecret: String, callback: String): OAuth20Service = {
+    val state: String = "state" + Util.randInt
+    ScribeJavaConfig.setForceTypeOfHttpRequests(ForceTypeOfHttpRequest.FORCE_ASYNC_ONLY_HTTP_REQUESTS)
+    val clientConfig: AsyncHttpClientConfig = new AsyncHttpClientConfig.Builder()
+      .setMaxConnections(5)
+      .setUseProxyProperties(true)
+      .setRequestTimeout(60000)
+      .setAllowPoolingConnections(false)
+      .setPooledConnectionIdleTimeout(60000)
+      .setReadTimeout(60000).build
+
+    val service: OAuth20Service = new ServiceBuilderAsync()
+      .apiKey(clientId)
+      .apiSecret(clientSecret)
+      .scope(scope)
+      .state(state)
+      .callback(callback)
+      .asyncHttpClientConfig(clientConfig)
+      .build(oauth2Api())
+
+    service
+  }
+}
+
+object BaseOAuth2Authenticator {
+
+  class BaseApi20(authorizeUrl: String, accessTokenEndpoint: String) extends DefaultApi20 {
+    def getAccessTokenEndpoint: String = {
+      accessTokenEndpoint
+    }
+
+    def getAuthorizationUrl(config: OAuthConfig): String = {
+      val sb: StringBuilder = new StringBuilder(String.format(authorizeUrl, config.getResponseType, config.getApiKey, OAuthEncoder.encode(config.getCallback), OAuthEncoder.encode(config.getScope)))
+      val state: String = config.getState
+      if (state != null) {
+        sb.append('&').append(OAuthConstants.STATE).append('=').append(OAuthEncoder.encode(state))
+      }
+      return sb.toString
+    }
+
+    override def createService(config: OAuthConfig): OAuth20Service = {
+      return new OAuth20Service(this, config) {
+
+        protected override def createAccessTokenRequest[T <: AbstractRequest](code: String, request: T): T = {
+          super.createAccessTokenRequest(code, request)
+
+          if (!getConfig.hasGrantType) {
+            request.addParameter(OAuthConstants.GRANT_TYPE, OAuthConstants.AUTHORIZATION_CODE)
+          }
+
+          // Work-around for issue https://github.com/scribejava/scribejava/issues/641
+          request.addHeader("Content-Type", "application/x-www-form-urlencoded")
+          request
+        }
+      }
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/CloudFoundryUAAOAuth2Authenticator.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/CloudFoundryUAAOAuth2Authenticator.scala b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/CloudFoundryUAAOAuth2Authenticator.scala
new file mode 100644
index 0000000..75fa07a
--- /dev/null
+++ b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/CloudFoundryUAAOAuth2Authenticator.scala
@@ -0,0 +1,137 @@
+/*
+ * 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 io.gearpump.services.security.oauth2.impl
+
+import com.github.scribejava.core.builder.api.DefaultApi20
+import com.github.scribejava.core.model.{AbstractRequest, OAuthConfig, OAuthConstants}
+import com.github.scribejava.core.oauth.OAuth20Service
+import com.typesafe.config.Config
+import io.gearpump.services.security.oauth2.OAuth2Authenticator
+import io.gearpump.services.security.oauth2.impl.BaseOAuth2Authenticator.BaseApi20
+import io.gearpump.services.security.oauth2.impl.CloudFoundryUAAOAuth2Authenticator.CloudFoundryUAAService
+import spray.json.{JsString, _}
+import sun.misc.BASE64Encoder
+
+/**
+ *
+ * Does authentication with CloudFoundry UAA service. Currently it only
+ * extract the email address of end user.
+ *
+ * For what is UAA, please see:
+ * @see [[https://github.com/cloudfoundry/uaa for information about CloudFoundry UAA]]
+ *      (User Account and Authentication Service)
+ *
+ * Pre-requisite steps to use this Authenticator:
+ *
+ * Step1: Register your website to UAA with tool uaac.
+ * 1) Check tutorial on uaac at [[https://docs.cloudfoundry.org/adminguide/uaa-user-management.html]]
+ * 2) Open a bash shell, and login in as user admin by
+ * {{{
+ *    uaac token client get admin -s MyAdminPassword
+ * }}}
+ * 3) Create a new Application (Client) in UAA,
+ * {{{
+ *   uaac client add [your_client_id]
+ *     --scope openid
+ *     --authorized_grant_types "authorization_code client_credentials refresh_token"
+ *     --authorities openid
+ *     --redirect_uri [your_redirect_url]
+ *     --autoapprove true
+ *     --secret [your_client_secret]
+ * }}}
+ *
+ * Step2: Configure the OAuth2 information in gear.conf
+ * 1) Enable OAuth2 authentication by setting "gearpump.ui-security.oauth2-authenticator-enabled"
+ * as true.
+ * 2) Navigate to section "gearpump.ui-security.oauth2-authenticators.cloudfoundryuaa"
+ * 3) Config gear.conf "gearpump.ui-security.oauth2-authenticators.cloudfoundryuaa" section.
+ * Please make sure class name, client ID, client Secret, and callback URL are set properly.
+ *
+ * @note The callback URL here should matche what you set on CloudFoundry UAA in step1.
+ *
+ * Step3: Restart the UI service and try the "social login" button for UAA.
+ *
+ * @note OAuth requires Internet access, @see [[OAuth2Authenticator]] to find tutorials to configure
+ *       Internet proxy.
+ *
+ * @see [[OAuth2Authenticator]] for more background information of OAuth2.
+ */
+class CloudFoundryUAAOAuth2Authenticator extends BaseOAuth2Authenticator {
+
+  private var host: String = null
+
+  protected override def authorizeUrl: String = s"$host/oauth/authorize?response_type=%s&client_id=%s&redirect_uri=%s&scope=%s"
+
+  protected override def accessTokenEndpoint: String = s"$host/oauth/token"
+
+  protected override def protectedResourceUrl: String = s"$host/userinfo"
+
+  protected override def scope: String = "openid"
+
+  override def init(config: Config): Unit = {
+    host = config.getString("uaahost")
+    super.init(config)
+  }
+
+  protected override def extractUserName(body: String): String = {
+    val email = body.parseJson.asJsObject.fields("email").asInstanceOf[JsString]
+    email.value
+  }
+
+
+  protected override def oauth2Api(): DefaultApi20 = {
+    new CloudFoundryUAAService(authorizeUrl, accessTokenEndpoint)
+  }
+}
+
+object CloudFoundryUAAOAuth2Authenticator {
+  private val RESPONSE_TYPE = "response_type"
+
+  private class CloudFoundryUAAService(authorizeUrl: String, accessTokenEndpoint: String)
+    extends BaseApi20(authorizeUrl, accessTokenEndpoint) {
+
+    private def base64(in: String): String = {
+      val encoder = new BASE64Encoder()
+      val utf8 = "UTF-8"
+      encoder.encode(in.getBytes(utf8))
+    }
+
+    override def createService(config: OAuthConfig): OAuth20Service = {
+      return new OAuth20Service(this, config) {
+
+        protected override def createAccessTokenRequest[T <: AbstractRequest](code: String, request: T): T = {
+          val config: OAuthConfig = getConfig()
+
+          request.addParameter(OAuthConstants.GRANT_TYPE, OAuthConstants.AUTHORIZATION_CODE)
+          request.addParameter(OAuthConstants.CODE, code)
+          request.addParameter(RESPONSE_TYPE, "token")
+          request.addParameter(OAuthConstants.REDIRECT_URI, config.getCallback)
+
+          // Work around issue https://github.com/scribejava/scribejava/issues/641
+          request.addHeader("Content-Type", "application/x-www-form-urlencoded")
+
+          // CloudFoundry requires a Authorization header encoded with client Id and secret.
+          val authorizationHeader = "Basic " + base64(config.getApiKey + ":" + config.getApiSecret)
+          request.addHeader("Authorization", authorizationHeader)
+          request
+        }
+      }
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/GoogleOAuth2Authenticator.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/GoogleOAuth2Authenticator.scala b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/GoogleOAuth2Authenticator.scala
new file mode 100644
index 0000000..54e6eef
--- /dev/null
+++ b/services/jvm/src/main/scala/io/gearpump/services/security/oauth2/impl/GoogleOAuth2Authenticator.scala
@@ -0,0 +1,100 @@
+/*
+ * 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 io.gearpump.services.security.oauth2.impl
+
+import com.github.scribejava.apis.google.GoogleJsonTokenExtractor
+import com.github.scribejava.core.builder.api.DefaultApi20
+import com.github.scribejava.core.extractors.TokenExtractor
+import com.github.scribejava.core.model._
+import io.gearpump.services.security.oauth2.OAuth2Authenticator
+import io.gearpump.services.security.oauth2.impl.GoogleOAuth2Authenticator.AsyncGoogleApi20
+import spray.json._
+
+/**
+ *
+ * Does authentication with Google OAuth2 service. It only extract the email address
+ * from user profile of Google.
+ *
+ * Pre-requisite steps to use this Authenticator:
+ *
+ * Step1: Register your website as an OAuth2 Application on Google
+ * 1) Create an application representing your website at [[https://console.developers.google.com]]
+ * 2) In "API Manager" of your created application, enable API "Google+ API"
+ * 3) Create OAuth client ID for this application. In "Credentials" tab of "API Manager",
+ * choose "Create credentials", and then select OAuth client ID. Follow the wizard
+ * to set callback URL, and generate client ID, and client Secret. Callback URL is NOT optional.
+ *
+ * Step2: Configure the OAuth2 information in gear.conf
+ * 1) Enable OAuth2 authentication by setting "gearpump.ui-security.oauth2-authenticator-enabled"
+ * as true.
+ * 2) Configure section "gearpump.ui-security.oauth2-authenticators.google". Please make sure
+ * class name, client ID, client Secret, and callback URL are set properly.
+ *
+ * @note callback URL set here should match what is configured on Google in step1.
+ *
+ * Step3: Restart the UI service and try out the Google social login button in UI.
+ *
+ * @note OAuth requires Internet access, @see [[OAuth2Authenticator]] to find some helpful tutorials.
+ *
+ * @note Google use scope to define what data can be fetched by OAuth2. Currently we use profile
+ * [[https://www.googleapis.com/auth/userinfo.email]]. However, Google may change the profile in future.
+ *
+ * @todo Currently, this doesn't verify the state from Google OAuth2 response.
+ *
+ * @see [[OAuth2Authenticator]] for more API information.
+ */
+class GoogleOAuth2Authenticator extends BaseOAuth2Authenticator {
+
+  import GoogleOAuth2Authenticator._
+
+  protected override def authorizeUrl: String = AuthorizeUrl
+
+  protected override def accessTokenEndpoint: String = AccessEndpoint
+
+  protected override def protectedResourceUrl: String = ResourceUrl
+
+  protected override def scope: String = GoogleOAuth2Authenticator.Scope
+
+  protected override def extractUserName(body: String): String = {
+    val emails = body.parseJson.asJsObject.fields("emails").asInstanceOf[JsArray]
+    val email = emails.elements(0).asJsObject("Cannot find email account")
+      .fields("value").asInstanceOf[JsString].value
+    email
+  }
+
+  override def oauth2Api(): DefaultApi20 = new AsyncGoogleApi20(authorizeUrl, accessTokenEndpoint)
+}
+
+object GoogleOAuth2Authenticator {
+
+  import BaseOAuth2Authenticator._
+
+  val AuthorizeUrl = "https://accounts.google.com/o/oauth2/auth?response_type=%s&client_id=%s&redirect_uri=%s&scope=%s"
+  val AccessEndpoint = "https://www.googleapis.com/oauth2/v4/token"
+  val ResourceUrl = "https://www.googleapis.com/plus/v1/people/me"
+  val Scope = "https://www.googleapis.com/auth/userinfo.email"
+
+  private class AsyncGoogleApi20(authorizeUrl: String, accessEndpoint: String)
+      extends BaseApi20(authorizeUrl, accessEndpoint) {
+
+    override def getAccessTokenExtractor: TokenExtractor[OAuth2AccessToken] = {
+      GoogleJsonTokenExtractor.instance
+    }
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/CloudFoundryUAAOAuth2AuthenticatorSpec.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/CloudFoundryUAAOAuth2AuthenticatorSpec.scala b/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/CloudFoundryUAAOAuth2AuthenticatorSpec.scala
new file mode 100644
index 0000000..3e3e0b0
--- /dev/null
+++ b/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/CloudFoundryUAAOAuth2AuthenticatorSpec.scala
@@ -0,0 +1,130 @@
+/*
+ * 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 io.gearpump.services.security.oauth2
+
+import akka.actor.ActorSystem
+import akka.http.javadsl.model.HttpEntityStrict
+import akka.http.scaladsl.model.MediaTypes._
+import akka.http.scaladsl.model.Uri.Path
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.testkit.ScalatestRouteTest
+import com.typesafe.config.ConfigFactory
+import io.gearpump.security.Authenticator
+import io.gearpump.services.security.oauth2.impl.{CloudFoundryUAAOAuth2Authenticator, GoogleOAuth2Authenticator}
+import org.scalatest.FlatSpec
+import scala.concurrent.Await
+import scala.concurrent.duration._
+import scala.collection.JavaConverters._
+
+class CloudFoundryUAAOAuth2AuthenticatorSpec extends FlatSpec with ScalatestRouteTest {
+
+  implicit val actorSystem: ActorSystem = system
+  private val server = new MockOAuth2Server(system, null)
+  server.start()
+  private val serverHost = s"http://127.0.0.1:${server.port}"
+
+  val configMap = Map(
+    "class" -> "io.gearpump.services.security.oauth2.impl.CloudFoundryUAAOAuth2Authenticator",
+    "callback" -> s"$serverHost/login/oauth2/cloudfoundryuaa/callback",
+    "clientid" -> "gearpump_test2",
+    "clientsecret" -> "gearpump_test2",
+    "default-userrole" -> "user",
+    "icon" -> "/icons/uaa.png",
+    "uaahost" -> serverHost)
+
+  val configString = ConfigFactory.parseMap(configMap.asJava)
+
+  private val uaa = new CloudFoundryUAAOAuth2Authenticator
+  uaa.init(configString)
+
+  it should "generate the correct authorization request" in {
+    val parameters = Uri(uaa.getAuthorizationUrl()).query.toMap
+    assert(parameters("response_type") == "code")
+    assert(parameters("client_id") == configMap("clientid"))
+    assert(parameters("redirect_uri") == configMap("callback"))
+    assert(parameters("scope") == "openid")
+  }
+
+  it should "authenticate the authorization code and return the correct profile" in {
+    val code = Map("code" -> "QGGVeA")
+    val accessToken = "e2922002-0218-4513-a62d-1da2ba64ee4c"
+    val refreshToken = "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI2Nm"
+    val mail = "test@gearpump.io"
+
+    def accessTokenEndpoint(request: HttpRequest) = {
+      assert(request.getHeader("Authorization").get.value() == "Basic Z2VhcnB1bXBfdGVzdDI6Z2VhcnB1bXBfdGVzdDI=")
+      assert(request.entity.contentType().mediaType.value == "application/x-www-form-urlencoded")
+
+      val body = request.entity.asInstanceOf[HttpEntityStrict].data().decodeString("UTF-8")
+      val form = Uri./.withQuery(body).query.toMap
+
+      assert(form("grant_type") == "authorization_code")
+      assert(form("code") == "QGGVeA")
+      assert(form("response_type") == "token")
+      assert(form("redirect_uri") == configMap("callback"))
+
+      val response =
+        s"""
+          |{
+          |  "access_token": "$accessToken",
+          |  "token_type": "bearer",
+          |  "refresh_token": "$refreshToken",
+          |  "expires_in": 43199,
+          |  "scope": "openid",
+          |  "jti": "e8739474-b2fa-42eb-a9ad-e065bf79d7e9"
+          |}
+        """.stripMargin
+      HttpResponse(entity = HttpEntity(ContentType(`application/json`), response))
+    }
+
+    def protectedResourceEndpoint(request: HttpRequest) = {
+      assert(request.getUri().parameter("access_token").get == accessToken)
+      val response =
+        s"""
+          |{
+          |    "user_id": "e2922002-0218-4513-a62d-1da2ba64ee4c",
+          |    "user_name": "user",
+          |    "email": "$mail"
+          |}
+        """.stripMargin
+      HttpResponse(entity = HttpEntity(ContentType(`application/json`), response))
+    }
+
+    server.requestHandler = (request: HttpRequest) => {
+      if (request.uri.path.startsWith(Path("/oauth/token"))) {
+        accessTokenEndpoint(request)
+      } else if (request.uri.path.startsWith(Path("/userinfo"))) {
+        protectedResourceEndpoint(request)
+      } else {
+        fail("Unexpected access to " + request.uri.toString())
+      }
+    }
+
+    val userFuture = uaa.authenticate(code)
+    val user = Await.result(userFuture, 30 seconds)
+    assert(user.user == mail)
+    assert(user.permissionLevel == Authenticator.User.permissionLevel)
+  }
+
+  override def cleanUp(): Unit = {
+    server.stop()
+    uaa.close()
+    super.cleanUp()
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-gearpump/blob/099842ad/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/GoogleOAuth2AuthenticatorSpec.scala
----------------------------------------------------------------------
diff --git a/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/GoogleOAuth2AuthenticatorSpec.scala b/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/GoogleOAuth2AuthenticatorSpec.scala
new file mode 100644
index 0000000..8fbe43f
--- /dev/null
+++ b/services/jvm/src/test/scala/io/gearpump/services/security/oauth2/GoogleOAuth2AuthenticatorSpec.scala
@@ -0,0 +1,153 @@
+/*
+ * 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 io.gearpump.services.security.oauth2
+
+import akka.actor.ActorSystem
+import akka.http.javadsl.model.HttpEntityStrict
+import akka.http.scaladsl.model.MediaTypes._
+import akka.http.scaladsl.model.Uri.Path
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.testkit.ScalatestRouteTest
+import com.typesafe.config.ConfigFactory
+import io.gearpump.security.Authenticator
+import io.gearpump.services.security.oauth2.GoogleOAuth2AuthenticatorSpec.MockGoogleAuthenticator
+import io.gearpump.services.security.oauth2.impl.{GoogleOAuth2Authenticator, CloudFoundryUAAOAuth2Authenticator}
+import org.scalatest.FlatSpec
+
+import scala.collection.JavaConverters._
+import scala.concurrent.Await
+import scala.concurrent.duration._
+
+class GoogleOAuth2AuthenticatorSpec extends FlatSpec with ScalatestRouteTest {
+
+  implicit val actorSystem: ActorSystem = system
+  private val server = new MockOAuth2Server(system, null)
+  server.start()
+  private val serverHost = s"http://127.0.0.1:${server.port}"
+
+  val configMap = Map(
+    "class" -> "io.gearpump.services.security.oauth2.impl.GoogleOAuth2Authenticator",
+    "callback" -> s"$serverHost/login/oauth2/google/callback",
+    "clientid" -> "170234147043-a1tag68jtq6ab4bi11jvsj7vbaqcmhkt.apps.googleusercontent.com",
+    "clientsecret" -> "ioeWLLDipz2S7aTDXym2-obe",
+    "default-userrole" -> "guest",
+    "icon" -> "/icons/google.png")
+
+  val configString = ConfigFactory.parseMap(configMap.asJava)
+
+  private val google = new MockGoogleAuthenticator(serverHost)
+  google.init(configString)
+
+  it should "generate the correct authorization request" in {
+    val parameters = Uri(google.getAuthorizationUrl()).query.toMap
+    assert(parameters("response_type") == "code")
+    assert(parameters("client_id") == configMap("clientid"))
+    assert(parameters("redirect_uri") == configMap("callback"))
+    assert(parameters("scope") == GoogleOAuth2Authenticator.Scope)
+  }
+
+  it should "authenticate the authorization code and return the correct profile" in {
+    val code = Map("code" -> "4/PME0pfxjiBA42SukR-OTGl7fpFzTWzvZPf1TbkpXL4M#")
+    val accessToken = "e2922002-0218-4513-a62d-1da2ba64ee4c"
+    val refreshToken = "eyJhbGciOiJSUzI1NiJ9.eyJqdGkiOiI2Nm"
+    val mail = "test@gearpump.io"
+
+    def accessTokenEndpoint(request: HttpRequest) = {
+
+      assert(request.entity.contentType().mediaType.value == "application/x-www-form-urlencoded")
+
+      val body = request.entity.asInstanceOf[HttpEntityStrict].data().decodeString("UTF-8")
+      val form = Uri./.withQuery(body).query.toMap
+
+      assert(form("client_id") == configMap("clientid"))
+      assert(form("client_secret") == configMap("clientsecret"))
+      assert(form("grant_type") == "authorization_code")
+      assert(form("code") == code("code"))
+      assert(form("redirect_uri") == configMap("callback"))
+      assert(form("scope") == GoogleOAuth2Authenticator.Scope)
+
+      val response = s"""
+           |{
+           | "access_token": "$accessToken",
+           | "token_type": "Bearer",
+           | "expires_in": 3591,
+           | "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6ImY1NjQyYzY2MzdhYWQyOTJiOThlOGIwN2MwMzIxN2QwMzBmOTdkODkifQ.eyJpc3"
+           |}
+        """.stripMargin
+
+      HttpResponse(entity = HttpEntity(ContentType(`application/json`), response))
+    }
+
+    def protectedResourceEndpoint(request: HttpRequest) = {
+      assert(request.getUri().parameter("access_token").get == accessToken)
+      val response =s"""
+           |{
+           |   "kind": "plus#person",
+           |   "etag": "4OZ_Kt6ujOh1jaML_U6RM6APqoE/mZ57HcMOYXaNXYXS5XEGJ9yVsI8",
+           |   "nickname": "gearpump",
+           |   "gender": "female",
+           |   "emails": [
+           |     {
+           |       "value": "$mail",
+           |       "type": "account"
+           |     }
+           |   ]
+           | }
+        """.stripMargin
+      HttpResponse(entity = HttpEntity(ContentType(`application/json`), response))
+    }
+
+    server.requestHandler = (request: HttpRequest) => {
+      if (request.uri.path.startsWith(Path("/oauth2/v4/token"))) {
+        accessTokenEndpoint(request)
+      } else if (request.uri.path.startsWith(Path("/plus/v1/people/me"))) {
+        protectedResourceEndpoint(request)
+      } else {
+        fail("Unexpected access to " + request.uri.toString())
+      }
+    }
+
+    val userFuture = google.authenticate(code)
+    val user = Await.result(userFuture, 30 seconds)
+    assert(user.user == mail)
+    assert(user.permissionLevel == Authenticator.Guest.permissionLevel)
+  }
+
+  override def cleanUp(): Unit = {
+    server.stop()
+    google.close()
+    super.cleanUp()
+  }
+}
+
+object GoogleOAuth2AuthenticatorSpec {
+  class MockGoogleAuthenticator(host: String) extends GoogleOAuth2Authenticator {
+    protected override def authorizeUrl: String = {
+      super.authorizeUrl.replace("https://accounts.google.com", host)
+    }
+
+    protected override def accessTokenEndpoint: String = {
+      super.accessTokenEndpoint.replace("https://www.googleapis.com", host)
+    }
+
+    protected override def protectedResourceUrl: String = {
+      super.protectedResourceUrl.replace("https://www.googleapis.com", host)
+    }
+  }
+}
\ No newline at end of file