Skip to content

Commit dff3768

Browse files
committed
Added new child module 'services-security'
1 parent 37a5c17 commit dff3768

File tree

6 files changed

+229
-4
lines changed

6 files changed

+229
-4
lines changed

pom.xml

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<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/maven-v4_0_0.xsd">
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
35
<modelVersion>4.0.0</modelVersion>
46

57
<parent>
@@ -58,9 +60,10 @@
5860

5961
<properties>
6062
<scalecube-cluster.version>2.6.7.RC1</scalecube-cluster.version>
61-
<scalecube-commons.version>1.0.12</scalecube-commons.version>
62-
<reactor.version>2020.0.5</reactor.version>
63+
<scalecube-commons.version>1.0.13</scalecube-commons.version>
64+
<scalecube-security-tokens.version>1.0.16</scalecube-security-tokens.version>
6365

66+
<reactor.version>2020.0.5</reactor.version>
6467
<jackson.version>2.11.0</jackson.version>
6568
<rsocket.version>1.0.4</rsocket.version>
6669
<protostuff.version>1.6.0</protostuff.version>
@@ -77,11 +80,12 @@
7780
</properties>
7881

7982
<modules>
83+
<module>services</module>
8084
<module>services-api</module>
8185
<module>services-transport-parent</module>
8286
<module>services-discovery</module>
8387
<module>services-bytebuf-codec</module>
84-
<module>services</module>
88+
<module>services-security</module>
8589
<module>services-examples</module>
8690
</modules>
8791

@@ -95,6 +99,13 @@
9599
<version>${scalecube-commons.version}</version>
96100
</dependency>
97101

102+
<!-- Scalecube security tokens -->
103+
<dependency>
104+
<groupId>io.scalecube</groupId>
105+
<artifactId>scalecube-security-tokens</artifactId>
106+
<version>${scalecube-security-tokens.version}</version>
107+
</dependency>
108+
98109
<!-- Scalecube cluster -->
99110
<dependency>
100111
<groupId>io.scalecube</groupId>

services-security/pom.xml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.scalecube</groupId>
9+
<artifactId>scalecube-services-parent</artifactId>
10+
<version>2.10.13-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>scalecube-services-security</artifactId>
14+
15+
<dependencies>
16+
<dependency>
17+
<groupId>io.scalecube</groupId>
18+
<artifactId>scalecube-services</artifactId>
19+
<version>${project.version}</version>
20+
</dependency>
21+
<dependency>
22+
<groupId>io.scalecube</groupId>
23+
<artifactId>scalecube-security-tokens</artifactId>
24+
</dependency>
25+
</dependencies>
26+
27+
</project>
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package io.scalecube.services.security;
2+
3+
import java.io.ByteArrayInputStream;
4+
import java.io.ByteArrayOutputStream;
5+
import java.io.InputStream;
6+
import java.io.ObjectInputStream;
7+
import java.io.ObjectOutputStream;
8+
import java.io.OutputStream;
9+
import java.util.Collections;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
import java.util.Map.Entry;
13+
import java.util.Objects;
14+
import reactor.core.Exceptions;
15+
16+
public class Credentials {
17+
18+
/**
19+
* Encodes the given credentials to the given stream.
20+
*
21+
* @param stream stream
22+
* @param credentials credentials
23+
*/
24+
public static void encode(OutputStream stream, Map<String, String> credentials) {
25+
if (credentials == null) {
26+
return;
27+
}
28+
Objects.requireNonNull(stream, "output stream");
29+
try (ObjectOutputStream out = new ObjectOutputStream(stream)) {
30+
// credentials
31+
out.writeInt(credentials.size());
32+
for (Entry<String, String> entry : credentials.entrySet()) {
33+
out.writeUTF(entry.getKey());
34+
out.writeObject(entry.getValue()); // value is nullable
35+
}
36+
37+
out.flush();
38+
} catch (Throwable th) {
39+
throw Exceptions.propagate(th);
40+
}
41+
}
42+
43+
/**
44+
* Encodes the given credentials to a byte array.
45+
*
46+
* @param credentials credentials
47+
* @return byte array representation of credentials
48+
*/
49+
public static byte[] toByteArray(Map<String, String> credentials) {
50+
if (credentials == null || credentials.isEmpty()) {
51+
return new byte[0];
52+
}
53+
ByteArrayOutputStream output = new ByteArrayOutputStream();
54+
encode(output, credentials);
55+
return output.toByteArray();
56+
}
57+
58+
/**
59+
* Decodes the given stream to credentials as {@code Map<String, String>}.
60+
*
61+
* @return credentials
62+
*/
63+
public static Map<String, String> decode(InputStream stream) {
64+
Objects.requireNonNull(stream, "input stream");
65+
try (ObjectInputStream in = new ObjectInputStream(stream)) {
66+
// credentials
67+
int credentialsSize = in.readInt();
68+
Map<String, String> credentials = new HashMap<>(credentialsSize);
69+
for (int i = 0; i < credentialsSize; i++) {
70+
String key = in.readUTF();
71+
String value = (String) in.readObject(); // value is nullable
72+
credentials.put(key, value);
73+
}
74+
return Collections.unmodifiableMap(credentials);
75+
} catch (Throwable th) {
76+
throw Exceptions.propagate(th);
77+
}
78+
}
79+
80+
/**
81+
* Decodes the given byte array to credentials as {@code Map<String, String>}.
82+
*
83+
* @return credentials
84+
*/
85+
public static Map<String, String> decode(byte[] bytes) {
86+
if (bytes == null || bytes.length == 0) {
87+
return Collections.emptyMap();
88+
}
89+
ByteArrayInputStream input = new ByteArrayInputStream(bytes);
90+
return decode(input);
91+
}
92+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.scalecube.services.security;
2+
3+
import java.util.StringJoiner;
4+
5+
public final class ServiceClaims {
6+
7+
private final String permissions;
8+
9+
public ServiceClaims(String permissions) {
10+
this.permissions = permissions;
11+
}
12+
13+
public String permissions() {
14+
return permissions;
15+
}
16+
17+
@Override
18+
public String toString() {
19+
return new StringJoiner(", ", ServiceClaims.class.getSimpleName() + "[", "]")
20+
.add("permissions='" + permissions + "'")
21+
.toString();
22+
}
23+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.scalecube.services.security;
2+
3+
import io.scalecube.security.tokens.jwt.JwtTokenResolver;
4+
import io.scalecube.services.auth.Authenticator;
5+
import io.scalecube.services.exceptions.UnauthorizedException;
6+
import java.util.Map;
7+
import org.slf4j.Logger;
8+
import org.slf4j.LoggerFactory;
9+
import reactor.core.publisher.Mono;
10+
import reactor.util.retry.Retry;
11+
12+
/**
13+
* Generic {@link Authenticator} implementation based on abstract {@link JwtTokenResolver}. Using
14+
* token resolver this authenticator turns extracted and verified token claims into the {@link
15+
* ServiceClaims} object.
16+
*/
17+
public final class ServiceTokenAuthenticator implements Authenticator<ServiceClaims> {
18+
19+
private static final Logger LOGGER = LoggerFactory.getLogger(ServiceTokenAuthenticator.class);
20+
21+
private final JwtTokenResolver tokenResolver;
22+
private final Retry retryStrategy;
23+
24+
public ServiceTokenAuthenticator(JwtTokenResolver tokenResolver) {
25+
this(tokenResolver, Retry.max(0));
26+
}
27+
28+
public ServiceTokenAuthenticator(JwtTokenResolver tokenResolver, Retry retryStrategy) {
29+
this.tokenResolver = tokenResolver;
30+
this.retryStrategy = retryStrategy;
31+
}
32+
33+
@Override
34+
public Mono<ServiceClaims> apply(Map<String, String> credentials) {
35+
return Mono.defer(
36+
() -> {
37+
String serviceToken = credentials.get(ServiceTokens.SERVICE_TOKEN_HEADER);
38+
39+
if (serviceToken == null) {
40+
throw new UnauthorizedException("Authentication failed");
41+
}
42+
43+
return tokenResolver
44+
.resolve(serviceToken)
45+
.map(ServiceTokenAuthenticator::toServiceClaims)
46+
.retryWhen(retryStrategy)
47+
.doOnError(th -> LOGGER.error("Failed to authenticate, cause: {}", th.toString()))
48+
.onErrorMap(th -> new UnauthorizedException("Authentication failed"))
49+
.switchIfEmpty(Mono.error(new UnauthorizedException("Authentication failed")));
50+
});
51+
}
52+
53+
private static ServiceClaims toServiceClaims(Map<String, Object> authData) {
54+
String permissionsClaim = (String) authData.get(ServiceTokens.PERMISSIONS_CLAIM);
55+
if (permissionsClaim == null || permissionsClaim.isEmpty()) {
56+
throw new UnauthorizedException("Authentication failed");
57+
}
58+
return new ServiceClaims(permissionsClaim);
59+
}
60+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package io.scalecube.services.security;
2+
3+
public final class ServiceTokens {
4+
5+
public static final String SERVICE_TOKEN_HEADER = "serviceToken";
6+
public static final String PERMISSIONS_CLAIM = "permissions";
7+
public static final String LOCAL_TOKEN_HEADER = "localToken";
8+
9+
private ServiceTokens() {
10+
// Do not instantiate
11+
}
12+
}

0 commit comments

Comments
 (0)