diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/pom.xml b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/pom.xml
new file mode 100644
index 00000000..9cf3e878
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/pom.xml
@@ -0,0 +1,53 @@
+
+
+
+ shenyu-plugin-security
+ org.apache.shenyu
+ 2.6.0-SNAPSHOT
+
+ 4.0.0
+
+ shenyu-plugin-maxkey
+
+
+
+ org.apache.shenyu
+ shenyu-plugin-base
+ ${project.version}
+
+
+
+ org.apache.oltu.oauth2
+ org.apache.oltu.oauth2.client
+ 1.0.2
+
+
+
+ cn.hutool
+ hutool-all
+ 5.8.16
+
+
+
+
+ org.springframework.security
+ spring-security-test
+ test
+
+
+
+ org.springframework
+ spring-test
+ test
+
+
+
+ io.projectreactor
+ reactor-test
+ test
+
+
+
+
\ No newline at end of file
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/MaxKeyPlugin.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/MaxKeyPlugin.java
new file mode 100644
index 00000000..85aa4ffb
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/MaxKeyPlugin.java
@@ -0,0 +1,119 @@
+/*
+ * 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.shenyu.plugin.maxkey;
+
+import cn.hutool.core.codec.Base64;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.common.utils.Singleton;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.result.ShenyuResultEnum;
+import org.apache.shenyu.plugin.api.result.ShenyuResultWrap;
+import org.apache.shenyu.plugin.api.utils.WebFluxResultUtils;
+import org.apache.shenyu.plugin.base.AbstractShenyuPlugin;
+import org.apache.shenyu.plugin.maxkey.config.MaxkeyConfig;
+import org.apache.shenyu.plugin.maxkey.service.MaxkeyService;
+import org.apache.shenyu.plugin.maxkey.service.MaxkeyUser;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpRequest;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+
+import java.util.Objects;
+
+/**
+ * The type Maxkey plugin.
+ */
+public class MaxKeyPlugin extends AbstractShenyuPlugin {
+
+ @Override
+ protected Mono doExecute(final ServerWebExchange exchange, final ShenyuPluginChain chain, final SelectorData selector, final RuleData rule) {
+ MaxkeyService maxkeyService = Singleton.INST.get(MaxkeyService.class);
+ MaxkeyConfig config = maxkeyService.getMaxkeyConfig();
+ ServerHttpRequest request = exchange.getRequest();
+
+ // 这里处理需要token的逻辑
+ if (config.isBearerOnly()) {
+ String token = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+ boolean isActive = maxkeyService.introspectAccessToken(token);
+ if (isActive) {
+ // 根据配置决定是否获取userInfo
+ return chain.execute(handlerUserInfo(exchange, token, maxkeyService, config.isSetUserInfoHeader()));
+ }
+ Object error = ShenyuResultWrap.error(exchange, ShenyuResultEnum.ERROR_TOKEN);
+ return WebFluxResultUtils.result(exchange, error);
+ }
+
+ // 走到这里说明没有token 需要处理code授权码的逻辑
+ String code = request.getQueryParams().getFirst("code");
+ String state = request.getQueryParams().getFirst("state");
+ if (Objects.nonNull(code)) {
+ String token = maxkeyService.getOAuthToken(code);
+ // 根据配置决定是否获取userInfo
+ return chain.execute(handlerUserInfo(exchange, token, maxkeyService, config.isSetUserInfoHeader()));
+ }
+
+ // 走到这里说明没有code 需要重定向至IdP服务获取code
+ return maxkeyService.redirect(exchange, state);
+ }
+
+ @Override
+ public int getOrder() {
+ return PluginEnum.MAXKEY.getCode();
+ }
+
+ @Override
+ public String named() {
+ return PluginEnum.MAXKEY.getName();
+ }
+
+ @Override
+ public boolean skip(final ServerWebExchange exchange) {
+ return false;
+ }
+
+ // 判断直接传递token还是传递userInfo
+ private ServerWebExchange handlerUserInfo(final ServerWebExchange exchange, final String token, final MaxkeyService maxkeyService, final boolean setUserInfo) {
+ if (setUserInfo) {
+ MaxkeyUser maxkeyUser = maxkeyService.getMaxkeyUser(token);
+ return handleToken(exchange, maxkeyUser);
+ } else {
+ return handleToken(exchange, token);
+ }
+ }
+
+ // 直接使用AccessToken访问收保护的资源
+ private ServerWebExchange handleToken(final ServerWebExchange exchange, final String accessToken) {
+ ServerHttpRequest.Builder mutate = exchange.getRequest().mutate();
+ mutate.headers(httpHeaders -> httpHeaders.remove(HttpHeaders.ACCEPT_ENCODING));
+ mutate.header(HttpHeaders.AUTHORIZATION, accessToken);
+ return exchange.mutate().request(mutate.build()).build();
+ }
+
+ // 获取原始请求对象 根据MaxKey认证解析后的userInfo 重新构建请求头 访问收保护的资源
+ private ServerWebExchange handleToken(final ServerWebExchange exchange, final MaxkeyUser maxkeyUser) {
+ ServerHttpRequest.Builder mutate = exchange.getRequest().mutate();
+ mutate.headers(httpHeaders -> httpHeaders.remove(HttpHeaders.ACCEPT_ENCODING));
+ String maxkeyUserInfoJson = GsonUtils.getInstance().toJson(maxkeyUser);
+ mutate.header("X-Userinfo", Base64.encode(maxkeyUserInfoJson));
+ return exchange.mutate().request(mutate.build()).build();
+ }
+
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/config/MaxkeyConfig.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/config/MaxkeyConfig.java
new file mode 100644
index 00000000..482d2159
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/config/MaxkeyConfig.java
@@ -0,0 +1,377 @@
+/*
+ * 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.shenyu.plugin.maxkey.config;
+
+public class MaxkeyConfig {
+
+ private String clientId;
+
+ private String clientSecret;
+
+ private String authorizationEndpoint;
+
+ private String scope;
+
+ private String responseType;
+
+ private String redirectUrl;
+
+ private String realm;
+
+ private String grantType;
+
+ private String tokenEndpoint;
+
+ private boolean bearerOnly;
+
+ private String introspectionEndpoint;
+
+ private boolean setUserInfoHeader;
+
+ private String userInfoEndpoint;
+
+ private String introspectionEndpointAuthMethodsSupported;
+
+ private String discovery;
+
+ public MaxkeyConfig() {
+ }
+
+ public MaxkeyConfig(final String clientId,
+ final String clientSecret,
+ final String authorizationEndpoint,
+ final String scope,
+ final String responseType,
+ final String redirectUrl,
+ final String realm,
+ final String grantType,
+ final String tokenEndpoint,
+ final boolean bearerOnly,
+ final String introspectionEndpoint,
+ final boolean setUserInfoHeader,
+ final String userInfoEndpoint,
+ final String introspectionEndpointAuthMethodsSupported,
+ final String discovery) {
+ this.clientId = clientId;
+ this.clientSecret = clientSecret;
+ this.authorizationEndpoint = authorizationEndpoint;
+ this.scope = scope;
+ this.responseType = responseType;
+ this.redirectUrl = redirectUrl;
+ this.realm = realm;
+ this.grantType = grantType;
+ this.tokenEndpoint = tokenEndpoint;
+ this.bearerOnly = bearerOnly;
+ this.introspectionEndpoint = introspectionEndpoint;
+ this.setUserInfoHeader = setUserInfoHeader;
+ this.userInfoEndpoint = userInfoEndpoint;
+ this.introspectionEndpointAuthMethodsSupported = introspectionEndpointAuthMethodsSupported;
+ this.discovery = discovery;
+ }
+
+ /**
+ * Gets clientId.
+ *
+ * @return the clientId
+ */
+ public String getClientId() {
+ return clientId;
+ }
+
+ /**
+ * Sets clientId.
+ *
+ * @param clientId the clientId
+ */
+ public void setClientId(final String clientId) {
+ this.clientId = clientId;
+ }
+
+ /**
+ * Gets clientSecret.
+ *
+ * @return the clientSecret
+ */
+ public String getClientSecret() {
+ return clientSecret;
+ }
+
+ /**
+ * Sets clientSecret.
+ *
+ * @param clientSecret the clientSecret
+ */
+ public void setClientSecret(final String clientSecret) {
+ this.clientSecret = clientSecret;
+ }
+
+ /**
+ * Gets authorizationEndpoint.
+ *
+ * @return the authorizationEndpoint
+ */
+ public String getAuthorizationEndpoint() {
+ return authorizationEndpoint;
+ }
+
+ /**
+ * Sets authorizationEndpoint.
+ *
+ * @param authorizationEndpoint the authorizationEndpoint
+ */
+ public void setAuthorizationEndpoint(final String authorizationEndpoint) {
+ this.authorizationEndpoint = authorizationEndpoint;
+ }
+
+ /**
+ * Gets scope.
+ *
+ * @return the scope
+ */
+ public String getScope() {
+ return scope;
+ }
+
+ /**
+ * Sets scope.
+ *
+ * @param scope the scope
+ */
+ public void setScope(final String scope) {
+ this.scope = scope;
+ }
+
+ /**
+ * Gets responseType.
+ *
+ * @return the responseType
+ */
+ public String getResponseType() {
+ return responseType;
+ }
+
+ /**
+ * Sets responseType.
+ *
+ * @param responseType the responseType
+ */
+ public void setResponseType(final String responseType) {
+ this.responseType = responseType;
+ }
+
+ /**
+ * Gets redirectUrl.
+ *
+ * @return the redirectUrl
+ */
+ public String getRedirectUrl() {
+ return redirectUrl;
+ }
+
+ /**
+ * Sets redirectUrl.
+ *
+ * @param redirectUrl the redirectUrl
+ */
+ public void setRedirectUrl(final String redirectUrl) {
+ this.redirectUrl = redirectUrl;
+ }
+
+ /**
+ * Gets realm.
+ *
+ * @return the realm
+ */
+ public String getRealm() {
+ return realm;
+ }
+
+ /**
+ * Sets realm.
+ *
+ * @param realm the realm
+ */
+ public void setRealm(final String realm) {
+ this.realm = realm;
+ }
+
+ /**
+ * Gets tokenType.
+ *
+ * @return the tokenType
+ */
+ public String getGrantType() {
+ return grantType;
+ }
+
+ /**
+ * Sets grantType.
+ *
+ * @param grantType the grantType
+ */
+ public void setGrantType(final String grantType) {
+ this.grantType = grantType;
+ }
+
+ /**
+ * Gets tokenEndpoint.
+ *
+ * @return the tokenEndpoint
+ */
+ public String getTokenEndpoint() {
+ return tokenEndpoint;
+ }
+
+ /**
+ * Sets tokenEndpoint.
+ *
+ * @param tokenEndpoint the tokenEndpoint
+ */
+ public void setTokenEndpoint(final String tokenEndpoint) {
+ this.tokenEndpoint = tokenEndpoint;
+ }
+
+ /**
+ * Is bearerOnly.
+ *
+ * @return is bearerOnly
+ */
+ public boolean isBearerOnly() {
+ return bearerOnly;
+ }
+
+ /**
+ * Sets bearerOnly.
+ *
+ * @param bearerOnly the bearerOnly
+ */
+ public void setBearerOnly(final boolean bearerOnly) {
+ this.bearerOnly = bearerOnly;
+ }
+
+ /**
+ * Gets introspectionEndpoint.
+ *
+ * @return the introspectionEndpoint
+ */
+ public String getIntrospectionEndpoint() {
+ return introspectionEndpoint;
+ }
+
+ /**
+ * Sets introspectionEndpoint.
+ *
+ * @param introspectionEndpoint the introspectionEndpoint
+ */
+ public void setIntrospectionEndpoint(final String introspectionEndpoint) {
+ this.introspectionEndpoint = introspectionEndpoint;
+ }
+
+ /**
+ * Is setUserInfoHeader.
+ *
+ * @return is setUserInfoHeader
+ */
+ public boolean isSetUserInfoHeader() {
+ return setUserInfoHeader;
+ }
+
+ /**
+ * Sets setUserInfoHeader.
+ *
+ * @param setUserInfoHeader the setUserInfoHeader
+ */
+ public void setSetUserInfoHeader(final boolean setUserInfoHeader) {
+ this.setUserInfoHeader = setUserInfoHeader;
+ }
+
+ /**
+ * Gets userInfoEndpoint.
+ *
+ * @return the userInfoEndpoint
+ */
+ public String getUserInfoEndpoint() {
+ return userInfoEndpoint;
+ }
+
+ /**
+ * Sets userInfoEndpoint.
+ *
+ * @param userInfoEndpoint the userInfoEndpoint
+ */
+ public void setUserInfoEndpoint(final String userInfoEndpoint) {
+ this.userInfoEndpoint = userInfoEndpoint;
+ }
+
+ /**
+ * Gets introspectionEndpointAuthMethodsSupported.
+ *
+ * @return the introspectionEndpointAuthMethodsSupported
+ */
+ public String getIntrospectionEndpointAuthMethodsSupported() {
+ return introspectionEndpointAuthMethodsSupported;
+ }
+
+ /**
+ * Sets introspectionEndpointAuthMethodsSupported.
+ *
+ * @param introspectionEndpointAuthMethodsSupported the accessToken
+ */
+ public void setIntrospectionEndpointAuthMethodsSupported(final String introspectionEndpointAuthMethodsSupported) {
+ this.introspectionEndpointAuthMethodsSupported = introspectionEndpointAuthMethodsSupported;
+ }
+
+ /**
+ * Gets discovery.
+ *
+ * @return the discovery
+ */
+ public String getDiscovery() {
+ return discovery;
+ }
+
+ /**
+ * Sets discovery.
+ *
+ * @param discovery the discovery
+ */
+ public void setDiscovery(final String discovery) {
+ this.discovery = discovery;
+ }
+
+ @Override
+ public String toString() {
+ return "MaxkeyConfig{"
+ + "clientId='" + clientId + '\''
+ + ", clientSecret='" + clientSecret + '\''
+ + ", authorizationEndpoint='" + authorizationEndpoint + '\''
+ + ", scope='" + scope + '\''
+ + ", responseType='" + responseType + '\''
+ + ", redirectUrl='" + redirectUrl + '\''
+ + ", realm='" + realm + '\''
+ + ", grantType='" + grantType + '\''
+ + ", tokenEndpoint='" + tokenEndpoint + '\''
+ + ", bearerOnly=" + bearerOnly
+ + ", introspectionEndpoint='" + introspectionEndpoint + '\''
+ + ", setUserInfoHeader=" + setUserInfoHeader
+ + ", userInfoEndpoint='" + userInfoEndpoint + '\''
+ + ", introspectionEndpointAuthMethodsSupported='" + introspectionEndpointAuthMethodsSupported + '\''
+ + ", discovery='" + discovery + '\''
+ + '}';
+ }
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/handle/MaxkeyPluginDataHandler.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/handle/MaxkeyPluginDataHandler.java
new file mode 100644
index 00000000..89f795d5
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/handle/MaxkeyPluginDataHandler.java
@@ -0,0 +1,83 @@
+/*
+ * 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.shenyu.plugin.maxkey.handle;
+
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.common.utils.Singleton;
+import org.apache.shenyu.plugin.base.handler.PluginDataHandler;
+import org.apache.shenyu.plugin.maxkey.config.MaxkeyConfig;
+import org.apache.shenyu.plugin.maxkey.service.MaxkeyService;
+
+import java.util.Map;
+import java.util.Optional;
+
+public class MaxkeyPluginDataHandler implements PluginDataHandler {
+
+ @Override
+ public void handlerPlugin(final PluginData pluginData) {
+
+ // 获取配置参数
+ Map configMap = GsonUtils.getInstance().toObjectMap(pluginData.getConfig(), String.class);
+
+ final String clientId = Optional.ofNullable(configMap.get("clientId")).orElse("");
+ final String clientSecret = Optional.ofNullable(configMap.get("clientSecret")).orElse("");
+ final String authorizationEndpoint = Optional.ofNullable(configMap.get("authorizationEndpoint")).orElse("");
+ final String scope = Optional.ofNullable(configMap.get("scope")).orElse("");
+ final String responseType = Optional.ofNullable(configMap.get("responseType")).orElse("");
+ final String redirectUrl = Optional.ofNullable(configMap.get("redirectUrl")).orElse("");
+ final String realm = Optional.ofNullable(configMap.get("realm")).orElse("");
+ final String grantType = Optional.ofNullable(configMap.get("grantType")).orElse("");
+ final String tokenEndpoint = Optional.ofNullable(configMap.get("tokenEndpoint")).orElse("");
+ final boolean bearerOnly = Optional.ofNullable(configMap.get("bearerOnly")).map(Boolean::parseBoolean).orElse(false);
+ final String introspectionEndpoint = Optional.ofNullable(configMap.get("introspectionEndpoint")).orElse("");
+ final String introspectionEndpointAuthMethodsSupported = Optional.ofNullable(configMap.get("introspectionEndpointAuthMethodsSupported")).orElse("");
+ final boolean setUserInfoHeader = Optional.ofNullable(configMap.get("setUserInfoHeader")).map(Boolean::parseBoolean).orElse(false);
+ final String userInfoEndpoint = Optional.ofNullable(configMap.get("userInfoEndpoint")).orElse("");
+ final String discovery = Optional.ofNullable(configMap.get("discovery")).orElse("");
+
+ // 获取MaxkeyConfig
+ MaxkeyConfig maxkeyConfig = new MaxkeyConfig(
+ clientId,
+ clientSecret,
+ authorizationEndpoint,
+ scope,
+ responseType,
+ redirectUrl,
+ realm,
+ grantType,
+ tokenEndpoint,
+ bearerOnly,
+ introspectionEndpoint,
+ setUserInfoHeader,
+ userInfoEndpoint,
+ introspectionEndpointAuthMethodsSupported,
+ discovery);
+
+ // 根据参数实例化 MaxkeyService 鉴权服务
+ MaxkeyService maxkeyService = new MaxkeyService(maxkeyConfig);
+ Singleton.INST.single(MaxkeyService.class, maxkeyService);
+ }
+
+ @Override
+ public String pluginNamed() {
+ return PluginEnum.MAXKEY.getName();
+ }
+
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/Introspection.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/Introspection.java
new file mode 100644
index 00000000..afe39677
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/Introspection.java
@@ -0,0 +1,81 @@
+/*
+ * 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.shenyu.plugin.maxkey.service;
+
+public class Introspection {
+
+ private String token;
+
+ private boolean active;
+
+ private String sub;
+
+ /**
+ * Gets token.
+ *
+ * @return the token
+ */
+ public String getToken() {
+ return token;
+ }
+
+ /**
+ * Sets token.
+ *
+ * @param token the token
+ */
+ public void setToken(final String token) {
+ this.token = token;
+ }
+
+ /**
+ * Gets active.
+ *
+ * @return is active
+ */
+ public boolean isActive() {
+ return active;
+ }
+
+ /**
+ * Sets active.
+ *
+ * @param active the active
+ */
+ public void setActive(final boolean active) {
+ this.active = active;
+ }
+
+ /**
+ * Gets sub.
+ *
+ * @return the sub
+ */
+ public String getSub() {
+ return sub;
+ }
+
+ /**
+ * Sets sub.
+ *
+ * @param sub the sub
+ */
+ public void setSub(final String sub) {
+ this.sub = sub;
+ }
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/MaxkeyService.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/MaxkeyService.java
new file mode 100644
index 00000000..d036d8cf
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/MaxkeyService.java
@@ -0,0 +1,180 @@
+/*
+ * 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.shenyu.plugin.maxkey.service;
+
+import cn.hutool.http.HttpResponse;
+import cn.hutool.http.HttpUtil;
+import com.google.gson.JsonSyntaxException;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.oltu.oauth2.client.OAuthClient;
+import org.apache.oltu.oauth2.client.URLConnectionClient;
+import org.apache.oltu.oauth2.client.request.OAuthClientRequest;
+import org.apache.oltu.oauth2.client.response.OAuthJSONAccessTokenResponse;
+import org.apache.oltu.oauth2.common.OAuth;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.plugin.maxkey.config.MaxkeyConfig;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.server.reactive.ServerHttpResponse;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.util.UriComponentsBuilder;
+import reactor.core.publisher.Mono;
+
+public class MaxkeyService {
+
+ private static final int REDIRECT_STATE_CODE = 302;
+
+ private final MaxkeyConfig maxkeyConfig;
+
+ public MaxkeyService(final MaxkeyConfig maxkeyConfig) {
+ this.maxkeyConfig = maxkeyConfig;
+ }
+
+ /**
+ * redirect unauthenticated requests to the IdP service.
+ *
+ * @param exchange exchange
+ * @param state state
+ * @return void
+ */
+ public Mono redirect(final ServerWebExchange exchange, final String state) {
+ ServerHttpResponse response = exchange.getResponse();
+ String redirectUrl = UriComponentsBuilder.fromUriString(maxkeyConfig.getAuthorizationEndpoint())
+ .queryParam("response_type", maxkeyConfig.getResponseType())
+ .queryParam("client_id", maxkeyConfig.getClientId())
+ .queryParam("redirect_uri", maxkeyConfig.getRedirectUrl())
+ .queryParam("scope", maxkeyConfig.getScope())
+ .queryParam("state", state)
+ .build()
+ .toUriString();
+
+ response.setRawStatusCode(REDIRECT_STATE_CODE);
+ response.getHeaders().add(HttpHeaders.LOCATION, redirectUrl);
+ return response.setComplete();
+ }
+
+ /**
+ * getAccessToken from maxkey service.
+ *
+ * @param code code
+ * @return String
+ */
+ public String getOAuthToken(final String code) {
+ try {
+ OAuthClientRequest oAuthClientRequest = OAuthClientRequest
+ .tokenLocation(maxkeyConfig.getTokenEndpoint())
+ .setGrantType(GrantType.AUTHORIZATION_CODE)
+ .setClientId(maxkeyConfig.getClientId())
+ .setClientSecret(maxkeyConfig.getClientSecret())
+ .setRedirectURI(String.format("%s", maxkeyConfig.getRedirectUrl()))
+ .setCode(code)
+ .buildQueryMessage();
+ OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
+ OAuthJSONAccessTokenResponse oAuthResponse = oAuthClient.accessToken(oAuthClientRequest, OAuth.HttpMethod.POST);
+ return oAuthResponse.getAccessToken();
+ } catch (OAuthSystemException | OAuthProblemException e) {
+ throw new RuntimeException("Code error, cannot get OAuth token from maxkey server.", e);
+ }
+ }
+
+ /**
+ * getOidcToken from maxkey service.
+ *
+ * @param code code
+ * @param state state
+ * @return OIDCToken
+ */
+ public OIDCToken getOidcToken(final String code, final String state) {
+ String url = maxkeyConfig.getTokenEndpoint();
+ String responseType = maxkeyConfig.getResponseType();
+ String redirectUri = maxkeyConfig.getRedirectUrl();
+ String scope = maxkeyConfig.getScope();
+ String clientId = maxkeyConfig.getClientId();
+ String clientSecret = maxkeyConfig.getClientSecret();
+ String grantType = maxkeyConfig.getGrantType();
+
+ String requestUrl = String.format("%s?response_type=%s&code=%s&redirect_uri=%s&scope=%s&client_id=%s&client_secret=%s&grant_type=%s&state=%s",
+ url, responseType, code, redirectUri, scope, clientId, clientSecret, grantType, state);
+ HttpResponse response = HttpUtil.createGet(requestUrl).execute();
+ OIDCToken oidcToken;
+ try {
+ oidcToken = GsonUtils.getInstance().fromJson(response.body(), OIDCToken.class);
+ } catch (JsonSyntaxException e) {
+ throw new RuntimeException("Code error, cannot get OAuth token from maxkey server.", e);
+ }
+ return oidcToken;
+ }
+
+ /**
+ * introspect AccessToken via maxkey authentication server.
+ *
+ * @param token access token
+ * @return boolean
+ */
+ public boolean introspectAccessToken(final String token) {
+ if (StringUtils.isBlank(token)) {
+ return false;
+ }
+ String url = maxkeyConfig.getIntrospectionEndpoint();
+ String requestUrl = String.format("%s?access_token=%s", url, token);
+ HttpResponse response = HttpUtil
+ .createGet(requestUrl)
+ .execute();
+ Introspection introspection = GsonUtils.getInstance().fromJson(response.body(), Introspection.class);
+ return introspection.isActive();
+ }
+
+ /**
+ * get maxkey user by access token.
+ *
+ * @param token access token
+ * @return MaxkeyUser
+ */
+ public MaxkeyUser getMaxkeyUser(final String token) {
+ String fullUserInfoJson = getUserInfo(token);
+ return GsonUtils.getInstance().fromJson(fullUserInfoJson, MaxkeyUser.class);
+ }
+
+ /**
+ * get user info by access token.
+ *
+ * @param token access token
+ * @return String
+ */
+ public String getUserInfo(final String token) {
+ String url = maxkeyConfig.getUserInfoEndpoint();
+ String requestUrl = String.format("%s?access_token=%s", url, token);
+ HttpResponse response = HttpUtil
+ .createGet(requestUrl)
+ .header("Content-Type", "application/x-www-form-urlencoded")
+ .execute();
+ return response.body();
+ }
+
+ /**
+ * get maxkey config.
+ *
+ * @return MaxkeyConfig
+ */
+ public MaxkeyConfig getMaxkeyConfig() {
+ return this.maxkeyConfig;
+ }
+}
+
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/MaxkeyUser.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/MaxkeyUser.java
new file mode 100644
index 00000000..4cd34510
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/MaxkeyUser.java
@@ -0,0 +1,376 @@
+/*
+ * 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.shenyu.plugin.maxkey.service;
+
+import com.google.gson.annotations.SerializedName;
+
+public class MaxkeyUser {
+
+ private String userId;
+
+ private String name;
+
+ private String displayName;
+
+ private String department;
+
+ private String departmentId;
+
+ private String gender;
+
+ private String phoneNumber;
+
+ private String email;
+
+ private String region;
+
+ private Address address;
+
+ /**
+ * Gets userId.
+ *
+ * @return the userId
+ */
+ public String getUserId() {
+ return userId;
+ }
+
+ /**
+ * Sets userId.
+ *
+ * @param userId the userId
+ */
+ public void setUserId(final String userId) {
+ this.userId = userId;
+ }
+
+ /**
+ * Gets clientId.
+ *
+ * @return the clientId
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets name.
+ *
+ * @param name the name
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * Gets displayName.
+ *
+ * @return the displayName
+ */
+ public String getDisplayName() {
+ return displayName;
+ }
+
+ /**
+ * Sets displayName.
+ *
+ * @param displayName the displayName
+ */
+ public void setDisplayName(final String displayName) {
+ this.displayName = displayName;
+ }
+
+ /**
+ * Gets department.
+ *
+ * @return the department
+ */
+ public String getDepartment() {
+ return department;
+ }
+
+ /**
+ * Sets department.
+ *
+ * @param department the department
+ */
+ public void setDepartment(final String department) {
+ this.department = department;
+ }
+
+ /**
+ * Gets departmentId.
+ *
+ * @return the departmentId
+ */
+ public String getDepartmentId() {
+ return departmentId;
+ }
+
+ /**
+ * Sets departmentId.
+ *
+ * @param departmentId the departmentId
+ */
+ public void setDepartmentId(final String departmentId) {
+ this.departmentId = departmentId;
+ }
+
+ /**
+ * Gets gender.
+ *
+ * @return the gender
+ */
+ public String getGender() {
+ return gender;
+ }
+
+ /**
+ * Sets gender.
+ *
+ * @param gender the gender
+ */
+ public void setGender(final String gender) {
+ this.gender = gender;
+ }
+
+ /**
+ * Gets phoneNumber.
+ *
+ * @return the phoneNumber
+ */
+ public String getPhoneNumber() {
+ return phoneNumber;
+ }
+
+ /**
+ * Sets gender.
+ *
+ * @param phoneNumber the phoneNumber
+ */
+ public void setPhoneNumber(final String phoneNumber) {
+ this.phoneNumber = phoneNumber;
+ }
+
+ /**
+ * Gets email.
+ *
+ * @return the email
+ */
+ public String getEmail() {
+ return email;
+ }
+
+ /**
+ * Sets email.
+ *
+ * @param email the email
+ */
+ public void setEmail(final String email) {
+ this.email = email;
+ }
+
+ /**
+ * Gets region.
+ *
+ * @return the region
+ */
+ public String getRegion() {
+ return region;
+ }
+
+ /**
+ * Sets region.
+ *
+ * @param region the region
+ */
+ public void setRegion(final String region) {
+ this.region = region;
+ }
+
+ /**
+ * Gets address.
+ *
+ * @return the address
+ */
+ public Address getAddress() {
+ return address;
+ }
+
+ /**
+ * Sets address.
+ *
+ * @param address the address
+ */
+ public void setAddress(final Address address) {
+ this.address = address;
+ }
+
+ @Override
+ public String toString() {
+ return "MaxkeyUser{"
+ + "userId='" + userId + '\''
+ + ", name='" + name + '\''
+ + ", displayName='" + displayName + '\''
+ + ", department='" + department + '\''
+ + ", departmentId='" + departmentId + '\''
+ + ", gender='" + gender + '\''
+ + ", phoneNumber='" + phoneNumber + '\''
+ + ", email='" + email + '\''
+ + ", region='" + region + '\''
+ + ", address=" + address.toString()
+ + '}';
+ }
+
+ public static class Address {
+
+ private String country;
+
+ @SerializedName("street_address")
+ private String streetAddress;
+
+ private String formatted;
+
+ private String locality;
+
+ private String region;
+
+ @SerializedName("postal_code")
+ private String postalCode;
+
+ /**
+ * Gets country.
+ *
+ * @return the country
+ */
+ public String getCountry() {
+ return country;
+ }
+
+ /**
+ * Sets streetAddress.
+ *
+ * @param country the streetAddress
+ */
+ public void setCountry(final String country) {
+ this.country = country;
+ }
+
+ /**
+ * Gets Street_address.
+ *
+ * @return the streetAddress
+ */
+ public String getStreetAddress() {
+ return streetAddress;
+ }
+
+ /**
+ * Sets streetAddress.
+ *
+ * @param streetAddress the streetAddress
+ */
+ public void setStreetAddress(final String streetAddress) {
+ this.streetAddress = streetAddress;
+ }
+
+ /**
+ * Gets formatted.
+ *
+ * @return the formatted
+ */
+ public String getFormatted() {
+ return formatted;
+ }
+
+ /**
+ * Sets formatted.
+ *
+ * @param formatted the formatted
+ */
+ public void setFormatted(final String formatted) {
+ this.formatted = formatted;
+ }
+
+ /**
+ * Gets formatted.
+ *
+ * @return the formatted
+ */
+ public String getLocality() {
+ return locality;
+ }
+
+ /**
+ * Sets locality.
+ *
+ * @param locality the locality
+ */
+ public void setLocality(final String locality) {
+ this.locality = locality;
+ }
+
+ /**
+ * Gets region.
+ *
+ * @return the region
+ */
+ public String getRegion() {
+ return region;
+ }
+
+ /**
+ * Sets region.
+ *
+ * @param region the region
+ */
+ public void setRegion(final String region) {
+ this.region = region;
+ }
+
+ /**
+ * Gets postalCode.
+ *
+ * @return the postalCode
+ */
+ public String getPostalCode() {
+ return postalCode;
+ }
+
+ /**
+ * Sets postalCode.
+ *
+ * @param postalCode the postalCode
+ */
+ public void setPostalCode(final String postalCode) {
+ this.postalCode = postalCode;
+ }
+
+ @Override
+ public String toString() {
+ return "Address{"
+ + "country='" + country + '\''
+ + ", streetAddress='" + streetAddress + '\''
+ + ", formatted='" + formatted + '\''
+ + ", locality='" + locality + '\''
+ + ", region='" + region + '\''
+ + ", postalCode='" + postalCode + '\''
+ + '}';
+ }
+ }
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/OIDCToken.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/OIDCToken.java
new file mode 100644
index 00000000..5ebc9025
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/main/java/org/apache/shenyu/plugin/maxkey/service/OIDCToken.java
@@ -0,0 +1,178 @@
+/*
+ * 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.shenyu.plugin.maxkey.service;
+
+import com.google.gson.annotations.SerializedName;
+
+public class OIDCToken {
+
+ @SerializedName("access_token")
+ private String accessToken;
+
+ @SerializedName("token_type")
+ private String tokenType;
+
+ @SerializedName("refresh_token")
+ private String refreshToken;
+
+ @SerializedName("expires_in")
+ private int expiresIn;
+
+ @SerializedName("scope")
+ private String scope;
+
+ @SerializedName("id_token")
+ private String idToken;
+
+ public OIDCToken() {
+ }
+
+ public OIDCToken(final String accessToken,
+ final String tokenType,
+ final String refreshToken,
+ final int expiresIn,
+ final String scope,
+ final String idToken) {
+ this.accessToken = accessToken;
+ this.tokenType = tokenType;
+ this.refreshToken = refreshToken;
+ this.expiresIn = expiresIn;
+ this.scope = scope;
+ this.idToken = idToken;
+ }
+
+ /**
+ * Gets accessToken.
+ *
+ * @return the accessToken
+ */
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ /**
+ * Sets accessToken.
+ *
+ * @param accessToken the accessToken
+ */
+ public void setAccessToken(final String accessToken) {
+ this.accessToken = accessToken;
+ }
+
+ /**
+ * Gets tokenType.
+ *
+ * @return the tokenType
+ */
+ public String getTokenType() {
+ return tokenType;
+ }
+
+ /**
+ * Sets tokenType.
+ *
+ * @param tokenType the tokenType
+ */
+ public void setTokenType(final String tokenType) {
+ this.tokenType = tokenType;
+ }
+
+ /**
+ * Gets refreshToken.
+ *
+ * @return the refreshToken
+ */
+ public String getRefreshToken() {
+ return refreshToken;
+ }
+
+ /**
+ * Sets refreshToken.
+ *
+ * @param refreshToken the refreshToken
+ */
+ public void setRefreshToken(final String refreshToken) {
+ this.refreshToken = refreshToken;
+ }
+
+ /**
+ * Gets expiresIn.
+ *
+ * @return the expiresIn
+ */
+ public int getExpiresIn() {
+ return expiresIn;
+ }
+
+ /**
+ * Sets expiresIn.
+ *
+ * @param expiresIn the expiresIn
+ */
+ public void setExpiresIn(final int expiresIn) {
+ this.expiresIn = expiresIn;
+ }
+
+ /**
+ * Gets scope.
+ *
+ * @return the scope
+ */
+ public String getScope() {
+ return scope;
+ }
+
+ /**
+ * Sets scope.
+ *
+ * @param scope the scope
+ */
+ public void setScope(final String scope) {
+ this.scope = scope;
+ }
+
+ /**
+ * Gets idToken.
+ *
+ * @return the idToken
+ */
+ public String getIdToken() {
+ return idToken;
+ }
+
+ /**
+ * Sets idToken.
+ *
+ * @param idToken the idToken
+ */
+ public void setIdToken(final String idToken) {
+ this.idToken = idToken;
+ }
+
+ @Override
+ public String toString() {
+ return "OIDCToken{"
+ + "accessToken='" + accessToken + '\''
+ + ", tokenType='" + tokenType + '\''
+ + ", refreshToken='" + refreshToken + '\''
+ + ", expiresIn=" + expiresIn
+ + ", scope='" + scope + '\''
+ + ", idToken='" + idToken + '\''
+ + '}';
+ }
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/MaxkeyPluginTest.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/MaxkeyPluginTest.java
new file mode 100644
index 00000000..ce48bd9a
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/MaxkeyPluginTest.java
@@ -0,0 +1,175 @@
+/*
+ * 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.shenyu.plugin.maxkey;
+
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.dto.RuleData;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.Singleton;
+import org.apache.shenyu.plugin.api.ShenyuPluginChain;
+import org.apache.shenyu.plugin.api.result.DefaultShenyuResult;
+import org.apache.shenyu.plugin.api.result.ShenyuResult;
+import org.apache.shenyu.plugin.api.utils.SpringBeanUtils;
+import org.apache.shenyu.plugin.maxkey.config.MaxkeyConfig;
+import org.apache.shenyu.plugin.maxkey.handle.MaxkeyPluginDataHandler;
+import org.apache.shenyu.plugin.maxkey.service.MaxkeyService;
+import org.apache.shenyu.plugin.maxkey.service.MaxkeyUser;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Assumptions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.Spy;
+import org.mockito.Mockito;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.http.HttpHeaders;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.web.server.ServerWebExchange;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class MaxkeyPluginTest {
+
+ @Spy
+ private MaxKeyPlugin maxkeyPluginTest;
+
+ @Spy
+ private MaxkeyPluginDataHandler maxkeyPluginDataHandlerTest;
+
+ private ServerWebExchange exchange;
+
+ @Mock
+ private ShenyuPluginChain chain;
+
+ @Mock
+ private SelectorData selector;
+
+ @Mock
+ private RuleData rule;
+
+ @BeforeEach
+ void setup() {
+ ConfigurableApplicationContext context = mock(ConfigurableApplicationContext.class);
+ when(context.getBean(ShenyuResult.class)).thenReturn(new DefaultShenyuResult());
+ SpringBeanUtils springBeanUtils = SpringBeanUtils.getInstance();
+ springBeanUtils.setApplicationContext(context);
+ MockitoAnnotations.openMocks(this);
+ // 模拟请求
+ exchange = MockServerWebExchange
+ .from(MockServerHttpRequest
+ .get("localhost")
+ .header(HttpHeaders.AUTHORIZATION, "25c8d6a6-ad3a-4767-8bfb-d80641b8dfdc")
+ .build());
+ }
+
+ @Test
+ void doExecute() {
+ final PluginData pluginData = new PluginData(
+ "pluginId",
+ "pluginName",
+ "{\n"
+ + "\"clientId\": \"ae20330a-ef0b-4dad-9f10-d5e3485ca2ad\",\n"
+ + "\"clientSecret\": \"KQY4MDUwNjIwMjAxNTE3NTM1OTEYty\",\n"
+ + "\"authorizationEndpoint\": \"http://192.168.1.16/sign/authz/oauth/v20/authorize\",\n"
+ + "\"scope\": \"openid\",\n"
+ + "\"responseType\": \"code\",\n"
+ + "\"redirectUrl\": \"http://192.168.1.5:9195/http/shenyu/client/hello\",\n"
+ + "\"realm\": \"1\",\n"
+ + "\"grantType\": \"authorization_code\",\n"
+ + "\"tokenEndpoint\": \"http://192.168.1.16/sign/authz/oauth/v20/token\",\n"
+ + "\"bearerOnly\": \"true\",\n"
+ + "\"introspectionEndpoint\": \"http://192.168.1.16/sign/authz/oauth/v20/introspect\",\n"
+ + "\"setUserInfoHeader\": \"true\",\n"
+ + "\"userInfoEndpoint\": \"http://192.168.1.16/sign/api/connect/v10/userinfo\",\n"
+ + "\"introspectionEndpointAuthMethodsSupported\": \"client_secret_basic\",\n"
+ + "\"discovery\": \"http://192.168.1.16/sign/authz/oauth/v20/1/.well-known/openid-configuration\"\n"
+ + "}",
+ "0",
+ false,
+ null);
+ // 测试数据同步
+ maxkeyPluginDataHandlerTest.handlerPlugin(pluginData);
+
+ // 模拟服务
+ MaxkeyService maxkeyService = mock(MaxkeyService.class);
+ MaxkeyConfig maxkeyConfig = mock(MaxkeyConfig.class);
+ when(maxkeyService.getMaxkeyConfig()).thenReturn(maxkeyConfig);
+
+ exchange = MockServerWebExchange.from(MockServerHttpRequest
+ .get("localhost")
+ .queryParam("state", "state")
+ .queryParam("code", "code")
+ .header(HttpHeaders.AUTHORIZATION, "token")
+ .build());
+
+ // 先测试bearerOnly模式和getUserInfo模式
+ final String token = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
+ // 处理 MaxkeyUser
+ MaxkeyUser maxkeyUser = new MaxkeyUser();
+ maxkeyUser.setAddress(new MaxkeyUser.Address());
+ Mockito.when(maxkeyService.getMaxkeyUser(token)).thenReturn(maxkeyUser);
+
+ // 模拟 Maxkey认证服务执行
+ Singleton.INST.single(MaxkeyService.class, maxkeyService);
+ // 认证执行之后 返回一个异步任务
+ when(this.chain.execute(any())).thenReturn(Mono.empty());
+ Mono mono = maxkeyPluginTest.doExecute(exchange, chain, selector, rule);
+ StepVerifier.create(mono).expectSubscription().verifyComplete();
+
+ // 再测试code模式
+ maxkeyService = Singleton.INST.get(MaxkeyService.class);
+ maxkeyConfig = maxkeyService.getMaxkeyConfig();
+ maxkeyConfig.setBearerOnly(false);
+
+ exchange = MockServerWebExchange.from(MockServerHttpRequest
+ .get("localhost")
+ .queryParam("state", "state")
+ .queryParam("code", "code")
+ .build());
+ Mockito.when(maxkeyService.getOAuthToken("code")).thenReturn(token);
+ Singleton.INST.single(MaxkeyService.class, maxkeyService);
+ mono = maxkeyPluginTest.doExecute(exchange, chain, selector, rule);
+ StepVerifier.create(mono).expectSubscription().verifyComplete();
+ }
+
+ @Test
+ public void testNamed() {
+ final String result = maxkeyPluginTest.named();
+ Assertions.assertEquals(PluginEnum.MAXKEY.getName(), result);
+ }
+
+ @Test
+ public void testGetOrder() {
+ final int result = maxkeyPluginTest.getOrder();
+ Assertions.assertEquals(PluginEnum.MAXKEY.getCode(), result);
+ }
+
+ @Test
+ public void skipTest() {
+ Assumptions.assumeFalse(maxkeyPluginTest.skip(exchange));
+ }
+
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/config/MaxkeyConfigTest.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/config/MaxkeyConfigTest.java
new file mode 100644
index 00000000..cab558c0
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/config/MaxkeyConfigTest.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.shenyu.plugin.maxkey.config;
+
+import org.junit.jupiter.api.Test;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+
+class MaxkeyConfigTest {
+
+ @Test
+ public void maxkeyConfig() {
+
+ MaxkeyConfig maxkeyConfig = new MaxkeyConfig("a", "b", "c", "d", "e", "f", "g", "h", "i", false, "j", false, "k", "l", "m");
+ assertEquals("a", maxkeyConfig.getClientId());
+ assertEquals("b", maxkeyConfig.getClientSecret());
+ assertEquals("c", maxkeyConfig.getAuthorizationEndpoint());
+ assertEquals("d", maxkeyConfig.getScope());
+ assertEquals("e", maxkeyConfig.getResponseType());
+ assertEquals("f", maxkeyConfig.getRedirectUrl());
+ assertEquals("g", maxkeyConfig.getRealm());
+ assertEquals("h", maxkeyConfig.getGrantType());
+ assertEquals("i", maxkeyConfig.getTokenEndpoint());
+ assertFalse(maxkeyConfig.isBearerOnly());
+ assertEquals("j", maxkeyConfig.getIntrospectionEndpoint());
+ assertFalse(maxkeyConfig.isSetUserInfoHeader());
+ assertEquals("k", maxkeyConfig.getUserInfoEndpoint());
+ assertEquals("l", maxkeyConfig.getIntrospectionEndpointAuthMethodsSupported());
+ assertEquals("m", maxkeyConfig.getDiscovery());
+
+ MaxkeyConfig maxkeyConfig1 = new MaxkeyConfig();
+ maxkeyConfig1.setClientId("a");
+ maxkeyConfig1.setClientSecret("b");
+ maxkeyConfig1.setAuthorizationEndpoint("c");
+ maxkeyConfig1.setScope("d");
+ maxkeyConfig1.setResponseType("e");
+ maxkeyConfig1.setRedirectUrl("f");
+ maxkeyConfig1.setRealm("g");
+ maxkeyConfig1.setGrantType("h");
+ maxkeyConfig1.setTokenEndpoint("i");
+ maxkeyConfig1.setBearerOnly(false);
+ maxkeyConfig1.setIntrospectionEndpoint("j");
+ maxkeyConfig1.setSetUserInfoHeader(false);
+ maxkeyConfig1.setUserInfoEndpoint("k");
+ maxkeyConfig1.setIntrospectionEndpointAuthMethodsSupported("l");
+ maxkeyConfig1.setDiscovery("m");
+ assertEquals("a", maxkeyConfig.getClientId());
+ assertEquals("b", maxkeyConfig.getClientSecret());
+ assertEquals("c", maxkeyConfig.getAuthorizationEndpoint());
+ assertEquals("d", maxkeyConfig.getScope());
+ assertEquals("e", maxkeyConfig.getResponseType());
+ assertEquals("f", maxkeyConfig.getRedirectUrl());
+ assertEquals("g", maxkeyConfig.getRealm());
+ assertEquals("h", maxkeyConfig.getGrantType());
+ assertEquals("i", maxkeyConfig.getTokenEndpoint());
+ assertFalse(maxkeyConfig.isBearerOnly());
+ assertEquals("j", maxkeyConfig.getIntrospectionEndpoint());
+ assertFalse(maxkeyConfig.isSetUserInfoHeader());
+ assertEquals("k", maxkeyConfig.getUserInfoEndpoint());
+ assertEquals("l", maxkeyConfig.getIntrospectionEndpointAuthMethodsSupported());
+ assertEquals("m", maxkeyConfig.getDiscovery());
+ assertEquals(
+ "MaxkeyConfig{clientId='a', "
+ + "clientSecret='b', "
+ + "authorizationEndpoint='c', "
+ + "scope='d', "
+ + "responseType='e', "
+ + "redirectUrl='f', "
+ + "realm='g', "
+ + "grantType='h', "
+ + "tokenEndpoint='i', "
+ + "bearerOnly=false, "
+ + "introspectionEndpoint='j', "
+ + "setUserInfoHeader=false, "
+ + "userInfoEndpoint='k', "
+ + "introspectionEndpointAuthMethodsSupported='l', "
+ + "discovery='m'}",
+ maxkeyConfig1.toString());
+ }
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/handle/MaxkeyPluginDataHandlerTest.java b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/handle/MaxkeyPluginDataHandlerTest.java
new file mode 100644
index 00000000..a32ad247
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-plugin-maxkey/src/test/java/org/apache/shenyu/plugin/maxkey/handle/MaxkeyPluginDataHandlerTest.java
@@ -0,0 +1,72 @@
+/*
+ * 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.shenyu.plugin.maxkey.handle;
+
+import org.apache.shenyu.common.dto.PluginData;
+import org.apache.shenyu.common.enums.PluginEnum;
+import org.apache.shenyu.common.utils.Singleton;
+import org.apache.shenyu.plugin.maxkey.service.MaxkeyService;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class MaxkeyPluginDataHandlerTest {
+
+ private MaxkeyPluginDataHandler maxkeyPluginDataHandlerTest;
+
+ @BeforeEach
+ public void setup() {
+ maxkeyPluginDataHandlerTest = new MaxkeyPluginDataHandler();
+ }
+
+ @Test
+ public void handlerPlugin() {
+ final PluginData pluginData = new PluginData(
+ "pluginId",
+ "pluginName",
+ "{\n"
+ + "\t\"clientId\": \"ae20330a-ef0b-4dad-9f10-d5e3485ca2ad\",\n"
+ + "\t\"clientSecret\": \"KQY4MDUwNjIwMjAxNTE3NTM1OTEYty\",\n"
+ + "\t\"authorizationEndpoint\": \"http://192.168.1.16/sign/authz/oauth/v20/authorize\",\n"
+ + "\t\"scope\": \"openid\",\n"
+ + "\t\"responseType\": \"code\",\n"
+ + "\t\"redirectUrl\": \"http://192.168.1.5:9195/http/shenyu/client/hello\",\n"
+ + "\t\"realm\": \"1\",\n"
+ + "\t\"grantType\": \"authorization_code\",\n"
+ + "\t\"tokenEndpoint\": \"http://192.168.1.16/sign/authz/oauth/v20/token\",\n"
+ + "\t\"bearerOnly\": \"false\",\n"
+ + "\t\"introspectionEndpoint\": \"http://192.168.1.16/sign/authz/oauth/v20/introspect\",\n"
+ + "\t\"setUserInfoHeader\": \"false\",\n"
+ + "\t\"userInfoEndpoint\": \"http://192.168.1.16/sign/api/connect/v10/userinfo\",\n"
+ + "\t\"introspectionEndpointAuthMethodsSupported\": \"client_secret_basic\",\n"
+ + "\t\"discovery\": \"http://192.168.1.16/sign/authz/oauth/v20/1/.well-known/openid-configuration\"\n"
+ + "}\n",
+ "0",
+ false,
+ null);
+ maxkeyPluginDataHandlerTest.handlerPlugin(pluginData);
+ MaxkeyService maxkeyService = Singleton.INST.get(MaxkeyService.class);
+ }
+
+ @Test
+ public void testPluginNamed() {
+ final String result = maxkeyPluginDataHandlerTest.pluginNamed();
+ assertEquals(PluginEnum.MAXKEY.getName(), result);
+ }
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/pom.xml b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/pom.xml
new file mode 100644
index 00000000..880faec4
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/pom.xml
@@ -0,0 +1,34 @@
+
+
+
+ shenyu-spring-boot-starter-plugin
+ org.apache.shenyu
+ 2.6.0-SNAPSHOT
+
+ 4.0.0
+
+ shenyu-spring-boot-starter-plugin-maxkey
+
+
+
+ org.springframework.boot
+ spring-boot-autoconfigure
+
+
+
+ org.apache.shenyu
+ shenyu-plugin-maxkey
+ ${project.version}
+
+
+
+ org.springframework.security
+ spring-security-oauth2-client
+ ${spring-security.version}
+ true
+
+
+
+
\ No newline at end of file
diff --git a/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/java/org/apache/shenyu/springboot/starter/plugin/maxkey/MaxkeyPluginConfiguration.java b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/java/org/apache/shenyu/springboot/starter/plugin/maxkey/MaxkeyPluginConfiguration.java
new file mode 100644
index 00000000..0725ae15
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/java/org/apache/shenyu/springboot/starter/plugin/maxkey/MaxkeyPluginConfiguration.java
@@ -0,0 +1,62 @@
+/*
+ * 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.shenyu.springboot.starter.plugin.maxkey;
+
+import org.apache.shenyu.plugin.api.ShenyuPlugin;
+import org.apache.shenyu.plugin.base.handler.PluginDataHandler;
+import org.apache.shenyu.plugin.maxkey.MaxKeyPlugin;
+import org.apache.shenyu.plugin.maxkey.config.MaxkeyConfig;
+import org.apache.shenyu.plugin.maxkey.handle.MaxkeyPluginDataHandler;
+import org.apache.shenyu.plugin.maxkey.service.MaxkeyService;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConditionalOnProperty(value = {"shenyu.plugins.maxkey.enabled"}, havingValue = "true", matchIfMissing = true)
+public class MaxkeyPluginConfiguration {
+
+ /**
+ * the maxkey plugin.
+ * @return the shenyu plugin
+ */
+ @Bean
+ public ShenyuPlugin maxkeyPlugin() {
+ return new MaxKeyPlugin();
+ }
+
+ /**
+ * Maxkey plugin data handler.
+ *
+ * @return the plugin data handler
+ */
+ @Bean
+ public PluginDataHandler maxkeyPluginDataHandler() {
+ return new MaxkeyPluginDataHandler();
+ }
+
+ /**
+ * Maxkey plugin data handler.
+ *
+ * @return the plugin data handler
+ */
+ @Bean
+ public MaxkeyService maxkeyService() {
+ return new MaxkeyService(new MaxkeyConfig());
+ }
+}
diff --git a/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/resources/META-INF/spring.factories b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000..1aa8887d
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
+org.apache.shenyu.springboot.starter.plugin.maxkey.MaxkeyPluginConfiguration
\ No newline at end of file
diff --git a/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/resources/META-INF/spring.provides b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/resources/META-INF/spring.provides
new file mode 100644
index 00000000..dfdd3009
--- /dev/null
+++ b/summer-ospp/2023/shenyu/shenyu-spring-boot-starter-plugin-maxkey/src/main/resources/META-INF/spring.provides
@@ -0,0 +1,18 @@
+#
+# 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.
+#
+
+provides: shenyu-spring-boot-starter-plugin-maxkey