From f9a3d5f4c10172512732e22a2cd973e3813ad76d Mon Sep 17 00:00:00 2001 From: ZeruiYang Date: Fri, 6 Dec 2024 11:43:35 +0800 Subject: [PATCH] Add RESTful Dubbo Integration with Spring Security and OAuth2 Sample (#1193) * feat: add authorization server * feat: create a resource server * feat: create case-configuration.yml * feat: create case-versions.conf * feat: add RestExtension spi * fix: use restExtension to intercept request(unfinished) * fix: use restExtension to intercept request * feat: update case-configuration.yml * fix: use restExtension to intercept request * fix: change the name of the test file * fix: change the systemProps in the case-configuration.yml * fix: change the systemProps in the case-configuration.yml * feat: add test cases * fix: update config in AuthorizationSeverConfig.java --------- Co-authored-by: Sean Yang --- .../case-configuration.yml | 51 ++++++++ .../case-versions.conf | 25 ++++ .../pom.xml | 61 +++++++++ .../pom.xml | 98 +++++++++++++++ .../rest/demo/AuthorizationApplication.java | 30 +++++ .../demo/config/AuthorizationSeverConfig.java | 73 +++++++++++ .../src/main/resources/application.yml | 27 ++++ .../java/OAuth2AuthorizationServerTest.java | 75 +++++++++++ .../spring-security-resource-server/pom.xml | 106 ++++++++++++++++ .../dubbo/rest/demo/ResourceApplication.java | 32 +++++ .../dubbo/rest/demo/filter/OAuthFilter.java | 95 ++++++++++++++ .../dubbo/rest/demo/service/HelloService.java | 29 +++++ .../demo/service/impl/HelloServiceImpl.java | 30 +++++ ...rpc.protocol.tri.rest.filter.RestExtension | 1 + .../src/main/resources/application.yml | 31 +++++ .../src/test/java/ResourceServerIT.java | 116 ++++++++++++++++++ 2-advanced/dubbo-samples-triple-rest/pom.xml | 1 + 17 files changed, 881 insertions(+) create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-configuration.yml create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-versions.conf create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/pom.xml create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/pom.xml create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/AuthorizationApplication.java create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/config/AuthorizationSeverConfig.java create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/resources/application.yml create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/test/java/OAuth2AuthorizationServerTest.java create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/pom.xml create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/ResourceApplication.java create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/filter/OAuthFilter.java create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/HelloService.java create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/impl/HelloServiceImpl.java create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/application.yml create mode 100644 2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/test/java/ResourceServerIT.java diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-configuration.yml b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-configuration.yml new file mode 100644 index 0000000000..a6ffa9f6a8 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-configuration.yml @@ -0,0 +1,51 @@ +# 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. + +services: + authorization: + type: app + basedir: spring-security-authorization-server + mainClass: org.apache.dubbo.rest.demo.AuthorizationApplication + systemProps: + - authorization.address=authorization + checkPorts: + - 9000 + checkLog: "authorization service started" + + resource: + type: app + basedir: spring-security-resource-server + mainClass: org.apache.dubbo.rest.demo.ResourceApplication + systemProps: + - authorization.address=authorization + checkPorts: + - 50051 + checkLog: "resource service started" + + test: + type: test + basedir: spring-security-resource-server + tests: + - "**/*IT.class" + systemProps: + - authorization.address=authorization + - resource.address=resource + waitPortsBeforeRun: + - authorization:9000 + - resource:50051 + depends_on: + - authorization + - resource diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-versions.conf b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-versions.conf new file mode 100644 index 0000000000..82aeb45603 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/case-versions.conf @@ -0,0 +1,25 @@ +# +# +# 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. +# + + +# Supported component versions of the test case + +# Spring app +dubbo.version=3.3.* +spring.version=6.* +java.version= [>= 17] diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/pom.xml b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/pom.xml new file mode 100644 index 0000000000..f4d69047fe --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/pom.xml @@ -0,0 +1,61 @@ + + + + 4.0.0 + + org.apache.dubbo + dubbo-samples-triple-rest + 1.0.0-SNAPSHOT + + pom + + spring-security-resource-server + spring-security-authorization-server + + + + + + org.springframework.security + spring-security-core + 6.2.2 + + + org.springframework.security + spring-security-oauth2-authorization-server + 1.2.3 + + + org.springframework.security + spring-security-oauth2-resource-server + 6.2.2 + + + org.springframework.security + spring-security-oauth2-jose + 6.2.2 + + + + + org.apache.dubbo.rest.demo + dubbo-samples-triple-rest-spring-security + + diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/pom.xml b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/pom.xml new file mode 100644 index 0000000000..3bfd26479b --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/pom.xml @@ -0,0 +1,98 @@ + + + + 4.0.0 + + + org.apache.dubbo.rest.demo + dubbo-samples-triple-rest-spring-security + 1.0.0-SNAPSHOT + + + org.apache.dubbo.rest.demo + spring-security-authorization-server + 1.0-SNAPSHOT + + + 17 + + + + + + + org.springframework.boot + spring-boot-starter-web + + + + + org.springframework.boot + spring-boot-starter-security + + + + + org.springframework.security + spring-security-oauth2-authorization-server + + + + + org.springframework.security + spring-security-oauth2-jose + + + nimbus-jose-jwt + com.nimbusds + + + + + + + org.springframework.boot + spring-boot-starter-logging + + + + + org.springframework.boot + spring-boot-starter-test + test + + + + + org.springframework.security + spring-security-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/AuthorizationApplication.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/AuthorizationApplication.java new file mode 100644 index 0000000000..0d8278670b --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/AuthorizationApplication.java @@ -0,0 +1,30 @@ +/* + * 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.dubbo.rest.demo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class AuthorizationApplication { + public static void main(String[] args) { + SpringApplication.run(AuthorizationApplication.class, args); + System.out.println("authorization service started"); + } +} + diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/config/AuthorizationSeverConfig.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/config/AuthorizationSeverConfig.java new file mode 100644 index 0000000000..393e0a9032 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/java/org/apache/dubbo/rest/demo/config/AuthorizationSeverConfig.java @@ -0,0 +1,73 @@ +/* + * 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.dubbo.rest.demo.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.server.authorization.client.InMemoryRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.web.SecurityFilterChain; + +import java.util.UUID; + +@Configuration +@EnableWebSecurity +public class AuthorizationSeverConfig { + + private static final String HOST = System.getProperty("authorization.address", "localhost"); + + String issuer = "http://" + HOST + ":9000"; + + @Bean + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { + OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http); + + return http.build(); + } + + @Bean + public RegisteredClientRepository registeredClientRepository() { + RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString()) + .clientId("49fd8518-12eb-422b-9264-2bae0ab89f66") //configure the client id + .clientSecret("{noop}H3DTtm2fR3GRAdr4ls1mcg") // configure the client secret + .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) + .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) + .redirectUri("http://localhost:9000/oauth2/token") // configure the redirect uri + .scope("openid") + .scope("read") + .scope("write") + .build(); + + return new InMemoryRegisteredClientRepository(registeredClient); + } + + @Bean + public AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder() + .issuer(issuer) // set the address of the authorization server + .build(); + } + +} diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/resources/application.yml b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/resources/application.yml new file mode 100644 index 0000000000..a4c8163a8f --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/main/resources/application.yml @@ -0,0 +1,27 @@ +# 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. + +server: + port: 9000 + +spring: + application: + name: authorization-server # Application name for Spring Boot + +logging: + level: + org.springframework.security: DEBUG + org.springframework.boot.autoconfigure.security: DEBUG diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/test/java/OAuth2AuthorizationServerTest.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/test/java/OAuth2AuthorizationServerTest.java new file mode 100644 index 0000000000..5cf0335088 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-authorization-server/src/test/java/OAuth2AuthorizationServerTest.java @@ -0,0 +1,75 @@ +/* + * 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. + */ + +import org.apache.dubbo.rest.demo.AuthorizationApplication; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; + +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientResponseException; + +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + +@SpringBootTest(classes = AuthorizationApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +public class OAuth2AuthorizationServerTest { + + @LocalServerPort + private int port; + + private final String clientId = "49fd8518-12eb-422b-9264-2bae0ab89f66"; + private final String clientSecret = "H3DTtm2fR3GRAdr4ls1mcg"; + + @Test + public void testClientCredentialsGrantFlow() { + assertNotEquals(0, port, "Port should not be 0"); + // build Basic Auth header + String credentials = clientId + ":" + clientSecret; + String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes()); + System.out.println("Encoded Credentials: " + encodedCredentials); + + // build RestClient request + RestClient restClient = RestClient.builder().build(); + String url = "http://localhost:" + port + "/oauth2/token"; + + try { + // make a post request + String response = restClient.post() + .uri(url) + .header(HttpHeaders.AUTHORIZATION, "Basic " + encodedCredentials) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .body("grant_type=client_credentials&scope=read") + .retrieve() + .body(String.class); + + System.out.println("Access Token Response: " + response); + + } catch (RestClientResponseException e) { + // use getStatusCode().value() to get status code + assertEquals(HttpStatus.UNAUTHORIZED.value(), e.getStatusCode() + .value(), "The request failed and was not authorized"); + System.err.println("Error Response: " + e.getResponseBodyAsString()); + } + } +} diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/pom.xml b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/pom.xml new file mode 100644 index 0000000000..83f268c38e --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/pom.xml @@ -0,0 +1,106 @@ + + + + 4.0.0 + + + org.apache.dubbo.rest.demo + dubbo-samples-triple-rest-spring-security + 1.0.0-SNAPSHOT + + + spring-security-resource-server + + + 17 + 3.2.3 + 3.3.1 + + + + + + org.apache.dubbo + dubbo-spring-boot-starter + ${dubbo.version} + + + + + org.springframework.boot + spring-boot-starter-web + ${spring.boot.version} + + + + + org.springframework.boot + spring-boot-starter-logging + ${spring.boot.version} + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + ${spring.boot.version} + + + + + org.springframework.security + spring-security-oauth2-jose + 6.2.2 + + + + + + + + + + + + + org.springframework.boot + spring-boot-starter-test + ${spring.boot.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${spring.boot.version} + + + + diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/ResourceApplication.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/ResourceApplication.java new file mode 100644 index 0000000000..305b2fde3c --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/ResourceApplication.java @@ -0,0 +1,32 @@ +/* + * 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.dubbo.rest.demo; + +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +@EnableDubbo +public class ResourceApplication { + public static void main(String[] args) { + SpringApplication.run(ResourceApplication.class, args); + System.out.println("resource service started"); + } +} diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/filter/OAuthFilter.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/filter/OAuthFilter.java new file mode 100644 index 0000000000..f14ea351c8 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/filter/OAuthFilter.java @@ -0,0 +1,95 @@ +/* + * 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.dubbo.rest.demo.filter; + +import jakarta.servlet.Filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletRequest; +import jakarta.servlet.ServletResponse; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; + +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension; + +import org.springframework.security.oauth2.jwt.Jwt; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; +import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; + +import java.io.IOException; + +@Activate +public class OAuthFilter implements Filter, RestExtension { + + private static final String HOST = System.getProperty("authorization.address", "localhost"); + + String issuer = "http://" + HOST + ":9000"; + + private JwtDecoder jwtDecoder; + private JwtAuthenticationConverter jwtAuthenticationConverter; + + @Override + public void init(FilterConfig filterConfig) { + // Initialize the JwtDecoder and obtain the public key from the configured authorization server URL for decoding the JWT + jwtDecoder = NimbusJwtDecoder.withIssuerLocation(issuer).build(); + // Initialize JwtAuthenticationConverter to convert JWT + jwtAuthenticationConverter = new JwtAuthenticationConverter(); + JwtGrantedAuthoritiesConverter authoritiesConverter = new JwtGrantedAuthoritiesConverter(); + jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(authoritiesConverter); + } + + @Override + public String[] getPatterns() { + return new String[] {"/**"}; // Intercept all requests + } + + @Override + public void doFilter( + ServletRequest servletRequest, + ServletResponse servletResponse, + FilterChain filterChain) throws IOException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + String authorization = request.getHeader("Authorization"); + if (authorization != null && authorization.startsWith("Bearer ")) { + String jwtToken = authorization.substring("Bearer ".length()); + // Decode the JWT token + try { + Jwt jwt = jwtDecoder.decode(jwtToken); + jwtAuthenticationConverter.convert(jwt); + filterChain.doFilter(request, response); + } catch (Exception e) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid JWT token"); + } + + } else { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing JWT token"); + } + + } + + @Override + public int getPriority() { + return -200; + } +} diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/HelloService.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/HelloService.java new file mode 100644 index 0000000000..a2230a1f79 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/HelloService.java @@ -0,0 +1,29 @@ +/* + * 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.dubbo.rest.demo.service; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("/hello") +public interface HelloService { + + @GetMapping("/sayHello/{name}") + String sayHello(@PathVariable String name); +} diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/impl/HelloServiceImpl.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/impl/HelloServiceImpl.java new file mode 100644 index 0000000000..49e1c14741 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/java/org/apache/dubbo/rest/demo/service/impl/HelloServiceImpl.java @@ -0,0 +1,30 @@ +/* + * 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.dubbo.rest.demo.service.impl; + +import org.apache.dubbo.config.annotation.DubboService; +import org.apache.dubbo.rest.demo.service.HelloService; + +@DubboService +public class HelloServiceImpl implements HelloService { + + @Override + public String sayHello(String name) { + return "Hello, " + name; + } +} diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension new file mode 100644 index 0000000000..69e53cb32f --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/META-INF/dubbo/org.apache.dubbo.rpc.protocol.tri.rest.filter.RestExtension @@ -0,0 +1 @@ +OAuthFilter=org.apache.dubbo.rest.demo.filter.OAuthFilter diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/application.yml b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/application.yml new file mode 100644 index 0000000000..88148fd184 --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/main/resources/application.yml @@ -0,0 +1,31 @@ +# 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. +server: + port: 9001 + +spring: + application: + name: resource-server # Application name for Spring Boot + +dubbo: + application: + name: ${spring.application.name} + qos-enable: false + protocol: + name: tri # Use the Triple protocol + port: 50051 # Service port (adjust as needed) + triple: + verbose: true # Enable verbose mode diff --git a/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/test/java/ResourceServerIT.java b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/test/java/ResourceServerIT.java new file mode 100644 index 0000000000..aabfdeda4c --- /dev/null +++ b/2-advanced/dubbo-samples-triple-rest/dubbo-samples-triple-rest-spring-security/spring-security-resource-server/src/test/java/ResourceServerIT.java @@ -0,0 +1,116 @@ +/* + * 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. + */ + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +import org.apache.dubbo.config.spring.context.annotation.EnableDubbo; + +import org.junit.Test; +import org.junit.jupiter.api.Assertions; +import org.junit.runner.RunWith; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClientResponseException; + +import java.util.Base64; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@EnableDubbo +@RunWith(SpringRunner.class) +public class ResourceServerIT { + + // @LocalServerPort + // private int port; + +// @DubboReference(url = "tri://localhost:50051") +// private HelloService helloService; + + private final String clientId = "49fd8518-12eb-422b-9264-2bae0ab89f66"; + private final String clientSecret = "H3DTtm2fR3GRAdr4ls1mcg"; + + private static final String OAUTH2HOST = System.getProperty("authorization.address", "localhost"); + private static final String HOST = System.getProperty("resource.address", "localhost"); + + @Test + public void testGetUserEndpoint() { + String credentials = clientId + ":" + clientSecret; + String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes()); + + // build RestClient request + RestClient restClient = RestClient.builder().build(); + String url = "http://"+ OAUTH2HOST + ":9000/oauth2/token"; + + try { + // make a post request + String response = restClient.post() + .uri(url) + .header(HttpHeaders.AUTHORIZATION, "Basic " + encodedCredentials) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) + .body("grant_type=client_credentials&scope=read") + .retrieve() + .body(String.class); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode jsonNode = objectMapper.readTree(response); + String accessToken = jsonNode.get("access_token").asText(); + + // Use the access token to authenticate the request to the /user endpoint + assert accessToken != null; + String userUrl = "http://" + HOST + ":50051/hello/sayHello/World"; + try { + String userResponse = restClient.get() + .uri(userUrl) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken) + .retrieve() + .body(String.class); + + assertEquals("\"Hello, World\"", userResponse, "error"); + } catch (RestClientResponseException e) { + System.err.println("Error Response: " + e.getResponseBodyAsString()); + Assertions.fail("Request failed with response: " + e.getResponseBodyAsString()); + } + + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Test + public void testGetUserEndpointWithInvalidToken() { + String invalidAccessToken = "invalid_token"; + RestClient restClient = RestClient.builder().build(); + String userUrl = "http://" + HOST + ":50051/hello/sayHello/World"; + + try { + restClient.get() + .uri(userUrl) + .header(HttpHeaders.AUTHORIZATION, "Bearer " + invalidAccessToken) + .retrieve() + .body(String.class); + + Assertions.fail("Request should have failed with an invalid token"); + } catch (RestClientResponseException e) { + System.err.println("Error Response: " + e.getResponseBodyAsString()); + assertEquals(401, e.getStatusCode().value(), "Expected 401 Unauthorized status"); + } + } +} diff --git a/2-advanced/dubbo-samples-triple-rest/pom.xml b/2-advanced/dubbo-samples-triple-rest/pom.xml index 4826d54178..d0910334d8 100644 --- a/2-advanced/dubbo-samples-triple-rest/pom.xml +++ b/2-advanced/dubbo-samples-triple-rest/pom.xml @@ -46,6 +46,7 @@ dubbo-samples-triple-rest-basic dubbo-samples-triple-rest-jaxrs dubbo-samples-triple-rest-springmvc + dubbo-samples-triple-rest-spring-security