You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@servicecomb.apache.org by li...@apache.org on 2019/07/08 07:35:48 UTC

[servicecomb-fence] 01/02: [SCB-1350]support third party authentications

This is an automated email from the ASF dual-hosted git repository.

liubao pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/servicecomb-fence.git

commit bb5731ddb1ffcb534cda5f7ac9df215c04c05565
Author: liubao <bi...@qq.com>
AuthorDate: Fri Jul 5 16:42:37 2019 +0800

    [SCB-1350]support third party authentications
---
 .../server/GithubAccessTokenResponse.java}         |  37 ++++--
 .../authentication/server/GithubAuthService.java}  |  31 ++---
 .../server/GithubDynamicProperties.java            |  60 ++++++++++
 .../server/GithubDynamicPropertiesManager.java}    |  26 ++--
 .../authentication/server/GithubTokenGranter.java  | 133 +++++++++++++++++++++
 .../server/ThirdPartyProviderEndpoint.java         |  55 +++++++++
 .../server/ThirdPartyRegisterBootListener.java     |  46 +++++++
 .../server/ThirdPartyTokenGranter.java             |  58 +++++++++
 .../server/AuthenticationServerConstants.java      |  22 +++-
 .../server/ThirdPartyProviderService.java}         |  27 ++---
 .../authentication/token/JWTTokenImpl.java         |   5 +
 .../authentication/token/OpenIDToken.java          |   5 +
 .../authentication/token/SessionTokenImpl.java     |  14 ++-
 .../servicecomb/authentication/token/Token.java    |   2 +
 .../AuthenticationConfiguration.java               |   9 ++
 .../src/main/resources/microservice.yaml           |   5 +
 .../src/main/resources/ui/githubLoginCallback.html | 103 ++++++++++++++++
 .../EdgeService/src/main/resources/ui/js/login.js  |  55 +++++++++
 .../EdgeService/src/main/resources/ui/login.html   |   8 ++
 19 files changed, 635 insertions(+), 66 deletions(-)

diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubAccessTokenResponse.java
similarity index 54%
copy from api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
copy to api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubAccessTokenResponse.java
index 1f444c3..03bb9c5 100644
--- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubAccessTokenResponse.java
@@ -14,26 +14,39 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.servicecomb.authentication.server;
 
-package org.apache.servicecomb.authentication.token;
+// see: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/
+public class GithubAccessTokenResponse {
+  private String access_token;
 
-import java.util.Map;
+  private String scope;
 
-public interface Token {
-  String username();
+  private String token_type;
 
-  default boolean isExpired() {
-    return (System.currentTimeMillis() < getNotBefore()) ||
-        (System.currentTimeMillis() - getIssueAt() > getExpiresIn() * 1000);
+  public String getAccess_token() {
+    return access_token;
   }
 
-  long getIssueAt();
+  public void setAccess_token(String access_token) {
+    this.access_token = access_token;
+  }
+
+  public String getScope() {
+    return scope;
+  }
 
-  long getExpiresIn();
+  public void setScope(String scope) {
+    this.scope = scope;
+  }
 
-  long getNotBefore();
+  public String getToken_type() {
+    return token_type;
+  }
+
+  public void setToken_type(String token_type) {
+    this.token_type = token_type;
+  }
 
-  String getValue();
 
-  Map<String, Object> getAdditionalInformation();
 }
diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/AuthenticationServerConstants.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubAuthService.java
similarity index 53%
copy from api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/AuthenticationServerConstants.java
copy to api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubAuthService.java
index 193e6d8..08ebe4e 100644
--- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/AuthenticationServerConstants.java
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubAuthService.java
@@ -17,22 +17,23 @@
 
 package org.apache.servicecomb.authentication.server;
 
-public class AuthenticationServerConstants {
-  public static final String PARAM_GRANT_TYPE = "grant_type";
+import javax.ws.rs.FormParam;
+import javax.ws.rs.POST;
+import javax.ws.rs.Path;
 
-  public static final String PARAM_USERNAME = "username";
+import org.springframework.http.MediaType;
 
-  public static final String PARAM_PASSWORD = "password";
+import io.swagger.annotations.Api;
 
-  public static final String PARAM_REFRESH_TOKEN = "refresh_token";
-
-  public static final String PARAM_ACCESS_TOKEN = "access_token";
-
-  public static final String GRANT_TYPE_PASSWORD = "password";
-  
-  public static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
-  
-  public static final String CONFIG_GRANTER_PASSWORD_ENABLED = "servicecomb.authentication.granter.password.enabled";
-  
-  public static final String CONFIG_GRANTER_REFRESH_TOKEN_ENABLED = "servicecomb.authentication.granter.refreshToken.enabled";
+//see: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/
+@Path("/login/oauth")
+@Api(produces = MediaType.APPLICATION_JSON_VALUE)
+public interface GithubAuthService {
+  @POST
+  @Path("/access_token")
+  public GithubAccessTokenResponse accessToken(@FormParam("client_id") String client_id,
+      @FormParam("client_secret") String client_secret,
+      @FormParam("code") String code,
+      @FormParam("state") String state,
+      @FormParam("redirect_uri") String redirect_uri);
 }
diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubDynamicProperties.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubDynamicProperties.java
new file mode 100644
index 0000000..c61f0a7
--- /dev/null
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubDynamicProperties.java
@@ -0,0 +1,60 @@
+/*
+ * 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.servicecomb.authentication.server;
+
+import org.apache.servicecomb.config.inject.InjectProperties;
+import org.apache.servicecomb.config.inject.InjectProperty;
+
+@InjectProperties(prefix = "servicecomb.authentication.github")
+public class GithubDynamicProperties {
+  @InjectProperty(keys = "clientId")
+  private String clientId;
+
+  // TODO : support secret encryption
+  @InjectProperty(keys = "clientSecret")
+  private String clientSecret;
+
+  @InjectProperty(keys = "oauthAuthorizeURL", defaultValue = "https://github.com/login/oauth/authorize")
+  private String oauthAuthorizeURL;
+
+  public String getClientId() {
+    return clientId;
+  }
+
+  public void setClientId(String clientId) {
+    this.clientId = clientId;
+  }
+
+  public String getClientSecret() {
+    return clientSecret;
+  }
+
+  public void setClientSecret(String clientSecret) {
+    this.clientSecret = clientSecret;
+  }
+
+  public String getOauthAuthorizeURL() {
+    return oauthAuthorizeURL;
+  }
+
+  public void setOauthAuthorizeURL(String oauthAuthorizeURL) {
+    this.oauthAuthorizeURL = oauthAuthorizeURL;
+  }
+
+
+}
diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubDynamicPropertiesManager.java
similarity index 57%
copy from api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
copy to api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubDynamicPropertiesManager.java
index 1f444c3..69e5f6f 100644
--- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubDynamicPropertiesManager.java
@@ -15,25 +15,21 @@
  * limitations under the License.
  */
 
-package org.apache.servicecomb.authentication.token;
+package org.apache.servicecomb.authentication.server;
 
 import java.util.Map;
 
-public interface Token {
-  String username();
+import org.apache.servicecomb.config.inject.ConfigObjectFactory;
+import org.apache.servicecomb.foundation.common.concurrent.ConcurrentHashMapEx;
 
-  default boolean isExpired() {
-    return (System.currentTimeMillis() < getNotBefore()) ||
-        (System.currentTimeMillis() - getIssueAt() > getExpiresIn() * 1000);
-  }
-
-  long getIssueAt();
-
-  long getExpiresIn();
+public class GithubDynamicPropertiesManager {
+  private static final Map<String, GithubDynamicProperties> CONFIGURATIONS = new ConcurrentHashMapEx<>();
 
-  long getNotBefore();
+  private static final ConfigObjectFactory FACTORY = new ConfigObjectFactory();
 
-  String getValue();
-
-  Map<String, Object> getAdditionalInformation();
+  public static GithubDynamicProperties getGithubConfiguration() {
+    return CONFIGURATIONS.computeIfAbsent("key", key -> {
+      return FACTORY.create(GithubDynamicProperties.class);
+    });
+  }
 }
diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubTokenGranter.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubTokenGranter.java
new file mode 100644
index 0000000..4c372e6
--- /dev/null
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/GithubTokenGranter.java
@@ -0,0 +1,133 @@
+/*
+ * 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.servicecomb.authentication.server;
+
+import java.io.UnsupportedEncodingException;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.servicecomb.authentication.token.AbstractOpenIDTokenStore;
+import org.apache.servicecomb.authentication.token.OpenIDToken;
+import org.apache.servicecomb.authentication.util.CommonConstants;
+import org.apache.servicecomb.provider.pojo.RpcReference;
+import org.apache.servicecomb.provider.springmvc.reference.RestTemplateBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Component;
+import org.springframework.util.LinkedMultiValueMap;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriUtils;
+
+import com.netflix.config.DynamicPropertyFactory;
+
+@Component
+public class GithubTokenGranter implements ThirdPartyTokenGranter {
+  private static final Logger LOGGER = LoggerFactory.getLogger(GithubTokenGranter.class);
+
+  @Autowired
+  @Qualifier(CommonConstants.BEAN_AUTH_USER_DETAILS_SERVICE)
+  private UserDetailsService userDetailsService;
+
+  @Autowired
+  @Qualifier(CommonConstants.BEAN_AUTH_OPEN_ID_TOKEN_STORE)
+  private AbstractOpenIDTokenStore openIDTokenStore;
+
+  @RpcReference(microserviceName = "githubAuthService", schemaId = "githubAuthService")
+  GithubAuthService githubAuthService;
+
+  RestTemplate githubRestTemplate = RestTemplateBuilder.create();
+
+  @Override
+  public boolean enabled() {
+    return DynamicPropertyFactory.getInstance()
+        .getBooleanProperty(AuthenticationServerConstants.CONFIG_GRANTER_THIRD_GITHUB_ENABLED, true)
+        .get();
+  }
+
+  @Override
+  public String name() {
+    return AuthenticationServerConstants.GRANT_TYPE_THIRD_PARTY_GITHUB;
+  }
+
+  @Override
+  public TokenResponse grant(String code, String state, String login) {
+    GithubAccessTokenResponse response = null;
+    try {
+      HttpHeaders headers = new HttpHeaders();
+      headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
+      headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
+      MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
+      map.add("client_secret", GithubDynamicPropertiesManager.getGithubConfiguration().getClientSecret());
+      map.add("client_id", GithubDynamicPropertiesManager.getGithubConfiguration().getClientId());
+      map.add("code", code);
+      map.add("state", state);
+      HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, headers);
+      response = githubRestTemplate.postForObject("cse://githubAuthService/login/oauth/access_token",
+          request,
+          GithubAccessTokenResponse.class);
+    } catch (Exception e) {
+      LOGGER.error("Call github error. ", e);
+    }
+
+    if (StringUtils.isEmpty(login)) {
+      login = "anonymous";
+    }
+    try {
+      UserDetails userDetails = userDetailsService.loadUserByUsername("github:" + login);
+
+      OpenIDToken openIDToken = openIDTokenStore.createToken(userDetails);
+      openIDToken.addAdditionalInformation(AuthenticationServerConstants.TOKEN_ADDTIONAL_INFORMATION_GITHUB_TOKEN,
+          response);
+
+      openIDTokenStore.saveToken(openIDToken);
+      return TokenResponse.fromOpenIDToken(openIDToken);
+    } catch (UsernameNotFoundException e) {
+      return null;
+    }
+  }
+
+  @Override
+  public String providerInfo(String provider, String redirectURI, String login, String scope, String initialState) {
+    StringBuilder url = new StringBuilder();
+    url.append(GithubDynamicPropertiesManager.getGithubConfiguration().getOauthAuthorizeURL() + "?");
+    url.append("client_id=" + GithubDynamicPropertiesManager.getGithubConfiguration().getClientId() + "&");
+    try {
+      if (login != null) {
+        url.append("login=" + UriUtils.encode(login, "utf-8") + "&");
+        redirectURI = redirectURI + "&login=" + login;
+      }
+      if (scope != null) {
+        url.append("scope=" + UriUtils.encode(scope, "utf-8") + "&");
+      }
+      url.append("redirect_uri=" + UriUtils.encode(redirectURI, "utf-8") + "&");
+    } catch (UnsupportedEncodingException e) {
+      // will not happen, ignore
+    }
+    url.append("state=" + initialState);
+    return url.toString();
+  }
+
+}
diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyProviderEndpoint.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyProviderEndpoint.java
new file mode 100644
index 0000000..f8196a3
--- /dev/null
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyProviderEndpoint.java
@@ -0,0 +1,55 @@
+/*
+ * 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.servicecomb.authentication.server;
+
+import java.util.List;
+
+import org.apache.servicecomb.provider.rest.common.RestSchema;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.CookieValue;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+
+@RestSchema(schemaId = "ThirdPartyProviderEndpoint")
+@RequestMapping(path = "/v1/thirdParty")
+public class ThirdPartyProviderEndpoint implements ThirdPartyProviderService {
+  @Autowired
+  private List<ThirdPartyTokenGranter> granters;
+
+  @Override
+  @GetMapping(path = "/providerInfo/{provider}")
+  public String providerInfo(@PathVariable(name = "provider") String provider,
+      @RequestParam(name = "login", required = false) String login,
+      @RequestParam(name = "redirectURI") String redirectURI,
+      @RequestParam(name = "scope", required = false) String scope,
+      @CookieValue(name = "initialState") String initialState) {
+
+    for (ThirdPartyTokenGranter granter : granters) {
+      if (granter.enabled() && granter.name().equals(provider)) {
+        String info = granter.providerInfo(provider, redirectURI, login, scope, initialState);
+        if (info != null) {
+          return info;
+        }
+      }
+    }
+
+    return null;
+  }
+}
diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyRegisterBootListener.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyRegisterBootListener.java
new file mode 100644
index 0000000..e6ec1da
--- /dev/null
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyRegisterBootListener.java
@@ -0,0 +1,46 @@
+/*
+ * 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.servicecomb.authentication.server;
+
+import java.util.Collections;
+
+import org.apache.servicecomb.core.BootListener;
+import org.apache.servicecomb.serviceregistry.RegistryUtils;
+import org.springframework.stereotype.Component;
+
+//see: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps/
+@Component
+public class ThirdPartyRegisterBootListener implements BootListener {
+  private static final String GITHUB_ENDPOINT = "rest://github.com:443?sslEnabled=true";
+
+  @Override
+  public void onBootEvent(BootEvent event) {
+    if (BootListener.EventType.AFTER_REGISTRY.equals(event.getEventType())) {
+      RegistryUtils.getServiceRegistry().registerMicroserviceMappingByEndpoints(
+          // 3rd party rest service name
+          "githubAuthService",
+          // service version
+          "1.0.0",
+          // list of endpoints
+          Collections.singletonList(GITHUB_ENDPOINT),
+          // java interface class to generate swagger schema
+          GithubAuthService.class);
+    }
+  }
+
+}
diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyTokenGranter.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyTokenGranter.java
new file mode 100644
index 0000000..1ef4fc8
--- /dev/null
+++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyTokenGranter.java
@@ -0,0 +1,58 @@
+/*
+ * 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.servicecomb.authentication.server;
+
+import java.util.Map;
+
+import org.apache.commons.lang3.StringUtils;
+
+public interface ThirdPartyTokenGranter extends TokenGranter {
+
+  @Override
+  default String grantType() {
+    return AuthenticationServerConstants.GRANT_TYPE_THIRD_PARTY;
+  }
+
+  @Override
+  default TokenResponse grant(Map<String, String> parameters) {
+    String provider = parameters.get(AuthenticationServerConstants.PARAM_PROVIDER);
+    String code = parameters.get(AuthenticationServerConstants.PARAM_CODE);
+    String state = parameters.get(AuthenticationServerConstants.PARAM_STATE);
+    String login = parameters.get(AuthenticationServerConstants.PARAM_LOGIN);
+    
+    // login can be null
+    if (StringUtils.isEmpty(provider) || StringUtils.isEmpty(code) || StringUtils.isEmpty(state)) {
+      return null;
+    }
+
+    if (!name().equals(provider)) {
+      return null;
+    }
+
+    return grant(code, state, login);
+  }
+
+  String name();
+
+  TokenResponse grant(String code, String state, String login);
+  
+  /**
+   * In authorization code mode, need to get authentication provider information first.
+   */
+  String providerInfo(String provider, String redirectURI, String login, String scope, String initialState);
+}
diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/AuthenticationServerConstants.java b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/AuthenticationServerConstants.java
index 193e6d8..7b6f219 100644
--- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/AuthenticationServerConstants.java
+++ b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/AuthenticationServerConstants.java
@@ -28,11 +28,29 @@ public class AuthenticationServerConstants {
 
   public static final String PARAM_ACCESS_TOKEN = "access_token";
 
+  public static final String PARAM_PROVIDER = "provider";
+
+  public static final String PARAM_CODE = "code";
+
+  public static final String PARAM_STATE = "state";
+
+  public static final String PARAM_LOGIN = "login";
+  
   public static final String GRANT_TYPE_PASSWORD = "password";
+
+  public static final String GRANT_TYPE_THIRD_PARTY = "third_party";
+
+  public static final String GRANT_TYPE_THIRD_PARTY_GITHUB = "github";
   
   public static final String GRANT_TYPE_REFRESH_TOKEN = "refresh_token";
-  
+
   public static final String CONFIG_GRANTER_PASSWORD_ENABLED = "servicecomb.authentication.granter.password.enabled";
+
+  public static final String CONFIG_GRANTER_THIRD_GITHUB_ENABLED =
+      "servicecomb.authentication.granter.thirdParty.github.enabled";
+
+  public static final String CONFIG_GRANTER_REFRESH_TOKEN_ENABLED =
+      "servicecomb.authentication.granter.refreshToken.enabled";
   
-  public static final String CONFIG_GRANTER_REFRESH_TOKEN_ENABLED = "servicecomb.authentication.granter.refreshToken.enabled";
+  public static final String TOKEN_ADDTIONAL_INFORMATION_GITHUB_TOKEN = "github-token";
 }
diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyProviderService.java
similarity index 64%
copy from api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
copy to api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyProviderService.java
index 1f444c3..55dd29e 100644
--- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
+++ b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/ThirdPartyProviderService.java
@@ -15,25 +15,12 @@
  * limitations under the License.
  */
 
-package org.apache.servicecomb.authentication.token;
+package org.apache.servicecomb.authentication.server;
 
-import java.util.Map;
-
-public interface Token {
-  String username();
-
-  default boolean isExpired() {
-    return (System.currentTimeMillis() < getNotBefore()) ||
-        (System.currentTimeMillis() - getIssueAt() > getExpiresIn() * 1000);
-  }
-
-  long getIssueAt();
-
-  long getExpiresIn();
-
-  long getNotBefore();
-
-  String getValue();
-
-  Map<String, Object> getAdditionalInformation();
+/**
+ * Connecting third party oAuth providers
+ *
+ */
+public interface ThirdPartyProviderService {
+  String providerInfo(String provider, String redirectURI, String login, String scope, String initialState);
 }
diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTTokenImpl.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTTokenImpl.java
index 980f96e..1c8c133 100644
--- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTTokenImpl.java
+++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTTokenImpl.java
@@ -77,4 +77,9 @@ public class JWTTokenImpl implements JWTToken {
   public JWTClaims getClaims() {
     return this.claims;
   }
+
+  @Override
+  public void addAdditionalInformation(String key, Object value) {
+    this.claims.addAdditionalInformation(key, value);
+  }
 }
diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/OpenIDToken.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/OpenIDToken.java
index 2587619..d82b663 100644
--- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/OpenIDToken.java
+++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/OpenIDToken.java
@@ -105,4 +105,9 @@ public class OpenIDToken implements Token {
   public Map<String, Object> getAdditionalInformation() {
     return accessToken.getAdditionalInformation();
   }
+
+  @Override
+  public void addAdditionalInformation(String key, Object value) {
+    accessToken.addAdditionalInformation(key, value);
+  }
 }
diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/SessionTokenImpl.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/SessionTokenImpl.java
index d31e775..d409ee9 100644
--- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/SessionTokenImpl.java
+++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/SessionTokenImpl.java
@@ -17,6 +17,7 @@
 
 package org.apache.servicecomb.authentication.token;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.UUID;
 
@@ -29,6 +30,8 @@ public class SessionTokenImpl implements SessionToken {
 
   private TokenDynamicProperties config;
 
+  private Map<String, Object> additionalInformation;
+
   public SessionTokenImpl(String username) {
     this.value = UUID.randomUUID().toString();
     this.issueAt = System.currentTimeMillis();
@@ -58,12 +61,19 @@ public class SessionTokenImpl implements SessionToken {
 
   @Override
   public Map<String, Object> getAdditionalInformation() {
-    // TODO additional information is not used now
-    return null;
+    return additionalInformation;
   }
 
   @Override
   public String username() {
     return this.username;
   }
+
+  @Override
+  public void addAdditionalInformation(String key, Object value) {
+    if (additionalInformation == null) {
+      additionalInformation = new HashMap<>();
+    }
+    additionalInformation.put(key, value);
+  }
 }
diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
index 1f444c3..ca51f89 100644
--- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
+++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java
@@ -36,4 +36,6 @@ public interface Token {
   String getValue();
 
   Map<String, Object> getAdditionalInformation();
+  
+  void addAdditionalInformation(String key, Object value);
 }
diff --git a/samples/AuthenticationServer/src/main/java/org/apache/servicecomb/authentication/AuthenticationConfiguration.java b/samples/AuthenticationServer/src/main/java/org/apache/servicecomb/authentication/AuthenticationConfiguration.java
index 992bb33..0921bb6 100644
--- a/samples/AuthenticationServer/src/main/java/org/apache/servicecomb/authentication/AuthenticationConfiguration.java
+++ b/samples/AuthenticationServer/src/main/java/org/apache/servicecomb/authentication/AuthenticationConfiguration.java
@@ -67,9 +67,18 @@ public class AuthenticationConfiguration {
         Arrays.asList(new SimpleGrantedAuthority("GUEST")));
     UserDetails uGuestExpiresQuickly = new User("guestExpiresQuickly", passwordEncoder.encode("changeMyPassword"),
         Arrays.asList(new SimpleGrantedAuthority("GUEST")));
+
+    // Third party users
+    UserDetails githubAnonymous = new User("github:anonymous", "",
+        Arrays.asList(new SimpleGrantedAuthority("GUEST")));
+    UserDetails githubLiubao68 = new User("github:liubao68", "",
+        Arrays.asList(new SimpleGrantedAuthority("ADMIN")));
+
     manager.createUser(uAdmin);
     manager.createUser(uGuest);
     manager.createUser(uGuestExpiresQuickly);
+    manager.createUser(githubAnonymous);
+    manager.createUser(githubLiubao68);
     return manager;
   }
 }
diff --git a/samples/AuthenticationServer/src/main/resources/microservice.yaml b/samples/AuthenticationServer/src/main/resources/microservice.yaml
index 35ac8bb..2a2373d 100644
--- a/samples/AuthenticationServer/src/main/resources/microservice.yaml
+++ b/samples/AuthenticationServer/src/main/resources/microservice.yaml
@@ -40,3 +40,8 @@ servicecomb:
       expiresIn: 600
       guestExpiresQuickly:
         expiresIn: 3
+
+    github:
+      clientId: ? # change to your github client id
+      clientSecret: ? # change to your github client secret
+
diff --git a/samples/EdgeService/src/main/resources/ui/githubLoginCallback.html b/samples/EdgeService/src/main/resources/ui/githubLoginCallback.html
new file mode 100644
index 0000000..facbf7c
--- /dev/null
+++ b/samples/EdgeService/src/main/resources/ui/githubLoginCallback.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+
+<!--
+  ~ 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.
+  -->
+
+<html>
+
+    <head>
+        <title>Github Login in progress ...</title>
+        <link href="css/style.css" rel="stylesheet" type="text/css" media="all" />
+        <script type="text/javascript" src="js/jquery-1.11.1.min.js"></script>
+        <script type="text/javascript" src="js/login.js"></script>
+
+        <script> 
+             var executed = false;
+            (document.onready = function () {
+                if(executed) {
+                  return;
+                }
+                executed = true;
+                
+                <!-- getting query params -->
+                <!-- -->
+                var match,
+                    pl     = /\+/g,  // Regex for replacing addition symbol with a space
+                    search = /([^&=]+)=?([^&]*)/g,
+                    decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
+                    query  = window.location.search.substring(1);
+
+                var urlParams = {};
+                while (match = search.exec(query))
+                   urlParams[decode(match[1])] = decode(match[2]);
+                
+                if(getCookie("initialState") != urlParams["state"]) {
+                    window.alert("CSRF attack!");
+                    return;
+                }
+
+                if(!urlParams["code"]) {
+                    window.alert("Access denied!");
+                    return;
+                }
+                
+                var formData = {};
+                formData.code = urlParams["code"];
+                formData.state = urlParams["state"];
+                formData.provider = "github";
+                formData.grant_type = "third_party";
+
+                <!-- send code to backgroupd processing -->
+                 $.ajax({
+                    type: 'POST',
+                    url: "/v1/token",
+                    data: formData,
+                    success: function (data) {
+                        console.log(JSON.stringify(data));
+                        window.localStorage.setItem("token", JSON.stringify(data));
+                        window.location = "/ui/operation.html";
+                    },
+                    error: function(data) {
+                        console.log(JSON.stringify(data));
+                        var error = document.getElementById("error");
+                        error.textContent="Login failed";
+                        error.hidden=false;
+                    },
+                    async: true
+                });
+            })();
+        </script>
+    </head>
+
+    <body>
+
+    <div class="header">
+        <h2>Login With Github</h2>
+    </div>
+    <div class="section">
+        <form method="POST" enctype="multipart/form-data">
+            <input type="button" value="Login With Github" onclick="loginWithGithubAction()">
+        </form>
+    </div>
+    <div class="footer">
+        <p id="error" hidden="true" class="error"/>
+    </div>
+    </body>
+
+</html>
+
+
diff --git a/samples/EdgeService/src/main/resources/ui/js/login.js b/samples/EdgeService/src/main/resources/ui/js/login.js
index 69c3c9b..b811187 100644
--- a/samples/EdgeService/src/main/resources/ui/js/login.js
+++ b/samples/EdgeService/src/main/resources/ui/js/login.js
@@ -42,3 +42,58 @@ function loginAction() {
     });
 }
 
+function loginWithGithubAction() {
+    setCookie("initialState", Math.floor(100000000 + Math.random() * 900000000), 1);
+    var redirectURI = window.location.protocol + "//" 
+      + window.location.hostname + ":" + window.location.port
+      + "/ui/githubLoginCallback.html";
+    redirectURI = encodeURIComponent(redirectURI);
+    
+    $.ajax({
+        type: 'GET',
+        url: "/api/authentication-server/v1/thirdParty/providerInfo/github?redirectURI=" + redirectURI,
+        success: function (data) {
+            console.log(JSON.stringify(data));
+            window.location = data;
+        },
+        error: function(data) {
+            console.log(JSON.stringify(data));
+            var error = document.getElementById("error");
+            error.textContent="Login failed";
+            error.hidden=false;
+        },
+        async: true
+    });
+}
+
+/*
+*  https://www.w3schools.com/js/js_cookies.asp 
+*/
+function setCookie(name,value,days) {
+    var expires = "";
+    if (days) {
+        var date = new Date();
+        date.setTime(date.getTime() + (days*24*60*60*1000));
+        expires = "; expires=" + date.toUTCString();
+    }
+    document.cookie = name + "=" + (value || "")  + expires + "; path=/";
+}
+
+/*
+*  https://www.w3schools.com/js/js_cookies.asp 
+*/
+function getCookie(cname) {
+  var name = cname + "=";
+  var decodedCookie = decodeURIComponent(document.cookie);
+  var ca = decodedCookie.split(';');
+  for(var i = 0; i <ca.length; i++) {
+    var c = ca[i];
+    while (c.charAt(0) == ' ') {
+      c = c.substring(1);
+    }
+    if (c.indexOf(name) == 0) {
+      return c.substring(name.length, c.length);
+    }
+  }
+  return "";
+}
\ No newline at end of file
diff --git a/samples/EdgeService/src/main/resources/ui/login.html b/samples/EdgeService/src/main/resources/ui/login.html
index c8b45fb..85e5abe 100644
--- a/samples/EdgeService/src/main/resources/ui/login.html
+++ b/samples/EdgeService/src/main/resources/ui/login.html
@@ -37,6 +37,14 @@
             <input type="button" value="Login" onclick="loginAction()">
         </form>
     </div>
+    <div class="header">
+        <h2>Login With Github</h2>
+    </div>
+    <div class="section">
+        <form method="POST" enctype="multipart/form-data">
+            <input type="button" value="Login With Github" onclick="loginWithGithubAction()">
+        </form>
+    </div>
     <div class="footer">
         <p id="error" hidden="true" class="error"/>
     </div>