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 1d2e250c22aa1f09f87cdbb0b1e22833c5b0bed7 Author: liubao <[email protected]> AuthorDate: Tue May 28 15:09:55 2019 +0800 [SCB-1293]1. add TokenStore to support stateless or stateful session management 2. refactor test cases and change developers guide --- .../server/PasswordTokenGranter.java | 51 ++++----- .../server/RefreshTokenTokenGranter.java | 77 ++++++-------- .../authentication/server/TokenEndpoint.java | 6 +- .../authentication/server/TokenGranter.java | 4 +- api/authentication-server/service/pom.xml | 8 ++ .../server/{Token.java => TokenResponse.java} | 13 ++- .../authentication/server/TokenService.java | 2 +- .../token/AbstractSessionIDTokenStore.java} | 7 +- .../token/InMemorySessionIDTokenStore.java} | 32 +++--- .../servicecomb/authentication/token/JWTToken.java | 85 +++++++++++++++ .../authentication/token/JWTTokenStore.java | 71 +++++++++++++ .../authentication/token/SessionIDToken.java | 76 +++++++++++++ .../servicecomb/authentication/token/Token.java} | 18 +++- .../authentication/token/TokenStore.java} | 10 +- .../servicecomb/authentication/util/Constants.java | 5 +- .../authentication/edge/AuthHandler.java | 17 +-- .../authentication/edge/AuthenticationFilter.java | 0 .../edge/AuthenticationServerTokenEndpoint.java} | 27 ++--- .../edge/CustomVertxRestDispatcher.java | 1 + .../authentication/edge/EdgeTokenStore.java} | 10 +- .../edge/InMemoryEdgeTokenStore.java} | 28 +++-- .../authentication/edge/InternalAccessHandler.java | 0 .../authentication/edge/TokenEndpoint.java | 65 ++++++++++++ ...servicecomb.common.rest.filter.HttpServerFilter | 0 ...cecomb.transport.rest.vertx.VertxHttpDispatcher | 2 +- .../src/main/resources/config/cse.handler.xml | 0 api/edge-service/service/pom.xml | 3 +- .../authentication/edge}/TokenService.java | 8 +- .../resource/ResourceAuthHandler.java | 24 ++--- docs/zh_CN/developersGuide.md | 97 ++++++++++++----- .../AuthenticationConfiguration.java | 30 ++++-- .../authentication/AuthenticationTestCase.java | 117 ++++++++------------- .../authentication/BootEventListener.java | 7 +- .../authentication/GateRestTemplate.java | 9 ++ .../gateway/AuthenticationConfiguration.java | 10 +- .../resource/AuthenticationConfiguration.java | 19 +++- 36 files changed, 652 insertions(+), 287 deletions(-) diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/PasswordTokenGranter.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/PasswordTokenGranter.java index 7d16268..a1538ff 100644 --- a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/PasswordTokenGranter.java +++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/PasswordTokenGranter.java @@ -18,18 +18,13 @@ package org.apache.servicecomb.authentication.server; import java.util.Map; -import java.util.UUID; -import org.apache.servicecomb.authentication.jwt.JWTClaims; -import org.apache.servicecomb.authentication.jwt.JsonParser; +import org.apache.servicecomb.authentication.token.TokenStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; -import org.springframework.security.jwt.crypto.sign.Signer; import org.springframework.stereotype.Component; import com.netflix.config.DynamicPropertyFactory; @@ -45,40 +40,34 @@ public class PasswordTokenGranter implements TokenGranter { private PasswordEncoder passwordEncoder; @Autowired - @Qualifier("authSigner") - private Signer signer; + @Qualifier("authAccessTokenStore") + private TokenStore accessTokenStore; + + @Autowired + @Qualifier("authRefreshTokenStore") + private TokenStore refreshTokenStore; + + @Autowired + @Qualifier("authIDTokenStore") + private TokenStore idTokenStore; @Override - public Token grant(Map<String, String> parameters) { + public TokenResponse grant(Map<String, String> parameters) { String username = parameters.get(TokenConst.PARAM_USERNAME); String password = parameters.get(TokenConst.PARAM_PASSWORD); UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (passwordEncoder.matches(password, userDetails.getPassword())) { - JWTClaims claims = new JWTClaims(); - if (userDetails.getAuthorities() != null) { - userDetails.getAuthorities().forEach(authority -> claims.addAuthority(authority.getAuthority())); - } - claims.setJti(UUID.randomUUID().toString()); - claims.setIat(System.currentTimeMillis()); - claims.setExp(5 * 60); - - String content = JsonParser.unparse(claims); - Jwt accessToken = JwtHelper.encode(content, signer); - - Token token = new Token(); - token.setScope(claims.getScope()); + TokenResponse token = new TokenResponse(); + token.setAccess_token(accessTokenStore.createToken(userDetails).getValue()); + token.setRefresh_token(refreshTokenStore.createToken(userDetails).getValue()); + token.setId_token(idTokenStore.createToken(userDetails).getValue()); + + //TODO add parameters. + token.setScope(null); token.setExpires_in(10 * 60); token.setToken_type("bearer"); - token.setAccess_token(accessToken.getEncoded()); - - JWTClaims accessTokenClaims = new JWTClaims(); - accessTokenClaims.setJti(UUID.randomUUID().toString()); - accessTokenClaims.setIat(System.currentTimeMillis()); - accessTokenClaims.setExp(60 * 60); - Jwt refreshToken = JwtHelper.encode(JsonParser.unparse(claims), signer); - token.setRefresh_token(refreshToken.getEncoded()); - + return token; } else { return null; diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/RefreshTokenTokenGranter.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/RefreshTokenTokenGranter.java index 7a41e90..6980287 100644 --- a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/RefreshTokenTokenGranter.java +++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/RefreshTokenTokenGranter.java @@ -19,14 +19,12 @@ package org.apache.servicecomb.authentication.server; import java.util.Map; -import org.apache.servicecomb.authentication.jwt.JWTClaims; -import org.apache.servicecomb.authentication.jwt.JsonParser; +import org.apache.servicecomb.authentication.token.Token; +import org.apache.servicecomb.authentication.token.TokenStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; -import org.springframework.security.jwt.crypto.sign.SignatureVerifier; -import org.springframework.security.jwt.crypto.sign.Signer; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import com.netflix.config.DynamicPropertyFactory; @@ -34,12 +32,20 @@ import com.netflix.config.DynamicPropertyFactory; @Component(value = "fefreshTokenTokenGranter") public class RefreshTokenTokenGranter implements TokenGranter { @Autowired - @Qualifier("authSignatureVerifier") - private SignatureVerifier signerVerifier; + @Qualifier("authUserDetailsService") + private UserDetailsService userDetailsService; @Autowired - @Qualifier("authSigner") - private Signer signer; + @Qualifier("authAccessTokenStore") + private TokenStore accessTokenStore; + + @Autowired + @Qualifier("authRefreshTokenStore") + private TokenStore refreshTokenStore; + + @Autowired + @Qualifier("authIDTokenStore") + private TokenStore idTokenStore; @Override public boolean enabled() { @@ -54,42 +60,27 @@ public class RefreshTokenTokenGranter implements TokenGranter { } @Override - public Token grant(Map<String, String> parameters) { - String refreshToken = parameters.get(TokenConst.PARAM_REFRESH_TOKEN); - String accessToken = parameters.get(TokenConst.PARAM_ACCESS_TOKEN); - - // verify refresh tokens - Jwt jwt = JwtHelper.decode(refreshToken); - JWTClaims claims; - try { - jwt.verifySignature(signerVerifier); - claims = JsonParser.parse(jwt.getClaims(), JWTClaims.class); - // TODO: verify claims. - } catch (Exception e) { - return null; - } + public TokenResponse grant(Map<String, String> parameters) { + String refreshTokenValue = parameters.get(TokenConst.PARAM_REFRESH_TOKEN); - // verify access tokens - jwt = JwtHelper.decode(accessToken); - claims = null; - try { - jwt.verifySignature(signerVerifier); - claims = JsonParser.parse(jwt.getClaims(), JWTClaims.class); - // TODO: verify claims. - } catch (Exception e) { - return null; - } + Token refreshToken = refreshTokenStore.readTokenByValue(refreshTokenValue); - claims.setIat(System.currentTimeMillis()); - String content = JsonParser.unparse(claims); - Jwt newAccessToken = JwtHelper.encode(content, signer); + if (refreshToken != null && !refreshToken.isExpired()) { + UserDetails userDetails = userDetailsService.loadUserByUsername(refreshToken.username()); - Token token = new Token(); - token.setScope(claims.getScope()); - token.setExpires_in(10 * 60); - token.setToken_type("bearer"); - token.setAccess_token(newAccessToken.getEncoded()); - return token; + TokenResponse token = new TokenResponse(); + token.setAccess_token(accessTokenStore.createToken(userDetails).getValue()); + // refresh token is not generated + token.setRefresh_token(refreshTokenValue); + token.setId_token(idTokenStore.createToken(userDetails).getValue()); + + //TODO add parameters. + token.setScope(null); + token.setExpires_in(10 * 60); + token.setToken_type("bearer"); + return token; + } + return null; } } diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenEndpoint.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenEndpoint.java index 775d332..fa2ca32 100644 --- a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenEndpoint.java +++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenEndpoint.java @@ -29,19 +29,19 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @RestSchema(schemaId = "TokenEndpoint") -@RequestMapping(path = "/v1/oauth/token") +@RequestMapping(path = "/v1/token") public class TokenEndpoint implements TokenService { @Autowired private List<TokenGranter> granters; @Override @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED) - public Token getAccessToken(@RequestBody Map<String, String> parameters) { + public TokenResponse getToken(@RequestBody Map<String, String> parameters) { String grantType = parameters.get(TokenConst.PARAM_GRANT_TYPE); for (TokenGranter granter : granters) { if (granter.enabled()) { - Token token = granter.grant(grantType, parameters); + TokenResponse token = granter.grant(grantType, parameters); if (token != null) { return token; } diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java index 9ba0861..e5f600b 100644 --- a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java +++ b/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java @@ -29,12 +29,12 @@ public interface TokenGranter { String grantType(); - default Token grant(String grantType, Map<String, String> parameters) { + default TokenResponse grant(String grantType, Map<String, String> parameters) { if (grantType().equals(grantType)) { return grant(parameters); } return null; } - Token grant(Map<String, String> parameters); + TokenResponse grant(Map<String, String> parameters); } diff --git a/api/authentication-server/service/pom.xml b/api/authentication-server/service/pom.xml index 4433ff6..8f9b5e2 100644 --- a/api/authentication-server/service/pom.xml +++ b/api/authentication-server/service/pom.xml @@ -28,4 +28,12 @@ <artifactId>authentication-server-api-service</artifactId> <packaging>jar</packaging> + <dependencies> + <dependency> + <groupId>org.apache.servicecomb.authentication</groupId> + <artifactId>authentication-common-api-service</artifactId> + <version>${project.parent.version}</version> + </dependency> + </dependencies> + </project> diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/Token.java b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenResponse.java similarity index 88% rename from api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/Token.java rename to api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenResponse.java index 8758bd1..9bdc6d7 100644 --- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/Token.java +++ b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenResponse.java @@ -20,7 +20,7 @@ package org.apache.servicecomb.authentication.server; import java.util.Map; import java.util.Set; -public class Token { +public class TokenResponse { // Naming conventions https://tools.ietf.org/html/draft-ietf-oauth-v2-http-mac-00#section-3.1 private String token_type; @@ -28,6 +28,9 @@ public class Token { private String refresh_token; + // Naming conventions https://openid.net/specs/openid-connect-basic-1_0.html#ObtainingTokens + private String id_token; + private int expires_in; private Set<String> scope; @@ -69,6 +72,14 @@ public class Token { this.expires_in = expires_in; } + public String getId_token() { + return id_token; + } + + public void setId_token(String id_token) { + this.id_token = id_token; + } + public Set<String> getScope() { return scope; } diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java index b0cea22..8dbd197 100644 --- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java +++ b/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java @@ -20,5 +20,5 @@ package org.apache.servicecomb.authentication.server; import java.util.Map; public interface TokenService { - Token getAccessToken(Map<String, String> parameters); + TokenResponse getToken(Map<String, String> parameters); } diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/AbstractSessionIDTokenStore.java similarity index 83% copy from api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java copy to api/common/service/src/main/java/org/apache/servicecomb/authentication/token/AbstractSessionIDTokenStore.java index b0cea22..cb507a3 100644 --- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/AbstractSessionIDTokenStore.java @@ -15,10 +15,7 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.token; -import java.util.Map; - -public interface TokenService { - Token getAccessToken(Map<String, String> parameters); +public abstract class AbstractSessionIDTokenStore implements TokenStore { } diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/InMemorySessionIDTokenStore.java similarity index 55% copy from api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java copy to api/common/service/src/main/java/org/apache/servicecomb/authentication/token/InMemorySessionIDTokenStore.java index 9ba0861..5bac8f0 100644 --- a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/InMemorySessionIDTokenStore.java @@ -15,26 +15,28 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.token; +import java.util.HashMap; import java.util.Map; -/** - * Token granter is used to grant access tokens. - * @author Administrator - * - */ -public interface TokenGranter { - boolean enabled(); +import org.springframework.security.core.userdetails.UserDetails; - String grantType(); +public class InMemorySessionIDTokenStore extends AbstractSessionIDTokenStore { + private Map<String, SessionIDToken> tokens = new HashMap<>(); + + @SuppressWarnings("unchecked") + @Override + public <T extends Token> T createToken(UserDetails userDetails) { + SessionIDToken token = new SessionIDToken(userDetails.getUsername()); + tokens.put(token.getValue(), token); + return (T) token; + } - default Token grant(String grantType, Map<String, String> parameters) { - if (grantType().equals(grantType)) { - return grant(parameters); - } - return null; + @SuppressWarnings("unchecked") + @Override + public <T extends Token> T readTokenByValue(String value) { + return (T) tokens.get(value); } - Token grant(Map<String, String> parameters); } diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTToken.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTToken.java new file mode 100644 index 0000000..e989ae5 --- /dev/null +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTToken.java @@ -0,0 +1,85 @@ +/* + * 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.token; + +import java.util.Map; + +import org.apache.servicecomb.authentication.jwt.JWTClaims; +import org.apache.servicecomb.authentication.jwt.JsonParser; +import org.springframework.security.jwt.Jwt; +import org.springframework.security.jwt.JwtHelper; +import org.springframework.security.jwt.crypto.sign.Signer; + +public class JWTToken implements Token { + private JWTClaims claims; + + private boolean valueCalculated = false; + + private String value; + + private Signer signer; + + public JWTToken(JWTClaims claims, Signer signer) { + this.claims = claims; + this.signer = signer; + } + + @Override + public boolean isExpired() { + return System.currentTimeMillis() - this.getIssueAt() > this.getExpiration() * 1000; + } + + @Override + public long getIssueAt() { + return this.claims.getIat(); + } + + @Override + public long getExpiration() { + return this.claims.getExp(); + } + + @Override + public long getNotBefore() { + return this.claims.getNbf(); + } + + @Override + public String getValue() { + if (!this.valueCalculated) { + String content = JsonParser.unparse(claims); + Jwt jwtToken = JwtHelper.encode(content, signer); + this.value = jwtToken.getEncoded(); + } + return this.value; + } + + @Override + public Map<String, Object> getAdditionalInformation() { + return this.claims.getAdditionalInformation(); + } + + @Override + public String username() { + return this.claims.getSub(); + } + + public JWTClaims getClaims() { + return this.claims; + } +} diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTTokenStore.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTTokenStore.java new file mode 100644 index 0000000..2f394e7 --- /dev/null +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/JWTTokenStore.java @@ -0,0 +1,71 @@ +/* + * 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.token; + +import java.util.UUID; + +import org.apache.servicecomb.authentication.jwt.JWTClaims; +import org.apache.servicecomb.authentication.jwt.JsonParser; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.jwt.Jwt; +import org.springframework.security.jwt.JwtHelper; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; +import org.springframework.security.jwt.crypto.sign.Signer; + +public class JWTTokenStore implements TokenStore { + private Signer signer; + + private SignatureVerifier signerVerifier; + + public JWTTokenStore(Signer signer, SignatureVerifier signerVerifier) { + this.signer = signer; + this.signerVerifier = signerVerifier; + } + + @SuppressWarnings("unchecked") + @Override + public <T extends Token> T createToken(UserDetails userDetails) { + JWTClaims claims = new JWTClaims(); + claims.setSub(userDetails.getUsername()); + if (userDetails.getAuthorities() != null) { + userDetails.getAuthorities().forEach(authority -> claims.addAuthority(authority.getAuthority())); + } + + // TODO : set other parameters. + claims.setJti(UUID.randomUUID().toString()); + claims.setIat(System.currentTimeMillis()); + claims.setExp(5 * 60); + + return (T) new JWTToken(claims, signer); + } + + @SuppressWarnings("unchecked") + @Override + public <T extends Token> T readTokenByValue(String value) { + Jwt jwt = JwtHelper.decode(value); + JWTClaims claims; + try { + jwt.verifySignature(signerVerifier); + claims = JsonParser.parse(jwt.getClaims(), JWTClaims.class); + } catch (Exception e) { + return null; + } + return (T) new JWTToken(claims, signer); + } + +} diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/SessionIDToken.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/SessionIDToken.java new file mode 100644 index 0000000..06a6f96 --- /dev/null +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/SessionIDToken.java @@ -0,0 +1,76 @@ +/* + * 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.token; + +import java.util.Map; +import java.util.UUID; + +public class SessionIDToken implements Token { + private String value; + + private long issueAt; + + // in seconds + private long expiration; + + private String username; + + public SessionIDToken(String username) { + this.value = UUID.randomUUID().toString(); + this.issueAt = System.currentTimeMillis(); + // TODO add a configuration + this.expiration = 600; + this.username = username; + } + + @Override + public boolean isExpired() { + return System.currentTimeMillis() - this.issueAt > this.expiration * 1000; + } + + @Override + public long getIssueAt() { + return this.issueAt; + } + + @Override + public long getExpiration() { + return this.expiration; + } + + @Override + public long getNotBefore() { + // TODO add a configuration + return 0; + } + + @Override + public String getValue() { + return this.value; + } + + @Override + public Map<String, Object> getAdditionalInformation() { + // TODO add a configuration + return null; + } + + public String username() { + return this.username; + } +} diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java similarity index 75% copy from api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java copy to api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java index b0cea22..21b06d5 100644 --- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/Token.java @@ -15,10 +15,22 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.token; import java.util.Map; -public interface TokenService { - Token getAccessToken(Map<String, String> parameters); +public interface Token { + String username(); + + boolean isExpired(); + + long getIssueAt(); + + long getExpiration(); + + long getNotBefore(); + + String getValue(); + + Map<String, Object> getAdditionalInformation(); } diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/TokenStore.java similarity index 75% copy from api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java copy to api/common/service/src/main/java/org/apache/servicecomb/authentication/token/TokenStore.java index b0cea22..1f26610 100644 --- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/token/TokenStore.java @@ -15,10 +15,12 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.token; -import java.util.Map; +import org.springframework.security.core.userdetails.UserDetails; -public interface TokenService { - Token getAccessToken(Map<String, String> parameters); +public interface TokenStore { + <T extends Token> T createToken(UserDetails userDetails); + + <T extends Token> T readTokenByValue(String value); } diff --git a/api/common/service/src/main/java/org/apache/servicecomb/authentication/util/Constants.java b/api/common/service/src/main/java/org/apache/servicecomb/authentication/util/Constants.java index 96333aa..4dcc8e7 100644 --- a/api/common/service/src/main/java/org/apache/servicecomb/authentication/util/Constants.java +++ b/api/common/service/src/main/java/org/apache/servicecomb/authentication/util/Constants.java @@ -21,6 +21,9 @@ public final class Constants { public static final String HTTP_HEADER_AUTHORIZATION = "Authorization"; public static final String CONTEXT_HEADER_AUTHORIZATION = "Authorization"; - + public static final String CONTEXT_HEADER_CLAIMS = "Claims"; + + public static final String TOKEN_TYPE_BEARER = "Bearer"; + } diff --git a/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/AuthHandler.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthHandler.java similarity index 78% rename from api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/AuthHandler.java rename to api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthHandler.java index abf4587..d209be4 100644 --- a/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/AuthHandler.java +++ b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthHandler.java @@ -17,32 +17,35 @@ package org.apache.servicecomb.authentication.edge; +import org.apache.servicecomb.authentication.server.TokenResponse; import org.apache.servicecomb.authentication.util.Constants; import org.apache.servicecomb.core.Handler; import org.apache.servicecomb.core.Invocation; import org.apache.servicecomb.foundation.common.utils.BeanUtils; import org.apache.servicecomb.swagger.invocation.AsyncResponse; import org.apache.servicecomb.swagger.invocation.exception.InvocationException; -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; -import org.springframework.security.jwt.crypto.sign.InvalidSignatureException; public class AuthHandler implements Handler { @Override public void handle(Invocation invocation, AsyncResponse asyncResponse) throws Exception { + EdgeTokenStore edgeTokenStore = BeanUtils.getBean("authEdgeTokenStore"); + String token = invocation.getContext(Constants.CONTEXT_HEADER_AUTHORIZATION); if (token == null) { asyncResponse.consumerFail(new InvocationException(403, "forbidden", "not authenticated")); return; } - Jwt jwt = JwtHelper.decode(token); - try { - jwt.verifySignature(BeanUtils.getBean("authSignatureVerifier")); - } catch (InvalidSignatureException e) { + + TokenResponse tokenResonse = edgeTokenStore.readTokenResponse(token); + if (tokenResonse == null) { + // TODO : check token validity by expiration time asyncResponse.consumerFail(new InvocationException(403, "forbidden", "not authenticated")); return; } + + // send id_token to services to apply state less validation + invocation.addContext(Constants.CONTEXT_HEADER_AUTHORIZATION, tokenResonse.getId_token()); invocation.next(asyncResponse); } } diff --git a/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/AuthenticationFilter.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthenticationFilter.java similarity index 100% rename from api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/AuthenticationFilter.java rename to api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthenticationFilter.java diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthenticationServerTokenEndpoint.java similarity index 59% copy from api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java copy to api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthenticationServerTokenEndpoint.java index 9ba0861..50c29d3 100644 --- a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java +++ b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/AuthenticationServerTokenEndpoint.java @@ -15,26 +15,17 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.edge; import java.util.Map; +import java.util.concurrent.CompletableFuture; -/** - * Token granter is used to grant access tokens. - * @author Administrator - * - */ -public interface TokenGranter { - boolean enabled(); - - String grantType(); - - default Token grant(String grantType, Map<String, String> parameters) { - if (grantType().equals(grantType)) { - return grant(parameters); - } - return null; - } +import org.apache.servicecomb.authentication.server.TokenResponse; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; - Token grant(Map<String, String> parameters); +public interface AuthenticationServerTokenEndpoint { + @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public CompletableFuture<TokenResponse> getToken(@RequestBody Map<String, String> parameters); } diff --git a/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/CustomVertxRestDispatcher.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/CustomVertxRestDispatcher.java similarity index 99% rename from api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/CustomVertxRestDispatcher.java rename to api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/CustomVertxRestDispatcher.java index 521b71c..a08873d 100644 --- a/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/CustomVertxRestDispatcher.java +++ b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/CustomVertxRestDispatcher.java @@ -63,6 +63,7 @@ public class CustomVertxRestDispatcher extends AbstractVertxHttpDispatcher { @Override public void init(Router router) { + // TODO: regex configuration String regex = "(/v1/log|/inspector|/v1/auth)/(.*)"; router.routeWithRegex(regex).handler(CookieHandler.create()); router.routeWithRegex(regex).handler(createBodyHandler()); diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/EdgeTokenStore.java similarity index 73% copy from api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java copy to api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/EdgeTokenStore.java index b0cea22..92bbbaf 100644 --- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java +++ b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/EdgeTokenStore.java @@ -15,10 +15,12 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.edge; -import java.util.Map; +import org.apache.servicecomb.authentication.server.TokenResponse; -public interface TokenService { - Token getAccessToken(Map<String, String> parameters); +public interface EdgeTokenStore { + TokenResponse readTokenResponse(String accessTokenValue); + + void saveTokenResponse(String accessTokenValue, TokenResponse tokenResponse); } diff --git a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/InMemoryEdgeTokenStore.java similarity index 60% copy from api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java copy to api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/InMemoryEdgeTokenStore.java index 9ba0861..03a7baa 100644 --- a/api/authentication-server/endpoint/src/main/java/org/apache/servicecomb/authentication/server/TokenGranter.java +++ b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/InMemoryEdgeTokenStore.java @@ -15,26 +15,24 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.edge; +import java.util.HashMap; import java.util.Map; -/** - * Token granter is used to grant access tokens. - * @author Administrator - * - */ -public interface TokenGranter { - boolean enabled(); +import org.apache.servicecomb.authentication.server.TokenResponse; - String grantType(); +public class InMemoryEdgeTokenStore implements EdgeTokenStore { + private Map<String, TokenResponse> tokens = new HashMap<>(); + + @Override + public TokenResponse readTokenResponse(String accessTokenValue) { + return tokens.get(accessTokenValue); + } - default Token grant(String grantType, Map<String, String> parameters) { - if (grantType().equals(grantType)) { - return grant(parameters); - } - return null; + @Override + public void saveTokenResponse(String accessTokenValue, TokenResponse tokenResponse) { + tokens.put(accessTokenValue, tokenResponse); } - Token grant(Map<String, String> parameters); } diff --git a/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/InternalAccessHandler.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/InternalAccessHandler.java similarity index 100% rename from api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/InternalAccessHandler.java rename to api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/InternalAccessHandler.java diff --git a/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/TokenEndpoint.java b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/TokenEndpoint.java new file mode 100644 index 0000000..87dcbb0 --- /dev/null +++ b/api/edge-service/endpoint/src/main/java/org/apache/servicecomb/authentication/edge/TokenEndpoint.java @@ -0,0 +1,65 @@ +/* + * 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.edge; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.apache.servicecomb.authentication.server.TokenResponse; +import org.apache.servicecomb.provider.pojo.RpcReference; +import org.apache.servicecomb.provider.rest.common.RestSchema; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; + +@RestSchema(schemaId = "TokenEndpoint") +@RequestMapping(path = "/v1/token") +public class TokenEndpoint implements TokenService { + @RpcReference(microserviceName = "authentication-server", schemaId = "TokenEndpoint") + private AuthenticationServerTokenEndpoint authenticationSererTokenEndpoint; + + @Autowired + @Qualifier("authEdgeTokenStore") + private EdgeTokenStore edgeTokenStore; + + + @Override + @PostMapping(path = "/", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE) + public CompletableFuture<TokenResponse> getToken(@RequestBody Map<String, String> parameters) { + CompletableFuture<TokenResponse> result = new CompletableFuture<>(); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + CompletableFuture<TokenResponse> response = + authenticationSererTokenEndpoint.getToken(parameters); + response.whenComplete((tokenResonse, ex) -> { + if (!response.isCompletedExceptionally()) { + result.complete(tokenResonse); + edgeTokenStore.saveTokenResponse(tokenResonse.getAccess_token(), tokenResonse); + } else { + result.completeExceptionally(ex); + } + }); + return result; + } +} diff --git a/api/edge-service/service/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter b/api/edge-service/endpoint/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter similarity index 100% rename from api/edge-service/service/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter rename to api/edge-service/endpoint/src/main/resources/META-INF/services/org.apache.servicecomb.common.rest.filter.HttpServerFilter diff --git a/api/edge-service/service/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher b/api/edge-service/endpoint/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher similarity index 91% rename from api/edge-service/service/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher rename to api/edge-service/endpoint/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher index 32c1583..53481f1 100644 --- a/api/edge-service/service/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher +++ b/api/edge-service/endpoint/src/main/resources/META-INF/services/org.apache.servicecomb.transport.rest.vertx.VertxHttpDispatcher @@ -15,4 +15,4 @@ # limitations under the License. # -# org.apache.servicecomb.authentication.edge.CustomVertxRestDispatcher \ No newline at end of file +org.apache.servicecomb.authentication.edge.CustomVertxRestDispatcher \ No newline at end of file diff --git a/api/edge-service/service/src/main/resources/config/cse.handler.xml b/api/edge-service/endpoint/src/main/resources/config/cse.handler.xml similarity index 100% rename from api/edge-service/service/src/main/resources/config/cse.handler.xml rename to api/edge-service/endpoint/src/main/resources/config/cse.handler.xml diff --git a/api/edge-service/service/pom.xml b/api/edge-service/service/pom.xml index 8d86c00..487f146 100644 --- a/api/edge-service/service/pom.xml +++ b/api/edge-service/service/pom.xml @@ -36,7 +36,8 @@ <dependencies> <dependency> <groupId>org.apache.servicecomb.authentication</groupId> - <artifactId>authentication-common-api-endpoint</artifactId> + <artifactId>authentication-server-api-service</artifactId> + <version>${project.parent.version}</version> </dependency> </dependencies> </project> diff --git a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java b/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/TokenService.java similarity index 77% copy from api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java copy to api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/TokenService.java index b0cea22..5e12a45 100644 --- a/api/authentication-server/service/src/main/java/org/apache/servicecomb/authentication/server/TokenService.java +++ b/api/edge-service/service/src/main/java/org/apache/servicecomb/authentication/edge/TokenService.java @@ -15,10 +15,14 @@ * limitations under the License. */ -package org.apache.servicecomb.authentication.server; +package org.apache.servicecomb.authentication.edge; import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.apache.servicecomb.authentication.server.TokenResponse; + public interface TokenService { - Token getAccessToken(Map<String, String> parameters); + CompletableFuture<TokenResponse> getToken(Map<String, String> parameters); } diff --git a/api/resource-server/service/src/main/java/org/apache/servicecomb/authentication/resource/ResourceAuthHandler.java b/api/resource-server/service/src/main/java/org/apache/servicecomb/authentication/resource/ResourceAuthHandler.java index 68c6960..5c81139 100644 --- a/api/resource-server/service/src/main/java/org/apache/servicecomb/authentication/resource/ResourceAuthHandler.java +++ b/api/resource-server/service/src/main/java/org/apache/servicecomb/authentication/resource/ResourceAuthHandler.java @@ -21,8 +21,8 @@ import java.util.HashSet; import java.util.Set; import org.apache.commons.lang3.StringUtils; -import org.apache.servicecomb.authentication.jwt.JWTClaims; -import org.apache.servicecomb.authentication.jwt.JsonParser; +import org.apache.servicecomb.authentication.token.JWTToken; +import org.apache.servicecomb.authentication.token.JWTTokenStore; import org.apache.servicecomb.authentication.util.Constants; import org.apache.servicecomb.core.Handler; import org.apache.servicecomb.core.Invocation; @@ -35,8 +35,6 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.context.SecurityContextImpl; -import org.springframework.security.jwt.Jwt; -import org.springframework.security.jwt.JwtHelper; public class ResourceAuthHandler implements Handler { @@ -56,23 +54,19 @@ public class ResourceAuthHandler implements Handler { return; } // verify tokens - Jwt jwt = JwtHelper.decode(token); - JWTClaims claims; - try { - jwt.verifySignature(BeanUtils.getBean("authSignatureVerifier")); - claims = JsonParser.parse(jwt.getClaims(), JWTClaims.class); - // TODO: verify claims. - } catch (Exception e) { + JWTTokenStore store = BeanUtils.getBean("authIDTokenStore"); + JWTToken t = store.readTokenByValue(token); + if(t == null) { asyncResponse.consumerFail(new InvocationException(403, "forbidden", "not authenticated")); return; } - + // check roles if (!StringUtils.isEmpty(config.roles)) { String[] roles = config.roles.split(","); if (roles.length > 0) { boolean valid = false; - Set<String> authorities = claims.getAuthorities(); + Set<String> authorities = t.getClaims().getAuthorities(); for (String role : roles) { if (authorities.contains(role)) { valid = true; @@ -87,8 +81,8 @@ public class ResourceAuthHandler implements Handler { } // pre method authentiation - Set<GrantedAuthority> grantedAuthorities = new HashSet<>(claims.getAuthorities().size()); - claims.getAuthorities().forEach(v -> grantedAuthorities.add(new SimpleGrantedAuthority(v))); + Set<GrantedAuthority> grantedAuthorities = new HashSet<>(t.getClaims().getAuthorities().size()); + t.getClaims().getAuthorities().forEach(v -> grantedAuthorities.add(new SimpleGrantedAuthority(v))); SecurityContext sc = new SecurityContextImpl(); Authentication authentication = new SimpleAuthentication(true, grantedAuthorities); sc.setAuthentication(authentication); diff --git a/docs/zh_CN/developersGuide.md b/docs/zh_CN/developersGuide.md index 013f98f..3d67833 100644 --- a/docs/zh_CN/developersGuide.md +++ b/docs/zh_CN/developersGuide.md @@ -11,15 +11,37 @@ ``` ** HTTP Request ** -POST http://localhost:9090/api/authentication-server/v1/oauth/token HTTP/1.1 +POST http://localhost:9090/v1/token HTTP/1.1 Content-Type: application/x-www-form-urlencoded grant_type=password&username=admin&password=changeMyPassword + +** HTTP Response ** + +{ + "token_type": "bearer", + "access_token": "SlAV32hkKG", + "refresh_token": "8xLOxBtZp8", + "id_token": "eyJhbGciOiJSUzI1NiIsImtpZCI6IjFlOWdkazcifQ.ewogImlzc + yI6ICJodHRwOi8vc2VydmVyLmV4YW1wbGUuY29tIiwKICJzdWIiOiAiMjQ4Mjg5 + NzYxMDAxIiwKICJhdWQiOiAiczZCaGRSa3F0MyIsCiAibm9uY2UiOiAibi0wUzZ + fV3pBMk1qIiwKICJleHAiOiAxMzExMjgxOTcwLAogImlhdCI6IDEzMTEyODA5Nz + AKfQ.ggW8hZ1EuVLuxNuuIJKX_V8a_OMXzR0EHR9R6jgdqrOOF4daGU96Sr_P6q + Jp6IcmD3HP99Obi1PRs-cwh3LO-p146waJ8IhehcwL7F09JdijmBqkvPeB2T9CJ + NqeGpe-gccMg4vfKjkM8FcGvnzZUN4_KSP0aAp1tOJ1zZwgjxqGByKHiOtX7Tpd + QyHE5lcMiKPXfEIQILVq0pc_E2DzL7emopWoaoZTF_m0_N0YzFC6g6EJbOEoRoS + K5hoDalrcvRYLSrQAZZKflyuVCyixEoV9GfNQC3_osjzw2PAithfubEEBLuVVk4 + XUVrWOLrLl0nx7RkKU8NXNHq-rvKMzqg", + "expires_in": 600, + "scope": null, + "jti": null, + "additionalInformation": null +} ``` * Authentication Server 发送 Token 给 Client 。 - * Client 携带 Token 请求 Resource Server 。 + * Client 携带 Access Token 请求 Edge Service 。 ``` ** HTTP Request ** @@ -29,6 +51,7 @@ Content-Type: application/x-www-form-urlencoded Authorization: Bearer czZCaGRSa3F0MzpnWDFmQmF0M2JW ``` + * Edge Service 将 Access Token 转换为对应的 ID Token , 然后将请求转发给Resource Server。 * Resource Server 返回对应的资源给 Client 。 ## 开发 Authentication Server @@ -48,31 +71,38 @@ Authentication Server 主要提供认证和授权等接口。 * 配置 -Authentication Server 需要配置 PasswordEncoder、Signer、SignerVerifier、UserDetailsService 等。这些对象和 Spring Security的概念一样。 +Authentication Server 需要配置 PasswordEncoder、Signer、SignerVerifier、TokenStore、UserDetailsService 等。 ``` @Configuration public class AuthenticationConfiguration { - @Autowired - @Qualifier("authPasswordEncoder") - private PasswordEncoder passwordEncoder; - @Bean(name = "authPasswordEncoder") public PasswordEncoder authPasswordEncoder() { return new Pbkdf2PasswordEncoder(); } - @Bean(name = "authSigner") - public Signer authSigner() { - return authSignerVerifier(); - } - - @Bean(name = "authSignerVerifier") + @Bean(name = {"authSigner", "authSignatureVerifier"}) public SignerVerifier authSignerVerifier() { + // If using RSA, need to configure authSigner and authSignatureVerifier separately. + // If using MacSigner, need to protect the shared key by properly encryption. return new MacSigner("Please change this key."); } + @Bean(name = {"authAccessTokenStore", "authRefreshTokenStore"}) + public TokenStore sessionIDTokenStore() { + // Use in memory store for testing. Need to implement JDBC or Redis SessionIDTokenStore in product. + return new InMemorySessionIDTokenStore(); + } + + @Bean(name = "authIDTokenStore") + public TokenStore authIDTokenStore(@Autowired @Qualifier("authSigner") Signer signer, + @Autowired @Qualifier("authSignatureVerifier") SignatureVerifier signerVerifier) { + return new JWTTokenStore(signer, signerVerifier); + } + @Bean(name = "authUserDetailsService") - public UserDetailsService authUserDetailsService() { + public UserDetailsService authUserDetailsService( + @Autowired @Qualifier("authPasswordEncoder") PasswordEncoder passwordEncoder) { + // Use in memory UserDetails, need to implement JDBC or others in product InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); UserDetails uAdmin = new User("admin", passwordEncoder.encode("changeMyPassword"), Arrays.asList(new SimpleGrantedAuthority("ADMIN"))); @@ -93,6 +123,9 @@ public class AuthenticationConfiguration { 生成 Token 和对 Token 进行校验。Singer 和 SignatureVerifier 是配套使用的, 在 Authentication Server , 生成 Token 的时候,需要使用 Singer 。 验证 Token 的有效性 (比如查询 userDetails 等场景), 需要使用 SignatureVerifier 。 通常有两种方式进行签名和校验, 一种是基于对称秘钥的机制,比如MacSigner,即是 Singer, 也是 SignatureVerifier (SignerVerifier); 一种是基于非对称秘钥的机制, 比如 RsaSigner 和 RsaVerifier , 生成 Token 和校验 Token 的秘钥是不同的。 +* TokenStore +在Authentication Server,TokenStore主要用来生成Access Token, Refresh Token和ID Token, 默认情况下, Access Token和Refresh Token都使用AbstractSessionIDTokenStore(本例子使用了InMemorySessionIDTokenStore,业务代码通常需要换为JDBC、Redis等实现), ID Token使用JWTTokenStore。 JWTTokenStore是一个无状态的会话机制,Authentication Server的任何一个实例都可以独立生成。 + * PasswordEncoder 从 UserDetailsService 校验用户密码的时候需要使用。 开发者需要在加密性能和保密程度方面选择合适的算法。 常用的有 Pbkdf2PasswordEncoder , 它可以设置迭代次数,能够更好的保护密码不被暴力破解。 @@ -114,17 +147,30 @@ Resource Server 对 Client 的访问进行认证, 并进行权限控制。 * 配置 -Resource Server 需要配置 SignatureVerifier 等, 对用户会话进行认证。 +Resource Server 需要配置 Signer、SignatureVerifier、TokenStore 等, 对用户会话进行认证。 + ``` -@Configuration -public class AuthenticationConfiguration { - @Bean(name = "authSignatureVerifier") - public SignerVerifier authSignatureVerifier() { + @Bean(name = {"authSigner", "authSignatureVerifier"}) + public SignerVerifier authSignerVerifier() { + // If using RSA, need to configure authSigner and authSignatureVerifier separately. + // If using MacSigner, need to protect the shared key by properly encryption. return new MacSigner("Please change this key."); } -} + + @Bean(name = "authIDTokenStore") + public TokenStore authIDTokenStore(@Autowired @Qualifier("authSigner") Signer signer, + @Autowired @Qualifier("authSignatureVerifier") SignatureVerifier signerVerifier) { + return new JWTTokenStore(signer, signerVerifier); + } ``` +* Signer、SignatureVerifier +对Token进行校验需要,实际上Resource Server只需要使用SignatureVerifier。 + +* TokenStore +默认情况下, Edge Service将ID Token传递给Resource Server,所以只需要配置authIDTokenStore。 + + * 权限配置 fence 提供了两种方式进行权限配置。 一种是基于配置文件的,一种是基于 Annotation 。 @@ -205,15 +251,14 @@ Edge Service 是微服务接入层。 在[单体应用微服务改造](https://b * 配置 -Edge Service 需要配置 SignatureVerifier 等, 对用户会话进行认证。 -``` +Edge Service 需要配置 EdgeTokenStore 等, 对用户会话进行认证。Edge Service 从HTTP头里面读取Access Token, 然后通过 EdgeTokenStore比对是否Access Token有效,如果有效,将对应的 ID Token传递到 Resource Server。 这里使用了 InMemoryEdgeTokenStore, 产品代码会多实例部署 Edge Service, 需要将其替换为 JDBC 或者 Redis 等实现。 +``` @Configuration public class AuthenticationConfiguration { - @Bean(name = "authSignatureVerifier") - public SignerVerifier authSignatureVerifier() { - return new MacSigner("Please change this key."); + @Bean(name = "authEdgeTokenStore") + public EdgeTokenStore authEdgeTokenStore() { + return new InMemoryEdgeTokenStore(); } } - ``` 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 b70273f..20e9f68 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 @@ -19,6 +19,9 @@ package org.apache.servicecomb.authentication; import java.util.Arrays; +import org.apache.servicecomb.authentication.token.InMemorySessionIDTokenStore; +import org.apache.servicecomb.authentication.token.JWTTokenStore; +import org.apache.servicecomb.authentication.token.TokenStore; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; @@ -30,28 +33,41 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.Pbkdf2PasswordEncoder; import org.springframework.security.jwt.crypto.sign.MacSigner; +import org.springframework.security.jwt.crypto.sign.SignatureVerifier; +import org.springframework.security.jwt.crypto.sign.Signer; import org.springframework.security.jwt.crypto.sign.SignerVerifier; import org.springframework.security.provisioning.InMemoryUserDetailsManager; @Configuration public class AuthenticationConfiguration { - @Autowired - @Qualifier("authPasswordEncoder") - private PasswordEncoder passwordEncoder; - @Bean(name = "authPasswordEncoder") public PasswordEncoder authPasswordEncoder() { return new Pbkdf2PasswordEncoder(); } - // If using RSA, need to configure authSigner and authSignatureVerifier separately. - @Bean(name = {"authSigner", "authSignatureVerifier" }) + @Bean(name = {"authSigner", "authSignatureVerifier"}) public SignerVerifier authSignerVerifier() { + // If using RSA, need to configure authSigner and authSignatureVerifier separately. + // If using MacSigner, need to protect the shared key by properly encryption. return new MacSigner("Please change this key."); } + @Bean(name = {"authAccessTokenStore", "authRefreshTokenStore"}) + public TokenStore sessionIDTokenStore() { + // Use in memory store for testing. Need to implement JDBC or Redis SessionIDTokenStore in product. + return new InMemorySessionIDTokenStore(); + } + + @Bean(name = "authIDTokenStore") + public TokenStore authIDTokenStore(@Autowired @Qualifier("authSigner") Signer signer, + @Autowired @Qualifier("authSignatureVerifier") SignatureVerifier signerVerifier) { + return new JWTTokenStore(signer, signerVerifier); + } + @Bean(name = "authUserDetailsService") - public UserDetailsService authUserDetailsService() { + public UserDetailsService authUserDetailsService( + @Autowired @Qualifier("authPasswordEncoder") PasswordEncoder passwordEncoder) { + // Use in memory UserDetails, need to implement JDBC or others in product InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); UserDetails uAdmin = new User("admin", passwordEncoder.encode("changeMyPassword"), Arrays.asList(new SimpleGrantedAuthority("ADMIN"))); diff --git a/samples/Client/src/main/java/org/apache/servicecomb/authentication/AuthenticationTestCase.java b/samples/Client/src/main/java/org/apache/servicecomb/authentication/AuthenticationTestCase.java index 13bfaba..e05545f 100644 --- a/samples/Client/src/main/java/org/apache/servicecomb/authentication/AuthenticationTestCase.java +++ b/samples/Client/src/main/java/org/apache/servicecomb/authentication/AuthenticationTestCase.java @@ -17,13 +17,10 @@ package org.apache.servicecomb.authentication; -import org.apache.servicecomb.authentication.jwt.JWTClaims; -import org.apache.servicecomb.authentication.jwt.JsonParser; -import org.apache.servicecomb.authentication.server.Token; +import org.apache.servicecomb.authentication.server.TokenResponse; import org.springframework.http.HttpEntity; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; -import org.springframework.security.jwt.JwtHelper; import org.springframework.stereotype.Component; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -33,12 +30,16 @@ import org.springframework.web.client.HttpClientErrorException; public class AuthenticationTestCase implements TestCase { @Override public void run() { - testHanlderAuth(); - testMethodAuth(); - } + String accessToken = accessToken(); + testHanlderAuth(accessToken); + testMethodAuth(accessToken); + accessToken = accessTokenByRefreshToken(); + testHanlderAuth(accessToken); + testMethodAuth(accessToken); + } - private void testHanlderAuth() { + private String accessToken() { // get token MultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); map.add("grant_type", "password"); @@ -47,68 +48,55 @@ public class AuthenticationTestCase implements TestCase { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.MULTIPART_FORM_DATA); - Token token = - BootEventListener.authenticationServerTokenEndpoint.postForObject("/", + TokenResponse token = + BootEventListener.edgeServiceTokenEndpoint.postForObject("/", new HttpEntity<>(map, headers), - Token.class); + TokenResponse.class); TestMgr.check("bearer", token.getToken_type()); TestMgr.check(true, token.getAccess_token().length() > 10); + return token.getAccess_token(); + } - // get resources - headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + token.getAccess_token()); - headers.setContentType(MediaType.APPLICATION_JSON); - String name; - name = BootEventListener.resouceServerHandlerAuthEndpoint.postForObject("/everyoneSayHello?name=Hi", - new HttpEntity<>(headers), - String.class); - TestMgr.check("Hi", name); - - name = BootEventListener.resouceServerHandlerAuthEndpoint.postForObject("/adminSayHello?name=Hi", - new HttpEntity<>(headers), - String.class); - TestMgr.check("Hi", name); + private String accessTokenByRefreshToken() { + // get token + MultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); + map.add("grant_type", "password"); + map.add("username", "admin"); + map.add("password", "changeMyPassword"); + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.MULTIPART_FORM_DATA); - name = BootEventListener.resouceServerHandlerAuthEndpoint.postForObject("/guestOrAdminSayHello?name=Hi", - new HttpEntity<>(headers), - String.class); - TestMgr.check("Hi", name); + TokenResponse token = + BootEventListener.edgeServiceTokenEndpoint.postForObject("/", + new HttpEntity<>(map, headers), + TokenResponse.class); + TestMgr.check("bearer", token.getToken_type()); + TestMgr.check(true, token.getAccess_token().length() > 10); - name = null; - try { - name = BootEventListener.resouceServerHandlerAuthEndpoint.postForObject("/guestSayHello?name=Hi", - new HttpEntity<>(headers), - String.class); - } catch (HttpClientErrorException e) { - TestMgr.check(403, e.getStatusCode().value()); - } - TestMgr.check(null, name); - // refresh token - // get token map = new LinkedMultiValueMap<>(); map.add("grant_type", "refresh_token"); map.add("refresh_token", token.getRefresh_token()); - map.add("access_token", token.getAccess_token()); - headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - Token tokenNew = - BootEventListener.authenticationServerTokenEndpoint.postForObject("/", + TokenResponse tokenNew = + BootEventListener.edgeServiceTokenEndpoint.postForObject("/", new HttpEntity<>(map, headers), - Token.class); + TokenResponse.class); TestMgr.check(token.getToken_type(), tokenNew.getToken_type()); - - JWTClaims claims = JsonParser.parse(JwtHelper.decode(token.getAccess_token()).getClaims(), JWTClaims.class); - JWTClaims newClaims = JsonParser.parse(JwtHelper.decode(tokenNew.getAccess_token()).getClaims(), JWTClaims.class); - TestMgr.check(claims.getJti(), newClaims.getJti()); - TestMgr.check(claims.getIat() < newClaims.getIat(), true); + TestMgr.check(token.getRefresh_token(), tokenNew.getRefresh_token()); + TestMgr.check(token.getAccess_token().equals(tokenNew.getAccess_token()), false); + TestMgr.check(token.getId_token().equals(tokenNew.getId_token()), false); + + return tokenNew.getAccess_token(); + } + private void testHanlderAuth(String accessToken) { // get resources + HttpHeaders headers = new HttpHeaders(); headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + tokenNew.getAccess_token()); + headers.add("Authorization", "Bearer " + accessToken); headers.setContentType(MediaType.APPLICATION_JSON); - + String name; name = BootEventListener.resouceServerHandlerAuthEndpoint.postForObject("/everyoneSayHello?name=Hi", new HttpEntity<>(headers), String.class); @@ -135,27 +123,10 @@ public class AuthenticationTestCase implements TestCase { TestMgr.check(null, name); } - - private void testMethodAuth() { - // get token - MultiValueMap<String, Object> map = new LinkedMultiValueMap<>(); - map.add("grant_type", "password"); - map.add("username", "admin"); - map.add("password", "changeMyPassword"); - HttpHeaders headers = new HttpHeaders(); - headers.setContentType(MediaType.MULTIPART_FORM_DATA); - - Token token = - BootEventListener.authenticationServerTokenEndpoint.postForObject("/", - new HttpEntity<>(map, headers), - Token.class); - TestMgr.check("bearer", token.getToken_type()); - TestMgr.check(true, token.getAccess_token().length() > 10); - TestMgr.check(true, token.getRefresh_token().length() > 10); - + private void testMethodAuth(String accessToken) { // get resources - headers = new HttpHeaders(); - headers.add("Authorization", "Bearer " + token.getAccess_token()); + HttpHeaders headers = new HttpHeaders(); + headers.add("Authorization", "Bearer " + accessToken); headers.setContentType(MediaType.APPLICATION_JSON); String name; name = BootEventListener.resouceServerMethodAuthEndpoint.postForObject("/everyoneSayHello?name=Hi", diff --git a/samples/Client/src/main/java/org/apache/servicecomb/authentication/BootEventListener.java b/samples/Client/src/main/java/org/apache/servicecomb/authentication/BootEventListener.java index 08d1bb3..74db45a 100644 --- a/samples/Client/src/main/java/org/apache/servicecomb/authentication/BootEventListener.java +++ b/samples/Client/src/main/java/org/apache/servicecomb/authentication/BootEventListener.java @@ -23,7 +23,8 @@ import org.springframework.stereotype.Component; @Component public class BootEventListener implements BootListener { public static GateRestTemplate authenticationServerTokenEndpoint; - public static GateRestTemplate gateEndpoint; + public static GateRestTemplate edgeService; + public static GateRestTemplate edgeServiceTokenEndpoint; public static GateRestTemplate resouceServerHandlerAuthEndpoint; public static GateRestTemplate resouceServerMethodAuthEndpoint; @@ -32,8 +33,10 @@ public class BootEventListener implements BootListener { if (EventType.AFTER_REGISTRY.equals(event.getEventType())) { authenticationServerTokenEndpoint = GateRestTemplate.createEdgeRestTemplate("edge-service", "authentication-server", "TokenEndpoint").init(); - gateEndpoint = + edgeService = GateRestTemplate.createEdgeRestTemplate("edge-service", null, null).init(); + edgeServiceTokenEndpoint = + GateRestTemplate.createEdgeRestTemplate("edge-service", "edge-service", "TokenEndpoint").init(); resouceServerHandlerAuthEndpoint = GateRestTemplate.createEdgeRestTemplate("edge-service", "resource-server", "HandlerAuthEndpoint").init(); resouceServerMethodAuthEndpoint = diff --git a/samples/Client/src/main/java/org/apache/servicecomb/authentication/GateRestTemplate.java b/samples/Client/src/main/java/org/apache/servicecomb/authentication/GateRestTemplate.java index 7a6709f..df981e5 100644 --- a/samples/Client/src/main/java/org/apache/servicecomb/authentication/GateRestTemplate.java +++ b/samples/Client/src/main/java/org/apache/servicecomb/authentication/GateRestTemplate.java @@ -100,6 +100,15 @@ public class GateRestTemplate extends RestTemplate { DefinitionConst.VERSION_RULE_ALL); MicroserviceVersionMeta microserviceVersionMeta = microserviceVersionRule.getLatestMicroserviceVersion(); SchemaMeta schemaMeta = microserviceVersionMeta.getMicroserviceMeta().ensureFindSchemaMeta(schemaId); + + if(producerName.equals(gateName)) { + return String + .format("%s://%s:%d/%s", + urlSchema, + edgeAddress.getHostOrIp(), + edgeAddress.getPort(), + schemaMeta.getSwagger().getBasePath()); + } return String .format("%s://%s:%d/api/%s%s", urlSchema, diff --git a/samples/EdgeService/src/main/java/org/apache/servicecomb/authentication/gateway/AuthenticationConfiguration.java b/samples/EdgeService/src/main/java/org/apache/servicecomb/authentication/gateway/AuthenticationConfiguration.java index 510d821..e9187b7 100644 --- a/samples/EdgeService/src/main/java/org/apache/servicecomb/authentication/gateway/AuthenticationConfiguration.java +++ b/samples/EdgeService/src/main/java/org/apache/servicecomb/authentication/gateway/AuthenticationConfiguration.java @@ -17,15 +17,15 @@ package org.apache.servicecomb.authentication.gateway; +import org.apache.servicecomb.authentication.edge.EdgeTokenStore; +import org.apache.servicecomb.authentication.edge.InMemoryEdgeTokenStore; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.security.jwt.crypto.sign.MacSigner; -import org.springframework.security.jwt.crypto.sign.SignerVerifier; @Configuration public class AuthenticationConfiguration { - @Bean(name = "authSignatureVerifier") - public SignerVerifier authSignatureVerifier() { - return new MacSigner("Please change this key."); + @Bean(name = "authEdgeTokenStore") + public EdgeTokenStore authEdgeTokenStore() { + return new InMemoryEdgeTokenStore(); } } diff --git a/samples/ResourceServer/src/main/java/org/apache/servicecomb/authentication/resource/AuthenticationConfiguration.java b/samples/ResourceServer/src/main/java/org/apache/servicecomb/authentication/resource/AuthenticationConfiguration.java index 2074321..e69217f 100644 --- a/samples/ResourceServer/src/main/java/org/apache/servicecomb/authentication/resource/AuthenticationConfiguration.java +++ b/samples/ResourceServer/src/main/java/org/apache/servicecomb/authentication/resource/AuthenticationConfiguration.java @@ -17,15 +17,30 @@ package org.apache.servicecomb.authentication.resource; +import org.apache.servicecomb.authentication.token.JWTTokenStore; +import org.apache.servicecomb.authentication.token.TokenStore; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.jwt.crypto.sign.MacSigner; import org.springframework.security.jwt.crypto.sign.SignatureVerifier; +import org.springframework.security.jwt.crypto.sign.Signer; +import org.springframework.security.jwt.crypto.sign.SignerVerifier; @Configuration public class AuthenticationConfiguration { - @Bean(name = "authSignatureVerifier") - public SignatureVerifier authSignatureVerifier() { + @Bean(name = {"authSigner", "authSignatureVerifier"}) + public SignerVerifier authSignerVerifier() { + // If using RSA, need to configure authSigner and authSignatureVerifier separately. + // If using MacSigner, need to protect the shared key by properly encryption. return new MacSigner("Please change this key."); } + + @Bean(name = "authIDTokenStore") + public TokenStore authIDTokenStore(@Autowired @Qualifier("authSigner") Signer signer, + @Autowired @Qualifier("authSignatureVerifier") SignatureVerifier signerVerifier) { + return new JWTTokenStore(signer, signerVerifier); + } + }
