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 <[email protected]> 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>
