Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

optimize: add secure authentication to interfaces in ClusterController #6042

Merged
merged 35 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6a0a0f3
add secure authentication to interfaces in ClusterController
ggbocoder Nov 17, 2023
6438318
fix codestyle
ggbocoder Nov 17, 2023
7ab84ae
fix codestyle
ggbocoder Nov 17, 2023
abbc2b2
fix
ggbocoder Nov 17, 2023
89adfb3
Merge branch '2.x' into 6012
funky-eyes Nov 17, 2023
34f30e8
use ttl to validate token expired time
ggbocoder Nov 17, 2023
62255d0
Merge remote-tracking branch 'origin/6012' into 6012
ggbocoder Nov 17, 2023
a607b24
Merge branch '2.x' into 6012
ggbocoder Nov 19, 2023
fff0c99
resolve conflicts
ggbocoder Nov 19, 2023
3a941fd
fix
ggbocoder Nov 19, 2023
aa378c9
opt
ggbocoder Nov 21, 2023
d8d3987
Merge branch '2.x' into 6012
funky-eyes Nov 24, 2023
623e43e
fix
ggbocoder Nov 26, 2023
6df8539
Merge branch '2.x' into 6012
funky-eyes Nov 26, 2023
b8a6a7f
Update discovery/seata-discovery-raft/src/main/java/io/seata/discover…
ggbocoder Nov 26, 2023
412700b
Update discovery/seata-discovery-raft/src/main/java/io/seata/discover…
ggbocoder Nov 26, 2023
47680da
Update discovery/seata-discovery-raft/src/main/java/io/seata/discover…
ggbocoder Nov 26, 2023
f8a3d3d
Update discovery/seata-discovery-raft/src/main/java/io/seata/discover…
ggbocoder Nov 26, 2023
509c0a2
add RetryableException
ggbocoder Nov 26, 2023
d9feaba
Merge remote-tracking branch 'origin/6012' into 6012
ggbocoder Nov 26, 2023
b062f88
Merge branch '2.x' into 6012
funky-eyes Nov 27, 2023
862dab2
Merge branch '2.x' into 6012
funky-eyes Nov 27, 2023
a2a2951
fix and add tests
ggbocoder Nov 27, 2023
733e92e
fix
ggbocoder Nov 27, 2023
4fe71b0
fix
ggbocoder Nov 27, 2023
58dd93c
fix
ggbocoder Nov 27, 2023
5f95f06
fix
ggbocoder Nov 27, 2023
631a9ad
fix
ggbocoder Nov 27, 2023
8143a9b
Update discovery/seata-discovery-raft/src/main/java/io/seata/discover…
ggbocoder Nov 28, 2023
3b4fe27
Update discovery/seata-discovery-raft/src/main/java/io/seata/discover…
ggbocoder Nov 28, 2023
129ab4f
Update discovery/seata-discovery-raft/src/main/java/io/seata/discover…
ggbocoder Nov 28, 2023
23109ee
simplify test config
ggbocoder Nov 28, 2023
de2ef50
simplify test config
ggbocoder Nov 28, 2023
4daf69a
fix
ggbocoder Nov 28, 2023
7db947e
Merge branch '2.x' into 6012
funky-eyes Nov 29, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
ggbocoder marked this conversation as resolved.
Show resolved Hide resolved
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadLocalRandom;
Expand All @@ -34,6 +35,7 @@
import java.util.stream.Stream;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.seata.common.ConfigurationKeys;
import io.seata.common.metadata.Metadata;
Expand All @@ -46,6 +48,7 @@
import io.seata.config.Configuration;
import io.seata.config.ConfigurationFactory;
import io.seata.discovery.registry.RegistryService;
import org.apache.http.Header;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.StatusLine;
Expand All @@ -71,13 +74,23 @@
* @author funkye
*/
public class RaftRegistryServiceImpl implements RegistryService<ConfigChangeListener> {

private static final Logger LOGGER = LoggerFactory.getLogger(RaftRegistryServiceImpl.class);

private static final String REGISTRY_TYPE = "raft";

private static final String PRO_SERVER_ADDR_KEY = "serverAddr";

private static final String PRO_USERNAME_KEY = "username";

private static final String PRO_PASSWORD_KEY = "password";

private static final String AUTHORIZATION_HEADER = "Authorization";

private static String jwtToken;

private static final String NEW_TOKEN_KEY = "new_token";

private static volatile RaftRegistryServiceImpl instance;

private static final Configuration CONFIG = ConfigurationFactory.getInstance();
Expand Down Expand Up @@ -113,7 +126,8 @@ public class RaftRegistryServiceImpl implements RegistryService<ConfigChangeList
POOLING_HTTP_CLIENT_CONNECTION_MANAGER.setDefaultMaxPerRoute(10);
}

private RaftRegistryServiceImpl() {}
private RaftRegistryServiceImpl() {
}

/**
* Gets instance.
Expand Down Expand Up @@ -244,6 +258,16 @@ private static String getRaftAddrFileKey() {
REGISTRY_TYPE, PRO_SERVER_ADDR_KEY);
}

private static String getRaftUserNameKey() {
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY,
REGISTRY_TYPE, PRO_USERNAME_KEY);
}

private static String getRaftPassWordKey() {
return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_REGISTRY,
REGISTRY_TYPE, PRO_PASSWORD_KEY);
}

private InetSocketAddress convertInetSocketAddress(Node node) {
Node.Endpoint endpoint = node.getTransaction();
return new InetSocketAddress(endpoint.getHost(), endpoint.getPort());
Expand All @@ -267,16 +291,29 @@ public List<InetSocketAddress> aliveLookup(String transactionServiceGroup) {
}

private static boolean watch() {
Map<String, String> header = new HashMap<>();
Map<String, String> param = new HashMap<>();
String clusterName = CURRENT_TRANSACTION_CLUSTER_NAME;
Map<String, Long> groupTerms = METADATA.getClusterTerm(clusterName);
groupTerms.forEach((k, v) -> param.put(k, String.valueOf(v)));
for (String group : groupTerms.keySet()) {
String tcAddress = queryHttpAddress(clusterName, group);
String token = getToken(tcAddress);
if (!Objects.isNull(token)) {
header.put(AUTHORIZATION_HEADER, token);
}
try (CloseableHttpResponse response =
doPost("http://" + tcAddress + "/metadata/v1/watch", param, null, 30000)) {
doPost("http://" + tcAddress + "/metadata/v1/watch", param, header, 30000, "application/x-www-form-urlencoded")) {
if (response != null) {
//refresh jwt token
Header tokenHeader = response.getFirstHeader(NEW_TOKEN_KEY);
if (tokenHeader != null) {
jwtToken = tokenHeader.getValue();
}
StatusLine statusLine = response.getStatusLine();
if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
LOGGER.error("Authentication failed! you should configure the correct username and password.");
}
return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK;
}
} catch (Exception e) {
Expand All @@ -294,7 +331,7 @@ private static boolean watch() {

@Override
public List<InetSocketAddress> refreshAliveLookup(String transactionServiceGroup,
List<InetSocketAddress> aliveAddress) {
List<InetSocketAddress> aliveAddress) {
if (METADATA.isRaftMode()) {
Node leader = METADATA.getLeader(getServiceGroup(transactionServiceGroup));
InetSocketAddress leaderAddress = convertInetSocketAddress(leader);
Expand All @@ -315,14 +352,28 @@ private static void acquireClusterMetaDataByClusterName(String clusterName) {

private static void acquireClusterMetaData(String clusterName, String group) {
String tcAddress = queryHttpAddress(clusterName, group);
Map<String, String> header = new HashMap<>();
String token = getToken(tcAddress);
if (!Objects.isNull(token)) {
funky-eyes marked this conversation as resolved.
Show resolved Hide resolved
header.put(AUTHORIZATION_HEADER, token);
}
if (StringUtils.isNotBlank(tcAddress)) {
Map<String, String> param = new HashMap<>();
param.put("group", group);
String response = null;
try (CloseableHttpResponse httpResponse =
doGet("http://" + tcAddress + "/metadata/v1/cluster", param, null, 1000)) {
if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8);
doGet("http://" + tcAddress + "/metadata/v1/cluster", param, header, 1000)) {
if (httpResponse != null) {
//refresh jwt token
Header newTokenHeader = httpResponse.getFirstHeader(NEW_TOKEN_KEY);
if (!Objects.isNull(newTokenHeader)) {
jwtToken = newTokenHeader.getValue();
}
if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8);
} else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) {
LOGGER.error("Authentication failed! you should configure the correct username and password.");
}
}
MetadataResponse metadataResponse;
if (StringUtils.isNotBlank(response)) {
Expand All @@ -339,8 +390,42 @@ private static void acquireClusterMetaData(String clusterName, String group) {
}
}

public static String getToken(String tcAddress) {
// if the token is present ,return it
if (!Objects.isNull(jwtToken)) {
return jwtToken;
}
// if username and password is not in config , return null
String username = CONFIG.getConfig(getRaftUserNameKey());
String password = CONFIG.getConfig(getRaftPassWordKey());
if (Objects.isNull(username) || Objects.isNull(password)) {
return null;
}
// get token and set it in cache
if (StringUtils.isNotBlank(tcAddress)) {
Map<String, String> param = new HashMap<>();
param.put(PRO_USERNAME_KEY, username);
param.put(PRO_PASSWORD_KEY, password);
String response = null;
try (CloseableHttpResponse httpResponse =
doPost("http://" + tcAddress + "/api/v1/auth/login", param, null, 1000, "application/json")) {
if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
funky-eyes marked this conversation as resolved.
Show resolved Hide resolved
response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8);
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(response);
jwtToken = jsonNode.get("data").asText();
}
} catch (IOException e) {
LOGGER.error(e.getMessage(), e);
}
}

return jwtToken;

}

public static CloseableHttpResponse doGet(String url, Map<String, String> param, Map<String, String> header,
int timeout) {
int timeout) {
CloseableHttpClient client;
try {
URIBuilder builder = new URIBuilder(url);
Expand All @@ -367,24 +452,35 @@ public static CloseableHttpResponse doGet(String url, Map<String, String> param,
}

public static CloseableHttpResponse doPost(String url, Map<String, String> params, Map<String, String> header,
int timeout) {
CloseableHttpClient client = null;
int timeout, String contentType) {
funky-eyes marked this conversation as resolved.
Show resolved Hide resolved
CloseableHttpClient client;
try {
URIBuilder builder = new URIBuilder(url);
URI uri = builder.build();
HttpPost httpPost = new HttpPost(uri);
if (header != null) {
header.forEach(httpPost::addHeader);
}
List<NameValuePair> nameValuePairs = new ArrayList<>();
params.forEach((k, v) -> {
nameValuePairs.add(new BasicNameValuePair(k, v));
});
String requestBody = URLEncodedUtils.format(nameValuePairs, StandardCharsets.UTF_8);

StringEntity stringEntity = new StringEntity(requestBody, ContentType.APPLICATION_FORM_URLENCODED);
httpPost.setEntity(stringEntity);
httpPost.setHeader("Content-Type", "application/x-www-form-urlencoded");
if (contentType != null) {
httpPost.setHeader("Content-Type", contentType);
}

if ("application/x-www-form-urlencoded".equals(contentType)) {
funky-eyes marked this conversation as resolved.
Show resolved Hide resolved
List<NameValuePair> nameValuePairs = new ArrayList<>();
params.forEach((k, v) -> {
nameValuePairs.add(new BasicNameValuePair(k, v));
});
String requestBody = URLEncodedUtils.format(nameValuePairs, StandardCharsets.UTF_8);

StringEntity stringEntity = new StringEntity(requestBody, ContentType.APPLICATION_FORM_URLENCODED);
httpPost.setEntity(stringEntity);
} else if ("application/json".equals(contentType)) {
ObjectMapper objectMapper = new ObjectMapper();
String requestBody = objectMapper.writeValueAsString(params);
StringEntity stringEntity = new StringEntity(requestBody, ContentType.APPLICATION_JSON);
httpPost.setEntity(stringEntity);
}

client = HTTP_CLIENT_MAP.computeIfAbsent(timeout,
k -> HttpClients.custom().setConnectionManager(POOLING_HTTP_CLIENT_CONNECTION_MANAGER)
.setDefaultRequestConfig(RequestConfig.custom().setConnectionRequestTimeout(timeout)
Expand Down
9 changes: 8 additions & 1 deletion script/client/conf/registry.conf
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,16 @@
# limitations under the License.

registry {
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom
# file 、nacos 、eureka、redis、zk、consul、etcd3、sofa、custom、raft
funky-eyes marked this conversation as resolved.
Show resolved Hide resolved
type = "file"

raft {
metadata-max-age-ms = 30000
serverAddr = "127.0.0.1:8848"
username = "seata"
password = "seata"
}

nacos {
application = "seata-server"
serverAddr = "127.0.0.1:8848"
Expand Down
2 changes: 2 additions & 0 deletions script/client/spring/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ seata.registry.type=file

seata.registry.raft.server-addr=
seata.registry.raft.metadata-max-age-ms=30000
seata.registry.raft.username=seata
seata.registry.raft.password=seata
seata.registry.consul.server-addr=127.0.0.1:8500

seata.registry.etcd3.server-addr=http://localhost:2379
Expand Down
2 changes: 2 additions & 0 deletions script/client/spring/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ seata:
raft:
server-addr:
metadata-max-age-ms: 30000
username: seata
password: seata
file:
name: file.conf
consul:
Expand Down
2 changes: 1 addition & 1 deletion server/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ seata:
secretKey: SeataSecretKey0c382ef121d778043159209298fd40bf3850a017
tokenValidityInMilliseconds: 1800000
ignore:
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login,/metadata/v1/**
urls: /,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.jpeg,/**/*.ico,/api/v1/auth/login
Loading