Revert "Add AuthorizationManager to Messaging"
This reverts commit 77a6e014a9
.
This commit is contained in:
parent
77a6e014a9
commit
b39f213e64
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2020 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -75,8 +75,8 @@ import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
@Documented
|
@Documented
|
||||||
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, SpringWebSocketImportSelector.class,
|
@Import({ WebSecurityConfiguration.class, SpringWebMvcImportSelector.class, OAuth2ImportSelector.class,
|
||||||
OAuth2ImportSelector.class, HttpSecurityConfiguration.class })
|
HttpSecurityConfiguration.class })
|
||||||
@EnableGlobalAuthentication
|
@EnableGlobalAuthentication
|
||||||
@Configuration
|
@Configuration
|
||||||
public @interface EnableWebSecurity {
|
public @interface EnableWebSecurity {
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.web.configuration;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.ImportSelector;
|
|
||||||
import org.springframework.core.type.AnnotationMetadata;
|
|
||||||
import org.springframework.util.ClassUtils;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Used by {@link EnableWebSecurity} to conditionally import
|
|
||||||
* {@link org.springframework.security.config.annotation.web.socket.WebSocketMessageBrokerSecurityConfiguration}
|
|
||||||
* when the AbstractWebSocketHandler is present on the classpath.
|
|
||||||
*
|
|
||||||
* @author Josh Cummings
|
|
||||||
* @since 5.7
|
|
||||||
*/
|
|
||||||
class SpringWebSocketImportSelector implements ImportSelector {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
|
|
||||||
if (!ClassUtils.isPresent("org.springframework.web.socket.handler.AbstractWebSocketHandler",
|
|
||||||
getClass().getClassLoader())) {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
return new String[] {
|
|
||||||
"org.springframework.security.config.annotation.web.socket.WebSocketMessageBrokerSecurityConfiguration" };
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,354 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.web.messaging;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
import org.springframework.messaging.simp.SimpMessageType;
|
|
||||||
import org.springframework.messaging.simp.annotation.support.SimpAnnotationMethodMessageHandler;
|
|
||||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
|
||||||
import org.springframework.security.authorization.AuthorityAuthorizationManager;
|
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
|
||||||
import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext;
|
|
||||||
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
|
|
||||||
import org.springframework.security.messaging.util.matcher.MessageMatcher;
|
|
||||||
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
|
|
||||||
import org.springframework.security.messaging.util.matcher.SimpMessageTypeMatcher;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.util.PathMatcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows mapping security constraints using {@link MessageMatcher} to authorization
|
|
||||||
* managers.
|
|
||||||
*
|
|
||||||
* @author Josh Cummings
|
|
||||||
* @since 5.7
|
|
||||||
*/
|
|
||||||
public final class AuthorizationManagerMessageMatcherRegistry {
|
|
||||||
|
|
||||||
private final MessageMatcherDelegatingAuthorizationManager.Builder builder = MessageMatcherDelegatingAuthorizationManager
|
|
||||||
.builder();
|
|
||||||
|
|
||||||
private final ApplicationContext context;
|
|
||||||
|
|
||||||
private PathMatcher pathMatcher;
|
|
||||||
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry(ApplicationContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps any {@link Message} to a security expression.
|
|
||||||
* @return the Expression to associate
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry.Constraint anyMessage() {
|
|
||||||
return matchers(MessageMatcher.ANY_MESSAGE);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps any {@link Message} that has a null SimpMessageHeaderAccessor destination
|
|
||||||
* header (i.e. CONNECT, CONNECT_ACK, HEARTBEAT, UNSUBSCRIBE, DISCONNECT,
|
|
||||||
* DISCONNECT_ACK, OTHER)
|
|
||||||
* @return the Expression to associate
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry.Constraint nullDestMatcher() {
|
|
||||||
return matchers(SimpDestinationMessageMatcher.NULL_DESTINATION_MATCHER);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances.
|
|
||||||
* @param typesToMatch the {@link SimpMessageType} instance to match on
|
|
||||||
* @return the {@link Constraint} associated to the matchers.
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry.Constraint simpTypeMatchers(SimpMessageType... typesToMatch) {
|
|
||||||
MessageMatcher<?>[] typeMatchers = new MessageMatcher<?>[typesToMatch.length];
|
|
||||||
for (int i = 0; i < typesToMatch.length; i++) {
|
|
||||||
SimpMessageType typeToMatch = typesToMatch[i];
|
|
||||||
typeMatchers[i] = new SimpMessageTypeMatcher(typeToMatch);
|
|
||||||
}
|
|
||||||
return matchers(typeMatchers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances without
|
|
||||||
* regard to the {@link SimpMessageType}. If no destination is found on the Message,
|
|
||||||
* then the Matcher returns false.
|
|
||||||
* @param patterns the patterns to create
|
|
||||||
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
|
|
||||||
* from.
|
|
||||||
*/
|
|
||||||
public Constraint simpDestMatchers(String... patterns) {
|
|
||||||
return simpDestMatchers(null, patterns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that match
|
|
||||||
* on {@code SimpMessageType.MESSAGE}. If no destination is found on the Message, then
|
|
||||||
* the Matcher returns false.
|
|
||||||
* @param patterns the patterns to create
|
|
||||||
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
|
|
||||||
* from.
|
|
||||||
*/
|
|
||||||
public Constraint simpMessageDestMatchers(String... patterns) {
|
|
||||||
return simpDestMatchers(SimpMessageType.MESSAGE, patterns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances that match
|
|
||||||
* on {@code SimpMessageType.SUBSCRIBE}. If no destination is found on the Message,
|
|
||||||
* then the Matcher returns false.
|
|
||||||
* @param patterns the patterns to create
|
|
||||||
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
|
|
||||||
* from.
|
|
||||||
*/
|
|
||||||
public Constraint simpSubscribeDestMatchers(String... patterns) {
|
|
||||||
return simpDestMatchers(SimpMessageType.SUBSCRIBE, patterns);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a {@link List} of {@link SimpDestinationMessageMatcher} instances. If no
|
|
||||||
* destination is found on the Message, then the Matcher returns false.
|
|
||||||
* @param type the {@link SimpMessageType} to match on. If null, the
|
|
||||||
* {@link SimpMessageType} is not considered for matching.
|
|
||||||
* @param patterns the patterns to create
|
|
||||||
* {@link org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher}
|
|
||||||
* from.
|
|
||||||
* @return the {@link Constraint} that is associated to the {@link MessageMatcher}
|
|
||||||
*/
|
|
||||||
private Constraint simpDestMatchers(SimpMessageType type, String... patterns) {
|
|
||||||
List<MessageMatcher<?>> matchers = new ArrayList<>(patterns.length);
|
|
||||||
for (String pattern : patterns) {
|
|
||||||
Supplier<MessageMatcher<Object>> supplier = new PathMatcherMessageMatcherBuilder(pattern, type);
|
|
||||||
MessageMatcher<?> matcher = new SupplierMessageMatcher(supplier);
|
|
||||||
matchers.add(matcher);
|
|
||||||
}
|
|
||||||
return new Constraint(matchers);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The {@link PathMatcher} to be used with the
|
|
||||||
* {@link MessageSecurityMetadataSourceRegistry#simpDestMatchers(String...)}. The
|
|
||||||
* default is to use the default constructor of {@link AntPathMatcher}.
|
|
||||||
* @param pathMatcher the {@link PathMatcher} to use. Cannot be null.
|
|
||||||
* @return the {@link MessageSecurityMetadataSourceRegistry} for further
|
|
||||||
* customization.
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry simpDestPathMatcher(PathMatcher pathMatcher) {
|
|
||||||
Assert.notNull(pathMatcher, "pathMatcher cannot be null");
|
|
||||||
this.pathMatcher = pathMatcher;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a {@link List} of {@link MessageMatcher} instances to a security expression.
|
|
||||||
* @param matchers the {@link MessageMatcher} instances to map.
|
|
||||||
* @return The {@link Constraint} that is associated to the {@link MessageMatcher}
|
|
||||||
* instances
|
|
||||||
*/
|
|
||||||
public Constraint matchers(MessageMatcher<?>... matchers) {
|
|
||||||
List<MessageMatcher<?>> builders = new ArrayList<>(matchers.length);
|
|
||||||
for (MessageMatcher<?> matcher : matchers) {
|
|
||||||
builders.add(matcher);
|
|
||||||
}
|
|
||||||
return new Constraint(builders);
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuthorizationManager<Message<?>> build() {
|
|
||||||
return this.builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents the security constraint to be applied to the {@link MessageMatcher}
|
|
||||||
* instances.
|
|
||||||
*/
|
|
||||||
public final class Constraint {
|
|
||||||
|
|
||||||
private final List<? extends MessageMatcher<?>> messageMatchers;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance
|
|
||||||
* @param messageMatchers the {@link MessageMatcher} instances to map to this
|
|
||||||
* constraint
|
|
||||||
*/
|
|
||||||
private Constraint(List<? extends MessageMatcher<?>> messageMatchers) {
|
|
||||||
Assert.notEmpty(messageMatchers, "messageMatchers cannot be null or empty");
|
|
||||||
this.messageMatchers = messageMatchers;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for specifying {@link Message} instances require a particular role. If
|
|
||||||
* you do not want to have "ROLE_" automatically inserted see
|
|
||||||
* {@link #hasAuthority(String)}.
|
|
||||||
* @param role the role to require (i.e. USER, ADMIN, etc). Note, it should not
|
|
||||||
* start with "ROLE_" as this is automatically inserted.
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry hasRole(String role) {
|
|
||||||
return access(AuthorityAuthorizationManager.hasRole(role));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shortcut for specifying {@link Message} instances require any of a number of
|
|
||||||
* roles. If you do not want to have "ROLE_" automatically inserted see
|
|
||||||
* {@link #hasAnyAuthority(String...)}
|
|
||||||
* @param roles the roles to require (i.e. USER, ADMIN, etc). Note, it should not
|
|
||||||
* start with "ROLE_" as this is automatically inserted.
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry hasAnyRole(String... roles) {
|
|
||||||
return access(AuthorityAuthorizationManager.hasAnyRole(roles));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify that {@link Message} instances require a particular authority.
|
|
||||||
* @param authority the authority to require (i.e. ROLE_USER, ROLE_ADMIN, etc).
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry hasAuthority(String authority) {
|
|
||||||
return access(AuthorityAuthorizationManager.hasAuthority(authority));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify that {@link Message} instances requires any of a number authorities.
|
|
||||||
* @param authorities the requests require at least one of the authorities (i.e.
|
|
||||||
* "ROLE_USER","ROLE_ADMIN" would mean either "ROLE_USER" or "ROLE_ADMIN" is
|
|
||||||
* required).
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry hasAnyAuthority(String... authorities) {
|
|
||||||
return access(AuthorityAuthorizationManager.hasAnyAuthority(authorities));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify that Messages are allowed by anyone.
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry permitAll() {
|
|
||||||
return access((authentication, context) -> new AuthorizationDecision(true));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify that Messages are not allowed by anyone.
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry denyAll() {
|
|
||||||
return access((authorization, context) -> new AuthorizationDecision(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Specify that Messages are allowed by any authenticated user.
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry authenticated() {
|
|
||||||
return access(AuthenticatedAuthorizationManager.authenticated());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows specifying that Messages are secured by an arbitrary expression
|
|
||||||
* @param authorizationManager the {@link AuthorizationManager} to secure the
|
|
||||||
* destinations
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customization
|
|
||||||
*/
|
|
||||||
public AuthorizationManagerMessageMatcherRegistry access(
|
|
||||||
AuthorizationManager<MessageAuthorizationContext<?>> authorizationManager) {
|
|
||||||
for (MessageMatcher<?> messageMatcher : this.messageMatchers) {
|
|
||||||
AuthorizationManagerMessageMatcherRegistry.this.builder.add(messageMatcher, authorizationManager);
|
|
||||||
}
|
|
||||||
return AuthorizationManagerMessageMatcherRegistry.this;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class SupplierMessageMatcher implements MessageMatcher<Object> {
|
|
||||||
|
|
||||||
private final Supplier<MessageMatcher<Object>> supplier;
|
|
||||||
|
|
||||||
private volatile MessageMatcher<Object> delegate;
|
|
||||||
|
|
||||||
SupplierMessageMatcher(Supplier<MessageMatcher<Object>> supplier) {
|
|
||||||
this.supplier = supplier;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean matches(Message<?> message) {
|
|
||||||
if (this.delegate == null) {
|
|
||||||
synchronized (this.supplier) {
|
|
||||||
if (this.delegate == null) {
|
|
||||||
this.delegate = this.supplier.get();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this.delegate.matches(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
private final class PathMatcherMessageMatcherBuilder implements Supplier<MessageMatcher<Object>> {
|
|
||||||
|
|
||||||
private final String pattern;
|
|
||||||
|
|
||||||
private final SimpMessageType type;
|
|
||||||
|
|
||||||
private PathMatcherMessageMatcherBuilder(String pattern, SimpMessageType type) {
|
|
||||||
this.pattern = pattern;
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
|
|
||||||
private PathMatcher resolvePathMatcher() {
|
|
||||||
if (AuthorizationManagerMessageMatcherRegistry.this.pathMatcher != null) {
|
|
||||||
return AuthorizationManagerMessageMatcherRegistry.this.pathMatcher;
|
|
||||||
}
|
|
||||||
if (AuthorizationManagerMessageMatcherRegistry.this.context
|
|
||||||
.getBeanNamesForType(SimpAnnotationMethodMessageHandler.class).length > 0) {
|
|
||||||
return AuthorizationManagerMessageMatcherRegistry.this.context
|
|
||||||
.getBean(SimpAnnotationMethodMessageHandler.class).getPathMatcher();
|
|
||||||
}
|
|
||||||
return new AntPathMatcher();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public MessageMatcher<Object> get() {
|
|
||||||
PathMatcher pathMatcher = resolvePathMatcher();
|
|
||||||
if (this.type == null) {
|
|
||||||
return new SimpDestinationMessageMatcher(this.pattern, pathMatcher);
|
|
||||||
}
|
|
||||||
if (SimpMessageType.MESSAGE == this.type) {
|
|
||||||
return SimpDestinationMessageMatcher.createMessageMatcher(this.pattern, pathMatcher);
|
|
||||||
}
|
|
||||||
if (SimpMessageType.SUBSCRIBE == this.type) {
|
|
||||||
return SimpDestinationMessageMatcher.createSubscribeMatcher(this.pattern, pathMatcher);
|
|
||||||
}
|
|
||||||
throw new IllegalStateException(this.type + " is not supported since it does not have a destination");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -43,9 +43,7 @@ import org.springframework.util.StringUtils;
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @deprecated Use {@link AuthorizationManagerMessageMatcherRegistry} instead
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public class MessageSecurityMetadataSourceRegistry {
|
public class MessageSecurityMetadataSourceRegistry {
|
||||||
|
|
||||||
private static final String permitAll = "permitAll";
|
private static final String permitAll = "permitAll";
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -81,15 +81,9 @@ import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsSe
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @see WebSocketMessageBrokerSecurityConfiguration
|
|
||||||
* @deprecated Use
|
|
||||||
* {@link org.springframework.security.config.annotation.web.configuration.EnableWebSecurity}
|
|
||||||
* and see {@link WebSocketMessageBrokerSecurityConfiguration} for additional usage
|
|
||||||
* information instead
|
|
||||||
*/
|
*/
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
|
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
|
||||||
@Import(ObjectPostProcessorConfiguration.class)
|
@Import(ObjectPostProcessorConfiguration.class)
|
||||||
@Deprecated
|
|
||||||
public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer
|
public abstract class AbstractSecurityWebSocketMessageBrokerConfigurer extends AbstractWebSocketMessageBrokerConfigurer
|
||||||
implements SmartInitializingSingleton {
|
implements SmartInitializingSingleton {
|
||||||
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.web.socket;
|
|
||||||
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Scope;
|
|
||||||
import org.springframework.security.config.annotation.web.messaging.AuthorizationManagerMessageMatcherRegistry;
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
final class MessageMatcherAuthorizationManagerConfiguration {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
@Scope("prototype")
|
|
||||||
AuthorizationManagerMessageMatcherRegistry authorizationManagerMessageMatcherRegistry(ApplicationContext context) {
|
|
||||||
return new AuthorizationManagerMessageMatcherRegistry(context);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.web.socket;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.SmartInitializingSingleton;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
import org.springframework.core.Ordered;
|
|
||||||
import org.springframework.core.annotation.Order;
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
|
||||||
import org.springframework.messaging.simp.config.ChannelRegistration;
|
|
||||||
import org.springframework.messaging.support.ChannelInterceptor;
|
|
||||||
import org.springframework.security.authorization.AuthenticatedAuthorizationManager;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
|
||||||
import org.springframework.security.authorization.SpringAuthorizationEventPublisher;
|
|
||||||
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
|
|
||||||
import org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager;
|
|
||||||
import org.springframework.security.messaging.context.AuthenticationPrincipalArgumentResolver;
|
|
||||||
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
|
|
||||||
import org.springframework.security.messaging.util.matcher.MessageMatcher;
|
|
||||||
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
|
|
||||||
import org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
|
||||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
|
||||||
import org.springframework.web.socket.server.HandshakeInterceptor;
|
|
||||||
import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.SockJsService;
|
|
||||||
import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allows configuring WebSocket Authorization.
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* For example:
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* <pre>
|
|
||||||
* @Configuration
|
|
||||||
* @EnableWebSecurity
|
|
||||||
* public class WebSocketSecurityConfig {
|
|
||||||
*
|
|
||||||
* @Bean
|
|
||||||
* AuthorizationManager<Message<?>> (AuthorizationManagerMessageMatcherRegistry messages) {
|
|
||||||
* messages.simpDestMatchers("/user/queue/errors").permitAll()
|
|
||||||
* .simpDestMatchers("/admin/**").hasRole("ADMIN").anyMessage()
|
|
||||||
* .authenticated();
|
|
||||||
*
|
|
||||||
* return messages.build();
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* </pre>
|
|
||||||
*
|
|
||||||
* @author Josh Cummings
|
|
||||||
* @since 5.7
|
|
||||||
*/
|
|
||||||
@Order(Ordered.HIGHEST_PRECEDENCE + 100)
|
|
||||||
@Import(MessageMatcherAuthorizationManagerConfiguration.class)
|
|
||||||
final class WebSocketMessageBrokerSecurityConfiguration
|
|
||||||
implements WebSocketMessageBrokerConfigurer, SmartInitializingSingleton {
|
|
||||||
|
|
||||||
private static final String SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME = "stompWebSocketHandlerMapping";
|
|
||||||
|
|
||||||
private static final AuthorizationManager<Message<?>> ANY_MESSAGE_AUTHENTICATED = MessageMatcherDelegatingAuthorizationManager
|
|
||||||
.builder().add(MessageMatcher.ANY_MESSAGE, AuthenticatedAuthorizationManager.authenticated()).build();
|
|
||||||
|
|
||||||
private ChannelInterceptor securityContextChannelInterceptor = new SecurityContextChannelInterceptor();
|
|
||||||
|
|
||||||
private ChannelInterceptor csrfChannelInterceptor = new CsrfChannelInterceptor();
|
|
||||||
|
|
||||||
private AuthorizationChannelInterceptor authorizationChannelInterceptor = new AuthorizationChannelInterceptor(
|
|
||||||
ANY_MESSAGE_AUTHENTICATED);
|
|
||||||
|
|
||||||
private Consumer<List<ChannelInterceptor>> interceptorsCustomizer = (interceptors) -> {
|
|
||||||
};
|
|
||||||
|
|
||||||
private ApplicationContext context;
|
|
||||||
|
|
||||||
private AbstractSecurityWebSocketMessageBrokerConfigurer configurer;
|
|
||||||
|
|
||||||
WebSocketMessageBrokerSecurityConfiguration(ApplicationContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
|
|
||||||
if (this.configurer != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
argumentResolvers.add(new AuthenticationPrincipalArgumentResolver());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureClientInboundChannel(ChannelRegistration registration) {
|
|
||||||
if (this.configurer != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.authorizationChannelInterceptor
|
|
||||||
.setAuthorizationEventPublisher(new SpringAuthorizationEventPublisher(this.context));
|
|
||||||
List<ChannelInterceptor> interceptors = new ArrayList<>(Arrays.asList(this.securityContextChannelInterceptor,
|
|
||||||
this.csrfChannelInterceptor, this.authorizationChannelInterceptor));
|
|
||||||
this.interceptorsCustomizer.accept(interceptors);
|
|
||||||
registration.interceptors(interceptors.toArray(new ChannelInterceptor[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
void setSecurityContextChannelInterceptor(SecurityContextChannelInterceptor interceptor) {
|
|
||||||
this.securityContextChannelInterceptor = interceptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
void setCsrfChannelInterceptor(CsrfChannelInterceptor csrfChannelInterceptor) {
|
|
||||||
this.csrfChannelInterceptor = csrfChannelInterceptor;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
void setAuthorizationManager(AuthorizationManager<Message<?>> authorizationManager) {
|
|
||||||
this.authorizationChannelInterceptor = new AuthorizationChannelInterceptor(authorizationManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
void setInterceptorsCustomizer(Consumer<List<ChannelInterceptor>> interceptorsCustomizer) {
|
|
||||||
this.interceptorsCustomizer = interceptorsCustomizer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired(required = false)
|
|
||||||
@Deprecated
|
|
||||||
void setAbstractSecurityWebSocketMessageBrokerConfigurer(
|
|
||||||
AbstractSecurityWebSocketMessageBrokerConfigurer configurer) {
|
|
||||||
this.configurer = configurer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void afterSingletonsInstantiated() {
|
|
||||||
if (this.configurer != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
SimpleUrlHandlerMapping mapping = getBeanOrNull(SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME,
|
|
||||||
SimpleUrlHandlerMapping.class);
|
|
||||||
if (mapping == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
configureCsrf(mapping);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T getBeanOrNull(String name, Class<T> type) {
|
|
||||||
Map<String, T> beans = this.context.getBeansOfType(type);
|
|
||||||
return beans.get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void configureCsrf(SimpleUrlHandlerMapping mapping) {
|
|
||||||
Map<String, Object> mappings = mapping.getHandlerMap();
|
|
||||||
for (Object object : mappings.values()) {
|
|
||||||
if (object instanceof SockJsHttpRequestHandler) {
|
|
||||||
setHandshakeInterceptors((SockJsHttpRequestHandler) object);
|
|
||||||
}
|
|
||||||
else if (object instanceof WebSocketHttpRequestHandler) {
|
|
||||||
setHandshakeInterceptors((WebSocketHttpRequestHandler) object);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
throw new IllegalStateException(
|
|
||||||
"Bean " + SIMPLE_URL_HANDLER_MAPPING_BEAN_NAME + " is expected to contain mappings to either a "
|
|
||||||
+ "SockJsHttpRequestHandler or a WebSocketHttpRequestHandler but got " + object);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setHandshakeInterceptors(SockJsHttpRequestHandler handler) {
|
|
||||||
SockJsService sockJsService = handler.getSockJsService();
|
|
||||||
Assert.state(sockJsService instanceof TransportHandlingSockJsService,
|
|
||||||
() -> "sockJsService must be instance of TransportHandlingSockJsService got " + sockJsService);
|
|
||||||
TransportHandlingSockJsService transportHandlingSockJsService = (TransportHandlingSockJsService) sockJsService;
|
|
||||||
List<HandshakeInterceptor> handshakeInterceptors = transportHandlingSockJsService.getHandshakeInterceptors();
|
|
||||||
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
|
|
||||||
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
|
|
||||||
interceptorsToSet.addAll(handshakeInterceptors);
|
|
||||||
transportHandlingSockJsService.setHandshakeInterceptors(interceptorsToSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setHandshakeInterceptors(WebSocketHttpRequestHandler handler) {
|
|
||||||
List<HandshakeInterceptor> handshakeInterceptors = handler.getHandshakeInterceptors();
|
|
||||||
List<HandshakeInterceptor> interceptorsToSet = new ArrayList<>(handshakeInterceptors.size() + 1);
|
|
||||||
interceptorsToSet.add(new CsrfTokenHandshakeInterceptor());
|
|
||||||
interceptorsToSet.addAll(handshakeInterceptors);
|
|
||||||
handler.setHandshakeInterceptors(interceptorsToSet);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,176 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.web.socket;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
import org.springframework.messaging.MessageChannel;
|
|
||||||
import org.springframework.messaging.MessageDeliveryException;
|
|
||||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
|
||||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
|
||||||
import org.springframework.messaging.simp.SimpMessageType;
|
|
||||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
|
||||||
import org.springframework.messaging.support.GenericMessage;
|
|
||||||
import org.springframework.mock.web.MockServletConfig;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.messaging.AuthorizationManagerMessageMatcherRegistry;
|
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
|
||||||
import org.springframework.security.web.csrf.CsrfToken;
|
|
||||||
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
|
||||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
|
||||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
|
|
||||||
public class WebSocketMessageBrokerSecurityConfigurationDocTests {
|
|
||||||
|
|
||||||
AnnotationConfigWebApplicationContext context;
|
|
||||||
|
|
||||||
TestingAuthenticationToken messageUser;
|
|
||||||
|
|
||||||
CsrfToken token;
|
|
||||||
|
|
||||||
String sessionAttr;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setup() {
|
|
||||||
this.token = new DefaultCsrfToken("header", "param", "token");
|
|
||||||
this.sessionAttr = "sessionAttr";
|
|
||||||
this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER");
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void cleanup() {
|
|
||||||
if (this.context != null) {
|
|
||||||
this.context.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void securityMappings() {
|
|
||||||
loadConfig(WebSocketSecurityConfig.class);
|
|
||||||
clientInboundChannel().send(message("/user/queue/errors", SimpMessageType.SUBSCRIBE));
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
|
||||||
.isThrownBy(() -> clientInboundChannel().send(message("/denyAll", SimpMessageType.MESSAGE)))
|
|
||||||
.withCauseInstanceOf(AccessDeniedException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadConfig(Class<?>... configs) {
|
|
||||||
this.context = new AnnotationConfigWebApplicationContext();
|
|
||||||
this.context.register(configs);
|
|
||||||
this.context.register(WebSocketConfig.class, SyncExecutorConfig.class);
|
|
||||||
this.context.setServletConfig(new MockServletConfig());
|
|
||||||
this.context.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageChannel clientInboundChannel() {
|
|
||||||
return this.context.getBean("clientInboundChannel", MessageChannel.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message<String> message(String destination, SimpMessageType type) {
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(type);
|
|
||||||
return message(headers, destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message<String> message(SimpMessageHeaderAccessor headers, String destination) {
|
|
||||||
headers.setSessionId("123");
|
|
||||||
headers.setSessionAttributes(new HashMap<>());
|
|
||||||
if (destination != null) {
|
|
||||||
headers.setDestination(destination);
|
|
||||||
}
|
|
||||||
if (this.messageUser != null) {
|
|
||||||
headers.setUser(this.messageUser);
|
|
||||||
}
|
|
||||||
return new GenericMessage<>("hi", headers.getMessageHeaders());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
static class MyController {
|
|
||||||
|
|
||||||
@MessageMapping("/authentication")
|
|
||||||
void authentication(@AuthenticationPrincipal String un) {
|
|
||||||
// ... do something ...
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
static class WebSocketSecurityConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager(AuthorizationManagerMessageMatcherRegistry messages) {
|
|
||||||
messages.nullDestMatcher().authenticated()
|
|
||||||
// <1>
|
|
||||||
.simpSubscribeDestMatchers("/user/queue/errors").permitAll()
|
|
||||||
// <2>
|
|
||||||
.simpDestMatchers("/app/**").hasRole("USER")
|
|
||||||
// <3>
|
|
||||||
.simpSubscribeDestMatchers("/user/**", "/topic/friends/*").hasRole("USER") // <4>
|
|
||||||
.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll() // <5>
|
|
||||||
.anyMessage().denyAll(); // <6>
|
|
||||||
return messages.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
static class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
registry.addEndpoint("/chat").withSockJS();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
|
||||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
|
||||||
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
MyController myController() {
|
|
||||||
return new MyController();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class SyncExecutorConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static SyncExecutorSubscribableChannelPostProcessor postProcessor() {
|
|
||||||
return new SyncExecutorSubscribableChannelPostProcessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,796 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.web.socket;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.context.ApplicationContext;
|
|
||||||
import org.springframework.context.annotation.Bean;
|
|
||||||
import org.springframework.context.annotation.Configuration;
|
|
||||||
import org.springframework.context.annotation.Import;
|
|
||||||
import org.springframework.core.MethodParameter;
|
|
||||||
import org.springframework.http.server.ServerHttpRequest;
|
|
||||||
import org.springframework.http.server.ServerHttpResponse;
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
import org.springframework.messaging.MessageChannel;
|
|
||||||
import org.springframework.messaging.MessageDeliveryException;
|
|
||||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
|
||||||
import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver;
|
|
||||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
|
||||||
import org.springframework.messaging.simp.SimpMessageType;
|
|
||||||
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
|
|
||||||
import org.springframework.messaging.support.AbstractMessageChannel;
|
|
||||||
import org.springframework.messaging.support.ChannelInterceptor;
|
|
||||||
import org.springframework.messaging.support.GenericMessage;
|
|
||||||
import org.springframework.mock.web.MockHttpServletRequest;
|
|
||||||
import org.springframework.mock.web.MockHttpServletResponse;
|
|
||||||
import org.springframework.mock.web.MockServletConfig;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
|
||||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
|
||||||
import org.springframework.security.config.annotation.web.messaging.AuthorizationManagerMessageMatcherRegistry;
|
|
||||||
import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
|
||||||
import org.springframework.security.messaging.access.intercept.AuthorizationChannelInterceptor;
|
|
||||||
import org.springframework.security.messaging.access.intercept.ChannelSecurityInterceptor;
|
|
||||||
import org.springframework.security.messaging.access.intercept.MessageAuthorizationContext;
|
|
||||||
import org.springframework.security.messaging.context.SecurityContextChannelInterceptor;
|
|
||||||
import org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor;
|
|
||||||
import org.springframework.security.web.csrf.CsrfToken;
|
|
||||||
import org.springframework.security.web.csrf.DefaultCsrfToken;
|
|
||||||
import org.springframework.security.web.csrf.MissingCsrfTokenException;
|
|
||||||
import org.springframework.stereotype.Controller;
|
|
||||||
import org.springframework.test.util.ReflectionTestUtils;
|
|
||||||
import org.springframework.util.AntPathMatcher;
|
|
||||||
import org.springframework.web.HttpRequestHandler;
|
|
||||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
|
||||||
import org.springframework.web.servlet.HandlerMapping;
|
|
||||||
import org.springframework.web.socket.WebSocketHandler;
|
|
||||||
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
|
|
||||||
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
|
|
||||||
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
|
|
||||||
import org.springframework.web.socket.server.HandshakeFailureException;
|
|
||||||
import org.springframework.web.socket.server.HandshakeHandler;
|
|
||||||
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.handler.SockJsWebSocketHandler;
|
|
||||||
import org.springframework.web.socket.sockjs.transport.session.WebSocketServerSockJsSession;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
|
||||||
import static org.assertj.core.api.Assertions.fail;
|
|
||||||
|
|
||||||
public class WebSocketMessageBrokerSecurityConfigurationTests {
|
|
||||||
|
|
||||||
AnnotationConfigWebApplicationContext context;
|
|
||||||
|
|
||||||
TestingAuthenticationToken messageUser;
|
|
||||||
|
|
||||||
CsrfToken token;
|
|
||||||
|
|
||||||
String sessionAttr;
|
|
||||||
|
|
||||||
@BeforeEach
|
|
||||||
public void setup() {
|
|
||||||
this.token = new DefaultCsrfToken("header", "param", "token");
|
|
||||||
this.sessionAttr = "sessionAttr";
|
|
||||||
this.messageUser = new TestingAuthenticationToken("user", "pass", "ROLE_USER");
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
public void cleanup() {
|
|
||||||
if (this.context != null) {
|
|
||||||
this.context.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void simpleRegistryMappings() {
|
|
||||||
loadConfig(SockJsSecurityConfig.class);
|
|
||||||
clientInboundChannel().send(message("/permitAll"));
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
|
||||||
.isThrownBy(() -> clientInboundChannel().send(message("/denyAll")))
|
|
||||||
.withCauseInstanceOf(AccessDeniedException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void annonymousSupported() {
|
|
||||||
loadConfig(SockJsSecurityConfig.class);
|
|
||||||
this.messageUser = null;
|
|
||||||
clientInboundChannel().send(message("/permitAll"));
|
|
||||||
}
|
|
||||||
|
|
||||||
// gh-3797
|
|
||||||
@Test
|
|
||||||
public void beanResolver() {
|
|
||||||
loadConfig(SockJsSecurityConfig.class);
|
|
||||||
this.messageUser = null;
|
|
||||||
clientInboundChannel().send(message("/beanResolver"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addsAuthenticationPrincipalResolver() {
|
|
||||||
loadConfig(SockJsSecurityConfig.class);
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
Message<String> message = message("/permitAll/authentication");
|
|
||||||
messageChannel.send(message);
|
|
||||||
assertThat(this.context.getBean(MyController.class).authenticationPrincipal)
|
|
||||||
.isEqualTo((String) this.messageUser.getPrincipal());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addsAuthenticationPrincipalResolverWhenNoAuthorization() {
|
|
||||||
loadConfig(NoInboundSecurityConfig.class);
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
Message<String> message = message("/permitAll/authentication");
|
|
||||||
messageChannel.send(message);
|
|
||||||
assertThat(this.context.getBean(MyController.class).authenticationPrincipal)
|
|
||||||
.isEqualTo((String) this.messageUser.getPrincipal());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addsCsrfProtectionWhenNoAuthorization() {
|
|
||||||
loadConfig(NoInboundSecurityConfig.class);
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
|
||||||
Message<?> message = message(headers, "/authentication");
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message))
|
|
||||||
.withCauseInstanceOf(MissingCsrfTokenException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void csrfProtectionForConnect() {
|
|
||||||
loadConfig(SockJsSecurityConfig.class);
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
|
||||||
Message<?> message = message(headers, "/authentication");
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class).isThrownBy(() -> messageChannel.send(message))
|
|
||||||
.withCauseInstanceOf(MissingCsrfTokenException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void csrfProtectionDisabledForConnect() {
|
|
||||||
loadConfig(CsrfDisabledSockJsSecurityConfig.class);
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
|
||||||
Message<?> message = message(headers, "/permitAll/connect");
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
messageChannel.send(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void csrfProtectionDefinedByBean() {
|
|
||||||
loadConfig(SockJsProxylessSecurityConfig.class);
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
Stream<Class<? extends ChannelInterceptor>> interceptors = ((AbstractMessageChannel) messageChannel)
|
|
||||||
.getInterceptors().stream().map(ChannelInterceptor::getClass);
|
|
||||||
assertThat(interceptors).contains(CsrfChannelInterceptor.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void messagesConnectUseCsrfTokenHandshakeInterceptor() throws Exception {
|
|
||||||
loadConfig(SockJsSecurityConfig.class);
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
|
||||||
Message<?> message = message(headers, "/authentication");
|
|
||||||
MockHttpServletRequest request = sockjsHttpRequest("/chat");
|
|
||||||
HttpRequestHandler handler = handler(request);
|
|
||||||
handler.handleRequest(request, new MockHttpServletResponse());
|
|
||||||
assertHandshake(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void messagesConnectUseCsrfTokenHandshakeInterceptorMultipleMappings() throws Exception {
|
|
||||||
loadConfig(SockJsSecurityConfig.class);
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
|
||||||
Message<?> message = message(headers, "/authentication");
|
|
||||||
MockHttpServletRequest request = sockjsHttpRequest("/other");
|
|
||||||
HttpRequestHandler handler = handler(request);
|
|
||||||
handler.handleRequest(request, new MockHttpServletResponse());
|
|
||||||
assertHandshake(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void messagesConnectWebSocketUseCsrfTokenHandshakeInterceptor() throws Exception {
|
|
||||||
loadConfig(WebSocketSecurityConfig.class);
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create(SimpMessageType.CONNECT);
|
|
||||||
Message<?> message = message(headers, "/authentication");
|
|
||||||
MockHttpServletRequest request = websocketHttpRequest("/websocket");
|
|
||||||
HttpRequestHandler handler = handler(request);
|
|
||||||
handler.handleRequest(request, new MockHttpServletResponse());
|
|
||||||
assertHandshake(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void msmsRegistryCustomPatternMatcher() {
|
|
||||||
loadConfig(MsmsRegistryCustomPatternMatcherConfig.class);
|
|
||||||
clientInboundChannel().send(message("/app/a.b"));
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
|
||||||
.isThrownBy(() -> clientInboundChannel().send(message("/app/a.b.c")))
|
|
||||||
.withCauseInstanceOf(AccessDeniedException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void overrideMsmsRegistryCustomPatternMatcher() {
|
|
||||||
loadConfig(OverrideMsmsRegistryCustomPatternMatcherConfig.class);
|
|
||||||
clientInboundChannel().send(message("/app/a/b"));
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
|
||||||
.isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c")))
|
|
||||||
.withCauseInstanceOf(AccessDeniedException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void defaultPatternMatcher() {
|
|
||||||
loadConfig(DefaultPatternMatcherConfig.class);
|
|
||||||
clientInboundChannel().send(message("/app/a/b"));
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
|
||||||
.isThrownBy(() -> clientInboundChannel().send(message("/app/a/b/c")))
|
|
||||||
.withCauseInstanceOf(AccessDeniedException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void customExpression() {
|
|
||||||
loadConfig(CustomExpressionConfig.class);
|
|
||||||
clientInboundChannel().send(message("/denyRob"));
|
|
||||||
this.messageUser = new TestingAuthenticationToken("rob", "password", "ROLE_USER");
|
|
||||||
assertThatExceptionOfType(MessageDeliveryException.class)
|
|
||||||
.isThrownBy(() -> clientInboundChannel().send(message("/denyRob")))
|
|
||||||
.withCauseInstanceOf(AccessDeniedException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void channelSecurityInterceptorUsesMetadataSourceBeanWhenProxyingDisabled() {
|
|
||||||
loadConfig(SockJsProxylessSecurityConfig.class);
|
|
||||||
AbstractMessageChannel messageChannel = clientInboundChannel();
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager = this.context.getBean(AuthorizationManager.class);
|
|
||||||
for (ChannelInterceptor interceptor : messageChannel.getInterceptors()) {
|
|
||||||
if (interceptor instanceof AuthorizationChannelInterceptor) {
|
|
||||||
assertThat(ReflectionTestUtils.getField(interceptor, "preSendAuthorizationManager"))
|
|
||||||
.isSameAs(authorizationManager);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fail("did not find AuthorizationChannelInterceptor");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void securityContextChannelInterceptorDefinedByBean() {
|
|
||||||
loadConfig(SockJsProxylessSecurityConfig.class);
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
Stream<Class<? extends ChannelInterceptor>> interceptors = ((AbstractMessageChannel) messageChannel)
|
|
||||||
.getInterceptors().stream().map(ChannelInterceptor::getClass);
|
|
||||||
assertThat(interceptors).contains(SecurityContextChannelInterceptor.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void inboundChannelSecurityDefinedByBean() {
|
|
||||||
loadConfig(SockJsProxylessSecurityConfig.class);
|
|
||||||
MessageChannel messageChannel = clientInboundChannel();
|
|
||||||
Stream<Class<? extends ChannelInterceptor>> interceptors = ((AbstractMessageChannel) messageChannel)
|
|
||||||
.getInterceptors().stream().map(ChannelInterceptor::getClass);
|
|
||||||
assertThat(interceptors).contains(AuthorizationChannelInterceptor.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void usingLegacyThenNewFiltersNotUsed() {
|
|
||||||
loadConfig(UsingLegacyConfigurerConfig.class);
|
|
||||||
AbstractMessageChannel messageChannel = clientInboundChannel();
|
|
||||||
List<Class<? extends ChannelInterceptor>> interceptors = messageChannel.getInterceptors().stream()
|
|
||||||
.map(ChannelInterceptor::getClass).collect(Collectors.toList());
|
|
||||||
assertThat(interceptors).contains(ChannelSecurityInterceptor.class);
|
|
||||||
assertThat(interceptors).doesNotContain(AuthorizationChannelInterceptor.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void assertHandshake(HttpServletRequest request) {
|
|
||||||
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
|
|
||||||
assertThat(handshakeHandler.attributes.get(CsrfToken.class.getName())).isSameAs(this.token);
|
|
||||||
assertThat(handshakeHandler.attributes.get(this.sessionAttr))
|
|
||||||
.isEqualTo(request.getSession().getAttribute(this.sessionAttr));
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequestHandler handler(HttpServletRequest request) throws Exception {
|
|
||||||
HandlerMapping handlerMapping = this.context.getBean(HandlerMapping.class);
|
|
||||||
return (HttpRequestHandler) handlerMapping.getHandler(request).getHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
private MockHttpServletRequest websocketHttpRequest(String mapping) {
|
|
||||||
MockHttpServletRequest request = sockjsHttpRequest(mapping);
|
|
||||||
request.setRequestURI(mapping);
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MockHttpServletRequest sockjsHttpRequest(String mapping) {
|
|
||||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
|
|
||||||
request.setMethod("GET");
|
|
||||||
request.setAttribute(HandlerMapping.PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE, "/289/tpyx6mde/websocket");
|
|
||||||
request.setRequestURI(mapping + "/289/tpyx6mde/websocket");
|
|
||||||
request.getSession().setAttribute(this.sessionAttr, "sessionValue");
|
|
||||||
request.setAttribute(CsrfToken.class.getName(), this.token);
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message<String> message(String destination) {
|
|
||||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
|
|
||||||
return message(headers, destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Message<String> message(SimpMessageHeaderAccessor headers, String destination) {
|
|
||||||
headers.setSessionId("123");
|
|
||||||
headers.setSessionAttributes(new HashMap<>());
|
|
||||||
if (destination != null) {
|
|
||||||
headers.setDestination(destination);
|
|
||||||
}
|
|
||||||
if (this.messageUser != null) {
|
|
||||||
headers.setUser(this.messageUser);
|
|
||||||
}
|
|
||||||
return new GenericMessage<>("hi", headers.getMessageHeaders());
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T extends MessageChannel> T clientInboundChannel() {
|
|
||||||
return (T) this.context.getBean("clientInboundChannel", MessageChannel.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadConfig(Class<?>... configs) {
|
|
||||||
this.context = new AnnotationConfigWebApplicationContext();
|
|
||||||
this.context.register(configs);
|
|
||||||
this.context.setServletConfig(new MockServletConfig());
|
|
||||||
this.context.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@EnableWebSecurity
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class MsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
registry
|
|
||||||
.addEndpoint("/other")
|
|
||||||
.setHandshakeHandler(testHandshakeHandler());
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
|
||||||
registry.setPathMatcher(new AntPathMatcher("."));
|
|
||||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
|
||||||
registry.setApplicationDestinationPrefixes("/app");
|
|
||||||
}
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager(AuthorizationManagerMessageMatcherRegistry messages) {
|
|
||||||
messages
|
|
||||||
.simpDestMatchers("/app/a.*").permitAll()
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
|
|
||||||
return messages.build();
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@EnableWebSecurity
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class OverrideMsmsRegistryCustomPatternMatcherConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
registry
|
|
||||||
.addEndpoint("/other")
|
|
||||||
.setHandshakeHandler(testHandshakeHandler());
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
|
||||||
registry.setPathMatcher(new AntPathMatcher("."));
|
|
||||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
|
||||||
registry.setApplicationDestinationPrefixes("/app");
|
|
||||||
}
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager(AuthorizationManagerMessageMatcherRegistry messages) {
|
|
||||||
messages
|
|
||||||
.simpDestPathMatcher(new AntPathMatcher())
|
|
||||||
.simpDestMatchers("/app/a/*").permitAll()
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
return messages.build();
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@EnableWebSecurity
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class DefaultPatternMatcherConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
registry
|
|
||||||
.addEndpoint("/other")
|
|
||||||
.setHandshakeHandler(testHandshakeHandler());
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
|
||||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
|
||||||
registry.setApplicationDestinationPrefixes("/app");
|
|
||||||
}
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager(AuthorizationManagerMessageMatcherRegistry messages) {
|
|
||||||
messages
|
|
||||||
.simpDestMatchers("/app/a/*").permitAll()
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
|
|
||||||
return messages.build();
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@EnableWebSecurity
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class CustomExpressionConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
registry
|
|
||||||
.addEndpoint("/other")
|
|
||||||
.setHandshakeHandler(testHandshakeHandler());
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
|
||||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
|
||||||
registry.setApplicationDestinationPrefixes("/app");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<Object>> authorizationManager() {
|
|
||||||
return (authentication, message) -> {
|
|
||||||
Authentication auth = authentication.get();
|
|
||||||
return new AuthorizationDecision(auth != null && !"rob".equals(auth.getName()));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Controller
|
|
||||||
static class MyController {
|
|
||||||
|
|
||||||
String authenticationPrincipal;
|
|
||||||
|
|
||||||
MyCustomArgument myCustomArgument;
|
|
||||||
|
|
||||||
@MessageMapping("/authentication")
|
|
||||||
void authentication(@AuthenticationPrincipal String un) {
|
|
||||||
this.authenticationPrincipal = un;
|
|
||||||
}
|
|
||||||
|
|
||||||
@MessageMapping("/myCustom")
|
|
||||||
void myCustom(MyCustomArgument myCustomArgument) {
|
|
||||||
this.myCustomArgument = myCustomArgument;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class MyCustomArgument {
|
|
||||||
|
|
||||||
MyCustomArgument(String notDefaultConstr) {
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class MyCustomArgumentResolver implements HandlerMethodArgumentResolver {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean supportsParameter(MethodParameter parameter) {
|
|
||||||
return parameter.getParameterType().isAssignableFrom(MyCustomArgument.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object resolveArgument(MethodParameter parameter, Message<?> message) {
|
|
||||||
return new MyCustomArgument("");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
static class TestHandshakeHandler implements HandshakeHandler {
|
|
||||||
|
|
||||||
Map<String, Object> attributes;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
|
|
||||||
Map<String, Object> attributes) throws HandshakeFailureException {
|
|
||||||
this.attributes = attributes;
|
|
||||||
if (wsHandler instanceof SockJsWebSocketHandler) {
|
|
||||||
// work around SPR-12716
|
|
||||||
SockJsWebSocketHandler sockJs = (SockJsWebSocketHandler) wsHandler;
|
|
||||||
WebSocketServerSockJsSession session = (WebSocketServerSockJsSession) ReflectionTestUtils
|
|
||||||
.getField(sockJs, "sockJsSession");
|
|
||||||
this.attributes = session.getAttributes();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class SockJsSecurityConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
// @formatter:off
|
|
||||||
registry.addEndpoint("/other").setHandshakeHandler(testHandshakeHandler())
|
|
||||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
registry.addEndpoint("/chat").setHandshakeHandler(testHandshakeHandler())
|
|
||||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager(AuthorizationManagerMessageMatcherRegistry messages,
|
|
||||||
SecurityCheck security) {
|
|
||||||
AuthorizationManager<MessageAuthorizationContext<?>> beanResolver =
|
|
||||||
(authentication, context) -> new AuthorizationDecision(security.check());
|
|
||||||
messages
|
|
||||||
.simpDestMatchers("/permitAll/**").permitAll()
|
|
||||||
.simpDestMatchers("/beanResolver/**").access(beanResolver)
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
return messages.build();
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
|
||||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
|
||||||
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
MyController myController() {
|
|
||||||
return new MyController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
SecurityCheck security() {
|
|
||||||
return new SecurityCheck();
|
|
||||||
}
|
|
||||||
|
|
||||||
static class SecurityCheck {
|
|
||||||
|
|
||||||
private boolean check;
|
|
||||||
|
|
||||||
boolean check() {
|
|
||||||
this.check = !this.check;
|
|
||||||
return this.check;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class NoInboundSecurityConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
// @formatter:off
|
|
||||||
registry.addEndpoint("/other")
|
|
||||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
registry.addEndpoint("/chat")
|
|
||||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureMessageBroker(MessageBrokerRegistry registry) {
|
|
||||||
registry.enableSimpleBroker("/queue/", "/topic/");
|
|
||||||
registry.setApplicationDestinationPrefixes("/permitAll", "/denyAll");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
MyController myController() {
|
|
||||||
return new MyController();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@Import(SockJsSecurityConfig.class)
|
|
||||||
static class CsrfDisabledSockJsSecurityConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
Consumer<List<ChannelInterceptor>> channelInterceptorCustomizer() {
|
|
||||||
return (interceptors) -> interceptors.remove(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class WebSocketSecurityConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
// @formatter:off
|
|
||||||
registry.addEndpoint("/websocket")
|
|
||||||
.setHandshakeHandler(testHandshakeHandler())
|
|
||||||
.addInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager(AuthorizationManagerMessageMatcherRegistry messages) {
|
|
||||||
// @formatter:off
|
|
||||||
messages
|
|
||||||
.simpDestMatchers("/permitAll/**").permitAll()
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
// @formatter:on
|
|
||||||
return messages.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class UsingLegacyConfigurerConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
// @formatter:off
|
|
||||||
registry.addEndpoint("/websocket")
|
|
||||||
.setHandshakeHandler(testHandshakeHandler())
|
|
||||||
.addInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
|
|
||||||
// @formatter:off
|
|
||||||
messages
|
|
||||||
.simpDestMatchers("/permitAll/**").permitAll()
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration(proxyBeanMethods = false)
|
|
||||||
@EnableWebSecurity
|
|
||||||
@EnableWebSocketMessageBroker
|
|
||||||
@Import(SyncExecutorConfig.class)
|
|
||||||
static class SockJsProxylessSecurityConfig implements WebSocketMessageBrokerConfigurer {
|
|
||||||
|
|
||||||
private ApplicationContext context;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void registerStompEndpoints(StompEndpointRegistry registry) {
|
|
||||||
// @formatter:off
|
|
||||||
registry.addEndpoint("/chat")
|
|
||||||
.setHandshakeHandler(this.context.getBean(TestHandshakeHandler.class))
|
|
||||||
.withSockJS().setInterceptors(new HttpSessionHandshakeInterceptor());
|
|
||||||
// @formatter:on
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
void setContext(ApplicationContext context) {
|
|
||||||
this.context = context;
|
|
||||||
}
|
|
||||||
|
|
||||||
// @formatter:off
|
|
||||||
@Bean
|
|
||||||
AuthorizationManager<Message<?>> authorizationManager(AuthorizationManagerMessageMatcherRegistry messages) {
|
|
||||||
messages
|
|
||||||
.anyMessage().denyAll();
|
|
||||||
return messages.build();
|
|
||||||
}
|
|
||||||
// @formatter:on
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
TestHandshakeHandler testHandshakeHandler() {
|
|
||||||
return new TestHandshakeHandler();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Configuration
|
|
||||||
static class SyncExecutorConfig {
|
|
||||||
|
|
||||||
@Bean
|
|
||||||
static SyncExecutorSubscribableChannelPostProcessor postProcessor() {
|
|
||||||
return new SyncExecutorSubscribableChannelPostProcessor();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -27,10 +27,7 @@ import org.springframework.expression.EvaluationContext;
|
||||||
*
|
*
|
||||||
* @author Daniel Bustamante Ospina
|
* @author Daniel Bustamante Ospina
|
||||||
* @since 5.2
|
* @since 5.2
|
||||||
* @deprecated Since {@link MessageExpressionVoter} is deprecated, there is no more need
|
|
||||||
* for this class
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
interface EvaluationContextPostProcessor<I> {
|
interface EvaluationContextPostProcessor<I> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -35,11 +35,7 @@ import org.springframework.security.messaging.util.matcher.MessageMatcher;
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @deprecated Use
|
|
||||||
* {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager}
|
|
||||||
* instead
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
|
public final class ExpressionBasedMessageSecurityMetadataSourceFactory {
|
||||||
|
|
||||||
private ExpressionBasedMessageSecurityMetadataSourceFactory() {
|
private ExpressionBasedMessageSecurityMetadataSourceFactory() {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -32,11 +32,7 @@ import org.springframework.util.Assert;
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Daniel Bustamante Ospina
|
* @author Daniel Bustamante Ospina
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @deprecated Use
|
|
||||||
* {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager}
|
|
||||||
* instead
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
@SuppressWarnings("serial")
|
@SuppressWarnings("serial")
|
||||||
class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<Message<?>> {
|
class MessageExpressionConfigAttribute implements ConfigAttribute, EvaluationContextPostProcessor<Message<?>> {
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -37,11 +37,7 @@ import org.springframework.util.Assert;
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @author Daniel Bustamante Ospina
|
* @author Daniel Bustamante Ospina
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @deprecated Use
|
|
||||||
* {@link org.springframework.security.messaging.access.intercept.MessageMatcherDelegatingAuthorizationManager}
|
|
||||||
* instead
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public class MessageExpressionVoter<T> implements AccessDecisionVoter<Message<T>> {
|
public class MessageExpressionVoter<T> implements AccessDecisionVoter<Message<T>> {
|
||||||
|
|
||||||
private SecurityExpressionHandler<Message<T>> expressionHandler = new DefaultMessageSecurityExpressionHandler<>();
|
private SecurityExpressionHandler<Message<T>> expressionHandler = new DefaultMessageSecurityExpressionHandler<>();
|
||||||
|
|
|
@ -1,95 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.messaging.access.intercept;
|
|
||||||
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
|
|
||||||
import org.springframework.core.log.LogMessage;
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
import org.springframework.messaging.MessageChannel;
|
|
||||||
import org.springframework.messaging.support.ChannelInterceptor;
|
|
||||||
import org.springframework.security.access.AccessDeniedException;
|
|
||||||
import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;
|
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
|
||||||
import org.springframework.security.authorization.AuthorizationEventPublisher;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.core.context.SecurityContextHolder;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Authorizes {@link Message} resources using the provided {@link AuthorizationManager}
|
|
||||||
*
|
|
||||||
* @author Josh Cummings
|
|
||||||
* @since 5.7
|
|
||||||
*/
|
|
||||||
public final class AuthorizationChannelInterceptor implements ChannelInterceptor {
|
|
||||||
|
|
||||||
static final Supplier<Authentication> AUTHENTICATION_SUPPLIER = () -> {
|
|
||||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
|
||||||
if (authentication == null) {
|
|
||||||
throw new AuthenticationCredentialsNotFoundException(
|
|
||||||
"An Authentication object was not found in the SecurityContext");
|
|
||||||
}
|
|
||||||
return authentication;
|
|
||||||
};
|
|
||||||
|
|
||||||
private final Log logger = LogFactory.getLog(this.getClass());
|
|
||||||
|
|
||||||
private final AuthorizationManager<Message<?>> preSendAuthorizationManager;
|
|
||||||
|
|
||||||
private AuthorizationEventPublisher eventPublisher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance
|
|
||||||
* @param preSendAuthorizationManager the {@link AuthorizationManager} to use. Cannot
|
|
||||||
* be null.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public AuthorizationChannelInterceptor(AuthorizationManager<Message<?>> preSendAuthorizationManager) {
|
|
||||||
Assert.notNull(preSendAuthorizationManager, "preSendAuthorizationManager cannot be null");
|
|
||||||
this.preSendAuthorizationManager = preSendAuthorizationManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Message<?> preSend(Message<?> message, MessageChannel channel) {
|
|
||||||
this.logger.debug(LogMessage.of(() -> "Authorizing message send"));
|
|
||||||
AuthorizationDecision decision = this.preSendAuthorizationManager.check(AUTHENTICATION_SUPPLIER, message);
|
|
||||||
this.eventPublisher.publishAuthorizationEvent(AUTHENTICATION_SUPPLIER, message, decision);
|
|
||||||
if (decision == null || !decision.isGranted()) { // default deny
|
|
||||||
this.logger.debug(LogMessage.of(() -> "Failed to authorize message with authorization manager "
|
|
||||||
+ this.preSendAuthorizationManager + " and decision " + decision));
|
|
||||||
throw new AccessDeniedException("Access Denied");
|
|
||||||
}
|
|
||||||
this.logger.debug(LogMessage.of(() -> "Authorized message send"));
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Use this {@link AuthorizationEventPublisher} to publish the
|
|
||||||
* {@link AuthorizationManager} result.
|
|
||||||
* @param eventPublisher
|
|
||||||
*/
|
|
||||||
public void setAuthorizationEventPublisher(AuthorizationEventPublisher eventPublisher) {
|
|
||||||
Assert.notNull(eventPublisher, "eventPublisher cannot be null");
|
|
||||||
this.eventPublisher = eventPublisher;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -36,9 +36,7 @@ import org.springframework.util.Assert;
|
||||||
*
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @deprecated Use {@link AuthorizationChannelInterceptor} instead
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements ChannelInterceptor {
|
public final class ChannelSecurityInterceptor extends AbstractSecurityInterceptor implements ChannelInterceptor {
|
||||||
|
|
||||||
private static final ThreadLocal<InterceptorStatusToken> tokenHolder = new ThreadLocal<>();
|
private static final ThreadLocal<InterceptorStatusToken> tokenHolder = new ThreadLocal<>();
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -40,9 +40,7 @@ import org.springframework.security.messaging.util.matcher.MessageMatcher;
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @see ChannelSecurityInterceptor
|
* @see ChannelSecurityInterceptor
|
||||||
* @see ExpressionBasedMessageSecurityMetadataSourceFactory
|
* @see ExpressionBasedMessageSecurityMetadataSourceFactory
|
||||||
* @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public final class DefaultMessageSecurityMetadataSource implements MessageSecurityMetadataSource {
|
public final class DefaultMessageSecurityMetadataSource implements MessageSecurityMetadataSource {
|
||||||
|
|
||||||
private final Map<MessageMatcher<?>, Collection<ConfigAttribute>> messageMap;
|
private final Map<MessageMatcher<?>, Collection<ConfigAttribute>> messageMap;
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.messaging.access.intercept;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link Message} authorization context.
|
|
||||||
*
|
|
||||||
* @author Josh Cummings
|
|
||||||
* @since 5.7
|
|
||||||
*/
|
|
||||||
public final class MessageAuthorizationContext<T> {
|
|
||||||
|
|
||||||
private final Message<T> message;
|
|
||||||
|
|
||||||
private final Map<String, String> variables;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance.
|
|
||||||
* @param message the {@link HttpServletRequest} to use
|
|
||||||
*/
|
|
||||||
public MessageAuthorizationContext(Message<T> message) {
|
|
||||||
this(message, Collections.emptyMap());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an instance.
|
|
||||||
* @param message the {@link HttpServletRequest} to use
|
|
||||||
* @param variables a map containing key-value pairs representing extracted variable
|
|
||||||
* names and variable values
|
|
||||||
*/
|
|
||||||
public MessageAuthorizationContext(Message<T> message, Map<String, String> variables) {
|
|
||||||
this.message = message;
|
|
||||||
this.variables = variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link HttpServletRequest}.
|
|
||||||
* @return the {@link HttpServletRequest} to use
|
|
||||||
*/
|
|
||||||
public Message<T> getMessage() {
|
|
||||||
return this.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the extracted variable values where the key is the variable name and the
|
|
||||||
* value is the variable value.
|
|
||||||
* @return a map containing key-value pairs representing extracted variable names and
|
|
||||||
* variable values
|
|
||||||
*/
|
|
||||||
public Map<String, String> getVariables() {
|
|
||||||
return this.variables;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.messaging.access.intercept;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
|
||||||
import org.apache.commons.logging.LogFactory;
|
|
||||||
|
|
||||||
import org.springframework.core.log.LogMessage;
|
|
||||||
import org.springframework.messaging.Message;
|
|
||||||
import org.springframework.security.authorization.AuthorizationDecision;
|
|
||||||
import org.springframework.security.authorization.AuthorizationManager;
|
|
||||||
import org.springframework.security.core.Authentication;
|
|
||||||
import org.springframework.security.messaging.util.matcher.MessageMatcher;
|
|
||||||
import org.springframework.security.messaging.util.matcher.MessageMatcherEntry;
|
|
||||||
import org.springframework.security.messaging.util.matcher.SimpDestinationMessageMatcher;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An {@link AuthorizationManager} which delegates to a specific
|
|
||||||
* {@link AuthorizationManager} based on a {@link MessageMatcher} evaluation.
|
|
||||||
*
|
|
||||||
* @author Josh Cummings
|
|
||||||
* @since 5.7
|
|
||||||
*/
|
|
||||||
public final class MessageMatcherDelegatingAuthorizationManager implements AuthorizationManager<Message<?>> {
|
|
||||||
|
|
||||||
private final Log logger = LogFactory.getLog(getClass());
|
|
||||||
|
|
||||||
private final List<MessageMatcherEntry<AuthorizationManager<MessageAuthorizationContext<?>>>> mappings;
|
|
||||||
|
|
||||||
private MessageMatcherDelegatingAuthorizationManager(
|
|
||||||
List<MessageMatcherEntry<AuthorizationManager<MessageAuthorizationContext<?>>>> mappings) {
|
|
||||||
Assert.notEmpty(mappings, "mappings cannot be empty");
|
|
||||||
this.mappings = mappings;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delegates to a specific {@link AuthorizationManager} based on a
|
|
||||||
* {@link MessageMatcher} evaluation.
|
|
||||||
* @param authentication the {@link Supplier} of the {@link Authentication} to check
|
|
||||||
* @param message the {@link Message} to check
|
|
||||||
* @return an {@link AuthorizationDecision}. If there is no {@link MessageMatcher}
|
|
||||||
* matching the message, or the {@link AuthorizationManager} could not decide, then
|
|
||||||
* null is returned
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public AuthorizationDecision check(Supplier<Authentication> authentication, Message<?> message) {
|
|
||||||
if (this.logger.isTraceEnabled()) {
|
|
||||||
this.logger.trace(LogMessage.format("Authorizing message"));
|
|
||||||
}
|
|
||||||
for (MessageMatcherEntry<AuthorizationManager<MessageAuthorizationContext<?>>> mapping : this.mappings) {
|
|
||||||
MessageMatcher<?> matcher = mapping.getMessageMatcher();
|
|
||||||
MessageAuthorizationContext<?> authorizationContext = authorizationContext(matcher, message);
|
|
||||||
if (authorizationContext != null) {
|
|
||||||
AuthorizationManager<MessageAuthorizationContext<?>> manager = mapping.getEntry();
|
|
||||||
if (this.logger.isTraceEnabled()) {
|
|
||||||
this.logger.trace(LogMessage.format("Checking authorization on message using %s", manager));
|
|
||||||
}
|
|
||||||
return manager.check(authentication, authorizationContext);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.logger.trace("Abstaining since did not find matching MessageMatcher");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MessageAuthorizationContext<?> authorizationContext(MessageMatcher<?> matcher, Message<?> message) {
|
|
||||||
if (!matcher.matches((Message) message)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (matcher instanceof SimpDestinationMessageMatcher) {
|
|
||||||
SimpDestinationMessageMatcher simp = (SimpDestinationMessageMatcher) matcher;
|
|
||||||
return new MessageAuthorizationContext<>(message, simp.extractPathVariables(message));
|
|
||||||
}
|
|
||||||
return new MessageAuthorizationContext<>(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a builder for {@link MessageMatcherDelegatingAuthorizationManager}.
|
|
||||||
* @return the new {@link MessageMatcherDelegatingAuthorizationManager.Builder}
|
|
||||||
* instance
|
|
||||||
*/
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A builder for {@link MessageMatcherDelegatingAuthorizationManager}.
|
|
||||||
*/
|
|
||||||
public static final class Builder {
|
|
||||||
|
|
||||||
private final List<MessageMatcherEntry<AuthorizationManager<MessageAuthorizationContext<?>>>> mappings = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Maps a {@link MessageMatcher} to an {@link AuthorizationManager}.
|
|
||||||
* @param matcher the {@link MessageMatcher} to use
|
|
||||||
* @param manager the {@link AuthorizationManager} to use
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager.Builder} for
|
|
||||||
* further customizations
|
|
||||||
*/
|
|
||||||
public MessageMatcherDelegatingAuthorizationManager.Builder add(MessageMatcher<?> matcher,
|
|
||||||
AuthorizationManager<MessageAuthorizationContext<?>> manager) {
|
|
||||||
Assert.notNull(matcher, "matcher cannot be null");
|
|
||||||
Assert.notNull(manager, "manager cannot be null");
|
|
||||||
this.mappings.add(new MessageMatcherEntry<>(matcher, manager));
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a {@link MessageMatcherDelegatingAuthorizationManager} instance.
|
|
||||||
* @return the {@link MessageMatcherDelegatingAuthorizationManager} instance
|
|
||||||
*/
|
|
||||||
public MessageMatcherDelegatingAuthorizationManager build() {
|
|
||||||
return new MessageMatcherDelegatingAuthorizationManager(this.mappings);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2022 the original author or authors.
|
* Copyright 2002-2016 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,9 +26,7 @@ import org.springframework.security.access.SecurityMetadataSource;
|
||||||
* @since 4.0
|
* @since 4.0
|
||||||
* @see ChannelSecurityInterceptor
|
* @see ChannelSecurityInterceptor
|
||||||
* @see DefaultMessageSecurityMetadataSource
|
* @see DefaultMessageSecurityMetadataSource
|
||||||
* @deprecated Use {@link MessageMatcherDelegatingAuthorizationManager} instead
|
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
|
||||||
public interface MessageSecurityMetadataSource extends SecurityMetadataSource {
|
public interface MessageSecurityMetadataSource extends SecurityMetadataSource {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2002-2022 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.messaging.util.matcher;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A rich object for associating a {@link MessageMatcher} to another object.
|
|
||||||
*
|
|
||||||
* @author Josh Cummings
|
|
||||||
* @since 5.7
|
|
||||||
*/
|
|
||||||
public class MessageMatcherEntry<T> {
|
|
||||||
|
|
||||||
private final MessageMatcher<?> messageMatcher;
|
|
||||||
|
|
||||||
private final T entry;
|
|
||||||
|
|
||||||
public MessageMatcherEntry(MessageMatcher requestMatcher, T entry) {
|
|
||||||
this.messageMatcher = requestMatcher;
|
|
||||||
this.entry = entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MessageMatcher<?> getMessageMatcher() {
|
|
||||||
return this.messageMatcher;
|
|
||||||
}
|
|
||||||
|
|
||||||
public T getEntry() {
|
|
||||||
return this.entry;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue