diff --git a/config/src/main/java/org/springframework/security/config/annotation/rsocket/PayloadInterceptorOrder.java b/config/src/main/java/org/springframework/security/config/annotation/rsocket/PayloadInterceptorOrder.java new file mode 100644 index 0000000000..8192d411c0 --- /dev/null +++ b/config/src/main/java/org/springframework/security/config/annotation/rsocket/PayloadInterceptorOrder.java @@ -0,0 +1,67 @@ +/* + * Copyright 2019 the original author or authors. + * + * Licensed 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 + * + * https://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.springframework.security.config.annotation.rsocket; + +import org.springframework.core.Ordered; +import org.springframework.security.config.Customizer; + +/** + * The standard order for {@link org.springframework.security.rsocket.PayloadInterceptor} to be + * sorted. The actual values might change, so users should use the {@link #getOrder()} method to + * calculate the position dynamically rather than copy values. + * + * @author Rob Winch + * @since 5.2 + */ +public enum PayloadInterceptorOrder implements Ordered { + /** + * Where basic authentication is placed. + * @see RSocketSecurity#basicAuthentication(Customizer) + */ + BASIC_AUTHENTICATION, + /** + * Where JWT based authentication is performed. + * @see RSocketSecurity#jwt(Customizer) + */ + JWT_AUTHENTICATION, + /** + * A generic placeholder for other types of authentication. + * @see org.springframework.security.rsocket.authentication.AuthenticationPayloadInterceptor + */ + AUTHENTICATION, + /** + * Where anonymous authentication is placed. + */ + ANONYMOUS, + /** + * Where authorization is placed. + * @see org.springframework.security.rsocket.authorization.AuthorizationPayloadInterceptor + */ + AUTHORIZATION; + + private static final int INTERVAL = 100; + + private int order; + + PayloadInterceptorOrder() { + this.order = ordinal() * INTERVAL; + } + + public int getOrder() { + return this.order; + } +} diff --git a/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java b/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java index a45e88df65..df88ccbe84 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java +++ b/config/src/main/java/org/springframework/security/config/annotation/rsocket/RSocketSecurity.java @@ -19,6 +19,7 @@ package org.springframework.security.config.annotation.rsocket; import org.springframework.beans.BeansException; import org.springframework.context.ApplicationContext; import org.springframework.core.ResolvableType; +import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.messaging.rsocket.annotation.support.RSocketMessageHandler; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authorization.AuthenticatedReactiveAuthorizationManager; @@ -107,6 +108,8 @@ import java.util.List; */ public class RSocketSecurity { + private List payloadInterceptors = new ArrayList<>(); + private BasicAuthenticationSpec basicAuthSpec; private JwtSpec jwtSpec; @@ -117,6 +120,22 @@ public class RSocketSecurity { private ReactiveAuthenticationManager authenticationManager; + /** + * Adds a {@link PayloadInterceptor} to be used. This is typically only used + * when using the DSL does not meet a users needs. In order to ensure the + * {@link PayloadInterceptor} is done in the proper order the {@link PayloadInterceptor} should + * either implement {@link org.springframework.core.Ordered} or be annotated with + * {@link org.springframework.core.annotation.Order}. + * + * @param interceptor + * @return the builder for additional customizations + * @see PayloadInterceptorOrder + */ + public RSocketSecurity addPayloadInterceptor(PayloadInterceptor interceptor) { + this.payloadInterceptors.add(interceptor); + return this; + } + public RSocketSecurity authenticationManager(ReactiveAuthenticationManager authenticationManager) { this.authenticationManager = authenticationManager; return this; @@ -147,7 +166,9 @@ public class RSocketSecurity { protected AuthenticationPayloadInterceptor build() { ReactiveAuthenticationManager manager = getAuthenticationManager(); - return new AuthenticationPayloadInterceptor(manager); + AuthenticationPayloadInterceptor result = new AuthenticationPayloadInterceptor(manager); + result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder()); + return result; } private BasicAuthenticationSpec() {} @@ -185,6 +206,7 @@ public class RSocketSecurity { ReactiveAuthenticationManager manager = getAuthenticationManager(); AuthenticationPayloadInterceptor result = new AuthenticationPayloadInterceptor(manager); result.setAuthenticationConverter(new BearerPayloadExchangeConverter()); + result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder()); return result; } @@ -209,20 +231,27 @@ public class RSocketSecurity { } private List payloadInterceptors() { - List payloadInterceptors = new ArrayList<>(); + List result = new ArrayList<>(this.payloadInterceptors); if (this.basicAuthSpec != null) { - payloadInterceptors.add(this.basicAuthSpec.build()); + result.add(this.basicAuthSpec.build()); } if (this.jwtSpec != null) { - payloadInterceptors.add(this.jwtSpec.build()); + result.add(this.jwtSpec.build()); } - payloadInterceptors.add(new AnonymousPayloadInterceptor("anonymousUser")); + result.add(anonymous()); if (this.authorizePayload != null) { - payloadInterceptors.add(this.authorizePayload.build()); + result.add(this.authorizePayload.build()); } - return payloadInterceptors; + AnnotationAwareOrderComparator.sort(result); + return result; + } + + private AnonymousPayloadInterceptor anonymous() { + AnonymousPayloadInterceptor result = new AnonymousPayloadInterceptor("anonymousUser"); + result.setOrder(PayloadInterceptorOrder.ANONYMOUS.getOrder()); + return result; } public class AuthorizePayloadsSpec { @@ -239,7 +268,9 @@ public class RSocketSecurity { } protected AuthorizationPayloadInterceptor build() { - return new AuthorizationPayloadInterceptor(this.authzBuilder.build()); + AuthorizationPayloadInterceptor result = new AuthorizationPayloadInterceptor(this.authzBuilder.build()); + result.setOrder(PayloadInterceptorOrder.AUTHENTICATION.getOrder()); + return result; } public Access route(String pattern) { diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AnonymousPayloadInterceptor.java b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AnonymousPayloadInterceptor.java index f5e6e56926..54c3be0363 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AnonymousPayloadInterceptor.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AnonymousPayloadInterceptor.java @@ -16,6 +16,7 @@ package org.springframework.security.rsocket.authentication; +import org.springframework.core.Ordered; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; @@ -35,12 +36,13 @@ import java.util.List; * @author Rob Winch * @since 5.2 */ -public class AnonymousPayloadInterceptor implements PayloadInterceptor { +public class AnonymousPayloadInterceptor implements PayloadInterceptor, Ordered { private String key; private Object principal; private List authorities; + private int order; /** * Creates a filter with a principal named "anonymousUser" and the single authority @@ -67,6 +69,14 @@ public class AnonymousPayloadInterceptor implements PayloadInterceptor { this.authorities = authorities; } + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } @Override public Mono intercept(PayloadExchange exchange, PayloadInterceptorChain chain) { diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadInterceptor.java b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadInterceptor.java index a5b3c56408..7219c65588 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadInterceptor.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/authentication/AuthenticationPayloadInterceptor.java @@ -16,6 +16,7 @@ package org.springframework.security.rsocket.authentication; +import org.springframework.core.Ordered; import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.ReactiveSecurityContextHolder; @@ -33,10 +34,12 @@ import reactor.core.publisher.Mono; * @author Rob Winch * @since 5.2 */ -public class AuthenticationPayloadInterceptor implements PayloadInterceptor { +public class AuthenticationPayloadInterceptor implements PayloadInterceptor, Ordered { private final ReactiveAuthenticationManager authenticationManager; + private int order; + private PayloadExchangeAuthenticationConverter authenticationConverter = new BasicAuthenticationPayloadExchangeConverter(); @@ -49,6 +52,15 @@ public class AuthenticationPayloadInterceptor implements PayloadInterceptor { this.authenticationManager = authenticationManager; } + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + /** * Sets the convert to be used * @param authenticationConverter diff --git a/rsocket/src/main/java/org/springframework/security/rsocket/authorization/AuthorizationPayloadInterceptor.java b/rsocket/src/main/java/org/springframework/security/rsocket/authorization/AuthorizationPayloadInterceptor.java index 12863f8003..097f25cdf2 100644 --- a/rsocket/src/main/java/org/springframework/security/rsocket/authorization/AuthorizationPayloadInterceptor.java +++ b/rsocket/src/main/java/org/springframework/security/rsocket/authorization/AuthorizationPayloadInterceptor.java @@ -16,6 +16,7 @@ package org.springframework.security.rsocket.authorization; +import org.springframework.core.Ordered; import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException; import org.springframework.security.authorization.ReactiveAuthorizationManager; import org.springframework.security.core.context.ReactiveSecurityContextHolder; @@ -32,15 +33,26 @@ import org.springframework.security.rsocket.PayloadInterceptor; * @author Rob Winch * @since 5.2 */ -public class AuthorizationPayloadInterceptor implements PayloadInterceptor { +public class AuthorizationPayloadInterceptor implements PayloadInterceptor, Ordered { private final ReactiveAuthorizationManager authorizationManager; + private int order; + public AuthorizationPayloadInterceptor( ReactiveAuthorizationManager authorizationManager) { Assert.notNull(authorizationManager, "authorizationManager cannot be null"); this.authorizationManager = authorizationManager; } + @Override + public int getOrder() { + return this.order; + } + + public void setOrder(int order) { + this.order = order; + } + @Override public Mono intercept(PayloadExchange exchange, PayloadInterceptorChain chain) { return ReactiveSecurityContextHolder.getContext()