Skip to content

Commit b3c7809

Browse files
authored
Merge pull request #824 from scalecube/refactor-authentication-internals
Enhanced ServiceMethodInvoker: added support of composite auth context
2 parents 12e01c3 + 4368e1e commit b3c7809

File tree

11 files changed

+280
-55
lines changed

11 files changed

+280
-55
lines changed

services-api/src/main/java/io/scalecube/services/methods/ServiceMethodInvoker.java

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,17 @@ public Flux<ServiceMessage> invokeBidirectional(Publisher<ServiceMessage> publis
113113

114114
private Mono<?> deferWithContextOne(ServiceMessage message, Object authData) {
115115
return Mono.deferContextual(context -> Mono.from(invoke(toRequest(message))))
116-
.contextWrite(context -> enhanceContextWithPrincipal(authData, context));
116+
.contextWrite(context -> enhanceContext(authData, context));
117117
}
118118

119119
private Flux<?> deferWithContextMany(ServiceMessage message, Object authData) {
120120
return Flux.deferContextual(context -> Flux.from(invoke(toRequest(message))))
121-
.contextWrite(context -> enhanceContextWithPrincipal(authData, context));
121+
.contextWrite(context -> enhanceContext(authData, context));
122122
}
123123

124124
private Flux<?> deferWithContextBidirectional(Flux<ServiceMessage> messages, Object authData) {
125125
return Flux.deferContextual(context -> messages.map(this::toRequest).transform(this::invoke))
126-
.contextWrite(context -> enhanceContextWithPrincipal(authData, context));
126+
.contextWrite(context -> enhanceContext(authData, context));
127127
}
128128

129129
private Publisher<?> invoke(Object request) {
@@ -160,16 +160,19 @@ private Mono<Object> authenticate(ServiceMessage message, Context context) {
160160
return Mono.just(NULL_AUTH_CONTEXT);
161161
}
162162

163-
if (context.hasKey(AUTH_CONTEXT_KEY)) {
164-
return Mono.just(context.get(AUTH_CONTEXT_KEY));
165-
}
166-
167163
if (authenticator == null) {
168-
LOGGER.error("Authentication failed (auth context not found and authenticator not set)");
169-
throw new UnauthorizedException("Authentication failed");
164+
if (context.hasKey(AUTH_CONTEXT_KEY)) {
165+
return Mono.just(context.get(AUTH_CONTEXT_KEY));
166+
} else {
167+
LOGGER.error("Authentication failed (auth context not found and authenticator not set)");
168+
throw new UnauthorizedException("Authentication failed");
169+
}
170170
}
171171

172-
return authenticator.apply(message.headers()).onErrorMap(this::toUnauthorizedException);
172+
return authenticator
173+
.apply(message.headers())
174+
.switchIfEmpty(Mono.just(NULL_AUTH_CONTEXT))
175+
.onErrorMap(this::toUnauthorizedException);
173176
}
174177

175178
private UnauthorizedException toUnauthorizedException(Throwable th) {
@@ -181,12 +184,14 @@ private UnauthorizedException toUnauthorizedException(Throwable th) {
181184
}
182185
}
183186

184-
private Context enhanceContextWithPrincipal(Object authData, Context context) {
185-
if (authData == NULL_AUTH_CONTEXT || authData == null) {
186-
return context;
187+
private Context enhanceContext(Object authData, Context context) {
188+
if (authData == NULL_AUTH_CONTEXT || principalMapper == null) {
189+
return context.put(AUTH_CONTEXT_KEY, authData);
187190
}
188-
return context.put(
189-
AUTH_CONTEXT_KEY, principalMapper != null ? principalMapper.apply(authData) : authData);
191+
192+
Object mappedData = principalMapper.apply(authData);
193+
194+
return context.put(AUTH_CONTEXT_KEY, mappedData != null ? mappedData : NULL_AUTH_CONTEXT);
190195
}
191196

192197
private Object toRequest(ServiceMessage message) {
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import java.util.StringJoiner;
4+
5+
public class CompositeProfile {
6+
7+
private final ServiceEndpointProfile serviceEndpointProfile;
8+
private final UserProfile userProfile;
9+
10+
public CompositeProfile(
11+
ServiceEndpointProfile serviceEndpointProfile, UserProfile userProfile) {
12+
this.serviceEndpointProfile = serviceEndpointProfile;
13+
this.userProfile = userProfile;
14+
}
15+
16+
public ServiceEndpointProfile serviceEndpointProfile() {
17+
return serviceEndpointProfile;
18+
}
19+
20+
public UserProfile userProfile() {
21+
return userProfile;
22+
}
23+
24+
@Override
25+
public String toString() {
26+
return new StringJoiner(", ", CompositeProfile.class.getSimpleName() + "[", "]")
27+
.add("serviceEndpointProfile=" + serviceEndpointProfile)
28+
.add("userProfile=" + userProfile)
29+
.toString();
30+
}
31+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import static io.scalecube.services.auth.MonoAuthUtil.deferWithPrincipal;
4+
5+
import io.scalecube.services.Microservices;
6+
import io.scalecube.services.ServiceEndpoint;
7+
import io.scalecube.services.ServiceInfo;
8+
import io.scalecube.services.api.ServiceMessage;
9+
import io.scalecube.services.auth.Authenticator;
10+
import io.scalecube.services.auth.CredentialsSupplier;
11+
import io.scalecube.services.discovery.ScalecubeServiceDiscovery;
12+
import io.scalecube.services.exceptions.UnauthorizedException;
13+
import io.scalecube.services.transport.rsocket.RSocketServiceTransport;
14+
import io.scalecube.transport.netty.websocket.WebsocketTransportFactory;
15+
import java.time.Duration;
16+
import java.util.Collections;
17+
import reactor.core.publisher.Mono;
18+
19+
public class CompositeProfileAuthExample {
20+
21+
/**
22+
* Main program.
23+
*
24+
* @param args arguments
25+
*/
26+
public static void main(String[] args) {
27+
Microservices service =
28+
Microservices.builder()
29+
.discovery(
30+
serviceEndpoint ->
31+
new ScalecubeServiceDiscovery()
32+
.transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory()))
33+
.options(opts -> opts.metadata(serviceEndpoint)))
34+
.transport(() -> new RSocketServiceTransport().authenticator(authenticator()))
35+
.services(
36+
call ->
37+
Collections.singletonList(
38+
ServiceInfo.fromServiceInstance(new SecuredServiceByCompositeProfileImpl())
39+
.authenticator(compositeAuthenticator())
40+
.build()))
41+
.startAwait();
42+
43+
Microservices caller =
44+
Microservices.builder()
45+
.discovery(endpoint -> discovery(service, endpoint))
46+
.transport(
47+
() -> new RSocketServiceTransport().credentialsSupplier(credentialsSupplier()))
48+
.startAwait();
49+
50+
ServiceMessage response =
51+
caller
52+
.call()
53+
.requestOne(
54+
ServiceMessage.builder()
55+
.qualifier("securedServiceByCompositeProfile/hello")
56+
.header("userProfile.name", "SEGA")
57+
.header("userProfile.role", "ADMIN")
58+
.data("hello world")
59+
.build(),
60+
String.class)
61+
.block(Duration.ofSeconds(3));
62+
63+
System.err.println("### Received 'caller' response: " + response.data());
64+
}
65+
66+
private static Authenticator<ServiceEndpointProfile> authenticator() {
67+
return headers -> {
68+
String transportSessionKey = headers.get("transportSessionKey");
69+
70+
if ("asdf7hasd9hasd7fha8ds7fahsdf87".equals(transportSessionKey)) {
71+
return Mono.just(new ServiceEndpointProfile("endpoint123", "operations"));
72+
}
73+
74+
return Mono.error(
75+
new UnauthorizedException("Authentication failed (transportSessionKey incorrect)"));
76+
};
77+
}
78+
79+
private static CredentialsSupplier credentialsSupplier() {
80+
return service ->
81+
Mono.just(
82+
Collections.singletonMap("transportSessionKey", "asdf7hasd9hasd7fha8ds7fahsdf87"));
83+
}
84+
85+
private static Authenticator<CompositeProfile> compositeAuthenticator() {
86+
return headers ->
87+
deferWithPrincipal(ServiceEndpointProfile.class)
88+
.flatMap(
89+
serviceEndpointProfile -> {
90+
91+
// If userProfile not set then throw error
92+
if (!headers.containsKey("userProfile.name")
93+
|| !headers.containsKey("userProfile.role")) {
94+
throw new UnauthorizedException("userProfile not found or invalid");
95+
}
96+
97+
// Otherwise return new combined profile which will be stored under
98+
// AUTH_CONTEXT_KEY
99+
100+
return Mono.just(
101+
new CompositeProfile(
102+
serviceEndpointProfile, UserProfile.fromHeaders(headers)));
103+
});
104+
}
105+
106+
private static ScalecubeServiceDiscovery discovery(
107+
Microservices service, ServiceEndpoint endpoint) {
108+
return new ScalecubeServiceDiscovery()
109+
.transport(cfg -> cfg.transportFactory(new WebsocketTransportFactory()))
110+
.options(opts -> opts.metadata(endpoint))
111+
.membership(opts -> opts.seedMembers(service.discovery().address()));
112+
}
113+
}

services-examples/src/main/java/io/scalecube/services/examples/auth/SecuredServiceByApiKey.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import reactor.core.publisher.Mono;
77

88
@Secured
9-
@Service
9+
@Service("securedServiceByApiKey")
1010
public interface SecuredServiceByApiKey {
1111

1212
@ServiceMethod
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import io.scalecube.services.annotations.Service;
4+
import io.scalecube.services.annotations.ServiceMethod;
5+
import io.scalecube.services.auth.Secured;
6+
import reactor.core.publisher.Mono;
7+
8+
@Secured
9+
@Service("securedServiceByCompositeProfile")
10+
public interface SecuredServiceByCompositeProfile {
11+
12+
@ServiceMethod
13+
Mono<String> hello(String name);
14+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import io.scalecube.services.auth.MonoAuthUtil;
4+
import io.scalecube.services.exceptions.ForbiddenException;
5+
import reactor.core.publisher.Mono;
6+
7+
public class SecuredServiceByCompositeProfileImpl implements SecuredServiceByCompositeProfile {
8+
9+
@Override
10+
public Mono<String> hello(String name) {
11+
return MonoAuthUtil.deferWithPrincipal(CompositeProfile.class)
12+
.flatMap(
13+
compositeProfile -> {
14+
final UserProfile userProfile = compositeProfile.userProfile();
15+
final ServiceEndpointProfile serviceEndpointProfile =
16+
compositeProfile.serviceEndpointProfile();
17+
checkPermissions(userProfile);
18+
return Mono.just(
19+
"Hello, name="
20+
+ name
21+
+ " (userProfile="
22+
+ userProfile
23+
+ ", serviceEndpointProfile="
24+
+ serviceEndpointProfile
25+
+ ")");
26+
});
27+
}
28+
29+
private void checkPermissions(UserProfile user) {
30+
if (!user.role().equals("ADMIN")) {
31+
throw new ForbiddenException("Forbidden");
32+
}
33+
}
34+
}

services-examples/src/main/java/io/scalecube/services/examples/auth/SecuredServiceByUserProfile.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import reactor.core.publisher.Mono;
77

88
@Secured
9-
@Service
9+
@Service("securedServiceByUserProfile")
1010
public interface SecuredServiceByUserProfile {
1111

1212
@ServiceMethod
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package io.scalecube.services.examples.auth;
2+
3+
import java.util.StringJoiner;
4+
5+
public class ServiceEndpointProfile {
6+
7+
private final String endpoint;
8+
private final String serviceRole;
9+
10+
public ServiceEndpointProfile(String endpoint, String serviceRole) {
11+
this.endpoint = endpoint;
12+
this.serviceRole = serviceRole;
13+
}
14+
15+
public String endpoint() {
16+
return endpoint;
17+
}
18+
19+
public String serviceRole() {
20+
return serviceRole;
21+
}
22+
23+
@Override
24+
public String toString() {
25+
return new StringJoiner(", ", ServiceEndpointProfile.class.getSimpleName() + "[", "]")
26+
.add("endpoint='" + endpoint + "'")
27+
.add("serviceRole='" + serviceRole + "'")
28+
.toString();
29+
}
30+
}

services-examples/src/main/java/io/scalecube/services/examples/auth/ServiceTransportAuthExample.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ public static void main(String[] args) {
3535
Microservices caller =
3636
Microservices.builder()
3737
.discovery(endpoint -> discovery(service, endpoint))
38-
.transport(() -> new RSocketServiceTransport().credentialsSupplier(credsSupplier()))
38+
.transport(
39+
() -> new RSocketServiceTransport().credentialsSupplier(credentialsSupplier()))
3940
.startAwait();
4041

4142
String response =
@@ -65,7 +66,7 @@ private static Authenticator<UserProfile> authenticator() {
6566
};
6667
}
6768

68-
private static CredentialsSupplier credsSupplier() {
69+
private static CredentialsSupplier credentialsSupplier() {
6970
return service -> {
7071
HashMap<String, String> creds = new HashMap<>();
7172
creds.put("username", "Alice");

services-examples/src/main/java/io/scalecube/services/examples/auth/UserProfile.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package io.scalecube.services.examples.auth;
22

3+
import java.util.Map;
4+
import java.util.Objects;
35
import java.util.StringJoiner;
46

57
public class UserProfile {
@@ -8,8 +10,12 @@ public class UserProfile {
810
private final String role;
911

1012
public UserProfile(String name, String role) {
11-
this.name = name;
12-
this.role = role;
13+
this.name = Objects.requireNonNull(name, "UserProfile.name");
14+
this.role = Objects.requireNonNull(role, "UserProfile.role");
15+
}
16+
17+
public static UserProfile fromHeaders(Map<String, String> headers) {
18+
return new UserProfile(headers.get("userProfile.name"), headers.get("userProfile.role"));
1319
}
1420

1521
public String name() {

0 commit comments

Comments
 (0)