OSPP-2023_shenyu
This commit is contained in:
53
summer-ospp/2023/shenyu/shenyu-plugin-maxkey/pom.xml
Normal file
53
summer-ospp/2023/shenyu/shenyu-plugin-maxkey/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>shenyu-plugin-security</artifactId>
|
||||
<groupId>org.apache.shenyu</groupId>
|
||||
<version>2.6.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>shenyu-plugin-maxkey</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.apache.shenyu</groupId>
|
||||
<artifactId>shenyu-plugin-base</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.apache.oltu.oauth2</groupId>
|
||||
<artifactId>org.apache.oltu.oauth2.client</artifactId>
|
||||
<version>1.0.2</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
<version>5.8.16</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Security Test -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.security</groupId>
|
||||
<artifactId>spring-security-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
@@ -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<Void> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -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<String, String> 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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<Void> 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 + '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
||||
@@ -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<Void> 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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user