8 changed files with 1164 additions and 0 deletions
@ -0,0 +1,71 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.method.configuration; |
||||||
|
|
||||||
|
import org.springframework.context.annotation.AdviceMode; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.context.annotation.Import; |
||||||
|
import org.springframework.core.Ordered; |
||||||
|
|
||||||
|
import java.lang.annotation.Documented; |
||||||
|
import java.lang.annotation.Retention; |
||||||
|
import java.lang.annotation.Target; |
||||||
|
|
||||||
|
/** |
||||||
|
* |
||||||
|
* @author Rob Winch |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
@Retention(value = java.lang.annotation.RetentionPolicy.RUNTIME) |
||||||
|
@Target(value = { java.lang.annotation.ElementType.TYPE }) |
||||||
|
@Documented |
||||||
|
@Import({ ReactiveMethodSecuritySelector.class }) |
||||||
|
@Configuration |
||||||
|
public @interface EnableReactiveMethodSecurity { |
||||||
|
/** |
||||||
|
* Indicate whether subclass-based (CGLIB) proxies are to be created as opposed |
||||||
|
* to standard Java interface-based proxies. The default is {@code false}. <strong> |
||||||
|
* Applicable only if {@link #mode()} is set to {@link AdviceMode#PROXY}</strong>. |
||||||
|
* <p>Note that setting this attribute to {@code true} will affect <em>all</em> |
||||||
|
* Spring-managed beans requiring proxying, not just those marked with {@code @Cacheable}. |
||||||
|
* For example, other beans marked with Spring's {@code @Transactional} annotation will |
||||||
|
* be upgraded to subclass proxying at the same time. This approach has no negative |
||||||
|
* impact in practice unless one is explicitly expecting one type of proxy vs another, |
||||||
|
* e.g. in tests. |
||||||
|
*/ |
||||||
|
boolean proxyTargetClass() default false; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicate how security advice should be applied. The default is |
||||||
|
* {@link AdviceMode#PROXY}. |
||||||
|
* @see AdviceMode |
||||||
|
* |
||||||
|
* @return the {@link AdviceMode} to use |
||||||
|
*/ |
||||||
|
AdviceMode mode() default AdviceMode.PROXY; |
||||||
|
|
||||||
|
/** |
||||||
|
* Indicate the ordering of the execution of the security advisor when multiple |
||||||
|
* advices are applied at a specific joinpoint. The default is |
||||||
|
* {@link Ordered#LOWEST_PRECEDENCE}. |
||||||
|
* |
||||||
|
* @return the order the security advisor should be applied |
||||||
|
*/ |
||||||
|
int order() default Ordered.LOWEST_PRECEDENCE; |
||||||
|
} |
||||||
@ -0,0 +1,85 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.method.configuration; |
||||||
|
|
||||||
|
import org.springframework.beans.factory.config.BeanDefinition; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.context.annotation.Configuration; |
||||||
|
import org.springframework.context.annotation.ImportAware; |
||||||
|
import org.springframework.context.annotation.Role; |
||||||
|
import org.springframework.core.type.AnnotationMetadata; |
||||||
|
import org.springframework.security.access.expression.method.*; |
||||||
|
import org.springframework.security.access.intercept.aopalliance.MethodSecurityMetadataSourceAdvisor; |
||||||
|
import org.springframework.security.access.method.AbstractMethodSecurityMetadataSource; |
||||||
|
import org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource; |
||||||
|
import org.springframework.security.access.method.PrePostAdviceMethodInterceptor; |
||||||
|
import org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource; |
||||||
|
|
||||||
|
import java.util.Arrays; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Rob Winch |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
@Configuration |
||||||
|
class ReactiveMethodSecurityConfiguration implements ImportAware { |
||||||
|
private int advisorOrder; |
||||||
|
|
||||||
|
@Bean |
||||||
|
@Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
||||||
|
public MethodSecurityMetadataSourceAdvisor methodSecurityInterceptor(AbstractMethodSecurityMetadataSource source) throws Exception { |
||||||
|
MethodSecurityMetadataSourceAdvisor advisor = new MethodSecurityMetadataSourceAdvisor( |
||||||
|
"securityMethodInterceptor", source, "methodMetadataSource"); |
||||||
|
advisor.setOrder(advisorOrder); |
||||||
|
return advisor; |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public DelegatingMethodSecurityMetadataSource methodMetadataSource() { |
||||||
|
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory( |
||||||
|
new DefaultMethodSecurityExpressionHandler()); |
||||||
|
PrePostAnnotationSecurityMetadataSource prePostSource = new PrePostAnnotationSecurityMetadataSource( |
||||||
|
attributeFactory); |
||||||
|
return new DelegatingMethodSecurityMetadataSource(Arrays.asList(prePostSource)); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public PrePostAdviceMethodInterceptor securityMethodInterceptor(AbstractMethodSecurityMetadataSource source, MethodSecurityExpressionHandler handler) { |
||||||
|
|
||||||
|
ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice( |
||||||
|
handler); |
||||||
|
ExpressionBasedPreInvocationAdvice preAdvice = new ExpressionBasedPreInvocationAdvice(); |
||||||
|
preAdvice.setExpressionHandler(handler); |
||||||
|
|
||||||
|
PrePostAdviceMethodInterceptor result = new PrePostAdviceMethodInterceptor(source); |
||||||
|
result.setPostAdvice(postAdvice); |
||||||
|
result.setPreAdvice(preAdvice); |
||||||
|
return result; |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public DefaultMethodSecurityExpressionHandler methodSecurityExpressionHandler() { |
||||||
|
return new DefaultMethodSecurityExpressionHandler(); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public void setImportMetadata(AnnotationMetadata importMetadata) { |
||||||
|
this.advisorOrder = (int) importMetadata.getAnnotationAttributes(EnableReactiveMethodSecurity.class.getName()).get("order"); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.method.configuration; |
||||||
|
|
||||||
|
import org.springframework.cache.annotation.ProxyCachingConfiguration; |
||||||
|
import org.springframework.context.annotation.AdviceMode; |
||||||
|
import org.springframework.context.annotation.AdviceModeImportSelector; |
||||||
|
import org.springframework.context.annotation.AutoProxyRegistrar; |
||||||
|
import org.springframework.lang.Nullable; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Rob Winch |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
class ReactiveMethodSecuritySelector extends |
||||||
|
AdviceModeImportSelector<EnableReactiveMethodSecurity> { |
||||||
|
|
||||||
|
@Override |
||||||
|
protected String[] selectImports(AdviceMode adviceMode) { |
||||||
|
switch (adviceMode) { |
||||||
|
case PROXY: |
||||||
|
return getProxyImports(); |
||||||
|
default: |
||||||
|
throw new IllegalStateException("AdviceMode " + adviceMode + " is not supported"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Return the imports to use if the {@link AdviceMode} is set to {@link AdviceMode#PROXY}. |
||||||
|
* <p>Take care of adding the necessary JSR-107 import if it is available. |
||||||
|
*/ |
||||||
|
private String[] getProxyImports() { |
||||||
|
List<String> result = new ArrayList<>(); |
||||||
|
result.add(AutoProxyRegistrar.class.getName()); |
||||||
|
result.add(ReactiveMethodSecurityConfiguration.class.getName()); |
||||||
|
return result.toArray(new String[result.size()]); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,38 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.method.configuration; |
||||||
|
|
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.stereotype.Component; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Rob Winch |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
@Component |
||||||
|
public class Authz { |
||||||
|
public boolean check(long id) { |
||||||
|
return id % 2 == 0; |
||||||
|
} |
||||||
|
|
||||||
|
public boolean check(Authentication authentication, String message) { |
||||||
|
return message != null && |
||||||
|
message.contains(authentication.getName()); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,131 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.method.configuration; |
||||||
|
|
||||||
|
import org.reactivestreams.Publisher; |
||||||
|
import org.springframework.security.access.prepost.PostAuthorize; |
||||||
|
import org.springframework.security.access.prepost.PreAuthorize; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
public class DelegatingReactiveMessageService implements ReactiveMessageService { |
||||||
|
private final ReactiveMessageService delegate; |
||||||
|
|
||||||
|
public DelegatingReactiveMessageService(ReactiveMessageService delegate) { |
||||||
|
this.delegate = delegate; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Mono<String> monoFindById(long id) { |
||||||
|
return delegate.monoFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PreAuthorize("hasRole('ADMIN')") |
||||||
|
public Mono<String> monoPreAuthorizeHasRoleFindById( |
||||||
|
long id) { |
||||||
|
return delegate.monoPreAuthorizeHasRoleFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PostAuthorize("returnObject?.contains(authentication?.name)") |
||||||
|
public Mono<String> monoPostAuthorizeFindById( |
||||||
|
long id) { |
||||||
|
return delegate.monoPostAuthorizeFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PreAuthorize("@authz.check(#id)") |
||||||
|
public Mono<String> monoPreAuthorizeBeanFindById( |
||||||
|
long id) { |
||||||
|
return delegate.monoPreAuthorizeBeanFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PostAuthorize("@authz.check(authentication, returnObject)") |
||||||
|
public Mono<String> monoPostAuthorizeBeanFindById( |
||||||
|
long id) { |
||||||
|
return delegate.monoPostAuthorizeBeanFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Flux<String> fluxFindById(long id) { |
||||||
|
return delegate.fluxFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PreAuthorize("hasRole('ADMIN')") |
||||||
|
public Flux<String> fluxPreAuthorizeHasRoleFindById( |
||||||
|
long id) { |
||||||
|
return delegate.fluxPreAuthorizeHasRoleFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PostAuthorize("returnObject?.contains(authentication?.name)") |
||||||
|
public Flux<String> fluxPostAuthorizeFindById( |
||||||
|
long id) { |
||||||
|
return delegate.fluxPostAuthorizeFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PreAuthorize("@authz.check(#id)") |
||||||
|
public Flux<String> fluxPreAuthorizeBeanFindById( |
||||||
|
long id) { |
||||||
|
return delegate.fluxPreAuthorizeBeanFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PostAuthorize("@authz.check(authentication, returnObject)") |
||||||
|
public Flux<String> fluxPostAuthorizeBeanFindById( |
||||||
|
long id) { |
||||||
|
return delegate.fluxPostAuthorizeBeanFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Publisher<String> publisherFindById(long id) { |
||||||
|
return delegate.publisherFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PreAuthorize("hasRole('ADMIN')") |
||||||
|
public Publisher<String> publisherPreAuthorizeHasRoleFindById( |
||||||
|
long id) { |
||||||
|
return delegate.publisherPreAuthorizeHasRoleFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PostAuthorize("returnObject?.contains(authentication?.name)") |
||||||
|
public Publisher<String> publisherPostAuthorizeFindById( |
||||||
|
long id) { |
||||||
|
return delegate.publisherPostAuthorizeFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PreAuthorize("@authz.check(#id)") |
||||||
|
public Publisher<String> publisherPreAuthorizeBeanFindById( |
||||||
|
long id) { |
||||||
|
return delegate.publisherPreAuthorizeBeanFindById(id); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
@PostAuthorize("@authz.check(authentication, returnObject)") |
||||||
|
public Publisher<String> publisherPostAuthorizeBeanFindById( |
||||||
|
long id) { |
||||||
|
return delegate.publisherPostAuthorizeBeanFindById(id); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,593 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.method.configuration; |
||||||
|
|
||||||
|
import org.assertj.core.api.AssertionsForClassTypes; |
||||||
|
import org.junit.After; |
||||||
|
import org.junit.Test; |
||||||
|
import org.junit.runner.RunWith; |
||||||
|
import org.reactivestreams.Publisher; |
||||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||||
|
import org.springframework.context.annotation.Bean; |
||||||
|
import org.springframework.security.access.AccessDeniedException; |
||||||
|
import org.springframework.security.authentication.TestingAuthenticationToken; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.test.context.ContextConfiguration; |
||||||
|
import org.springframework.test.context.junit4.SpringRunner; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
import reactor.test.StepVerifier; |
||||||
|
import reactor.test.publisher.TestPublisher; |
||||||
|
import reactor.util.context.Context; |
||||||
|
|
||||||
|
import java.util.function.Function; |
||||||
|
|
||||||
|
import static org.mockito.Mockito.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Rob Winch |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
@RunWith(SpringRunner.class) |
||||||
|
@ContextConfiguration |
||||||
|
public class EnableReactiveMethodSecurityTests { |
||||||
|
@Autowired ReactiveMessageService messageService; |
||||||
|
ReactiveMessageService delegate; |
||||||
|
TestPublisher<String> result = TestPublisher.create(); |
||||||
|
|
||||||
|
Function<Context, Context> withAdmin = context -> context.put(Authentication.class, Mono |
||||||
|
.just(new TestingAuthenticationToken("admin","password","ROLE_USER", "ROLE_ADMIN"))); |
||||||
|
Function<Context, Context> withUser = context -> context.put(Authentication.class, Mono |
||||||
|
.just(new TestingAuthenticationToken("user","password","ROLE_USER"))); |
||||||
|
|
||||||
|
@After |
||||||
|
public void cleanup() { |
||||||
|
reset(delegate); |
||||||
|
} |
||||||
|
|
||||||
|
@Autowired |
||||||
|
public void setConfig(Config config) { |
||||||
|
this.delegate = config.delegate; |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoWhenPermitAllThenAopDoesNotSubscribe() { |
||||||
|
when(this.delegate.monoFindById(1L)).thenReturn(Mono.from(result)); |
||||||
|
|
||||||
|
this.delegate.monoFindById(1L); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoWhenPermitAllThenSuccess() { |
||||||
|
when(this.delegate.monoFindById(1L)).thenReturn(Mono.just("success")); |
||||||
|
|
||||||
|
StepVerifier.create(this.delegate.monoFindById(1L)) |
||||||
|
.expectNext("success") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPreAuthorizeHasRoleWhenGrantedThenSuccess() { |
||||||
|
when(this.delegate.monoPreAuthorizeHasRoleFindById(1L)).thenReturn(Mono.just("result")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeHasRoleFindById(1L) |
||||||
|
.contextStart(withAdmin); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("result") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPreAuthorizeHasRoleWhenNoAuthenticationThenDenied() { |
||||||
|
when(this.delegate.monoPreAuthorizeHasRoleFindById(1L)).thenReturn(Mono.from(result)); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeHasRoleFindById(1L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPreAuthorizeHasRoleWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.monoPreAuthorizeHasRoleFindById(1L)).thenReturn(Mono.from(result)); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeHasRoleFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPreAuthorizeBeanWhenGrantedThenSuccess() { |
||||||
|
when(this.delegate.monoPreAuthorizeBeanFindById(2L)).thenReturn(Mono.just("result")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(2L) |
||||||
|
.contextStart(withAdmin); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("result") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPreAuthorizeBeanWhenNotAuthenticatedAndGrantedThenSuccess() { |
||||||
|
when(this.delegate.monoPreAuthorizeBeanFindById(2L)).thenReturn(Mono.just("result")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(2L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("result") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPreAuthorizeBeanWhenNoAuthenticationThenDenied() { |
||||||
|
when(this.delegate.monoPreAuthorizeBeanFindById(1L)).thenReturn(Mono.from(result)); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(1L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPreAuthorizeBeanWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.monoPreAuthorizeBeanFindById(1L)).thenReturn(Mono.from(result)); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPreAuthorizeBeanFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPostAuthorizeWhenAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.monoPostAuthorizeFindById(1L)).thenReturn(Mono.just("user")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("user") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPostAuthorizeWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.monoPostAuthorizeBeanFindById(1L)).thenReturn(Mono.just("not-authorized")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPostAuthorizeWhenBeanAndAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.monoPostAuthorizeBeanFindById(2L)).thenReturn(Mono.just("user")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(2L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("user") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPostAuthorizeWhenBeanAndNotAuthenticatedAndAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.monoPostAuthorizeBeanFindById(2L)).thenReturn(Mono.just("anonymous")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(2L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("anonymous") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void monoPostAuthorizeWhenBeanAndNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.monoPostAuthorizeBeanFindById(1L)).thenReturn(Mono.just("not-authorized")); |
||||||
|
|
||||||
|
Mono<String> findById = this.messageService.monoPostAuthorizeBeanFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
} |
||||||
|
|
||||||
|
// Flux tests
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxWhenPermitAllThenAopDoesNotSubscribe() { |
||||||
|
when(this.delegate.fluxFindById(1L)).thenReturn(Flux.from(result)); |
||||||
|
|
||||||
|
this.delegate.fluxFindById(1L); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxWhenPermitAllThenSuccess() { |
||||||
|
when(this.delegate.fluxFindById(1L)).thenReturn(Flux.just("success")); |
||||||
|
|
||||||
|
StepVerifier.create(this.delegate.fluxFindById(1L)) |
||||||
|
.expectNext("success") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPreAuthorizeHasRoleWhenGrantedThenSuccess() { |
||||||
|
when(this.delegate.fluxPreAuthorizeHasRoleFindById(1L)).thenReturn(Flux.just("result")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeHasRoleFindById(1L) |
||||||
|
.contextStart(withAdmin); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.consumeNextWith( s -> AssertionsForClassTypes.assertThat(s).isEqualTo("result")) |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPreAuthorizeHasRoleWhenNoAuthenticationThenDenied() { |
||||||
|
when(this.delegate.fluxPreAuthorizeHasRoleFindById(1L)).thenReturn(Flux.from(result)); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeHasRoleFindById(1L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPreAuthorizeHasRoleWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.fluxPreAuthorizeHasRoleFindById(1L)).thenReturn(Flux.from(result)); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeHasRoleFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPreAuthorizeBeanWhenGrantedThenSuccess() { |
||||||
|
when(this.delegate.fluxPreAuthorizeBeanFindById(2L)).thenReturn(Flux.just("result")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(2L) |
||||||
|
.contextStart(withAdmin); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("result") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPreAuthorizeBeanWhenNotAuthenticatedAndGrantedThenSuccess() { |
||||||
|
when(this.delegate.fluxPreAuthorizeBeanFindById(2L)).thenReturn(Flux.just("result")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(2L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("result") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPreAuthorizeBeanWhenNoAuthenticationThenDenied() { |
||||||
|
when(this.delegate.fluxPreAuthorizeBeanFindById(1L)).thenReturn(Flux.from(result)); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(1L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPreAuthorizeBeanWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.fluxPreAuthorizeBeanFindById(1L)).thenReturn(Flux.from(result)); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPreAuthorizeBeanFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPostAuthorizeWhenAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.fluxPostAuthorizeFindById(1L)).thenReturn(Flux.just("user")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("user") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPostAuthorizeWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.fluxPostAuthorizeBeanFindById(1L)).thenReturn(Flux.just("not-authorized")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPostAuthorizeWhenBeanAndAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.fluxPostAuthorizeBeanFindById(2L)).thenReturn(Flux.just("user")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(2L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("user") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPostAuthorizeWhenBeanAndNotAuthenticatedAndAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.fluxPostAuthorizeBeanFindById(2L)).thenReturn(Flux.just("anonymous")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(2L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("anonymous") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void fluxPostAuthorizeWhenBeanAndNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.fluxPostAuthorizeBeanFindById(1L)).thenReturn(Flux.just("not-authorized")); |
||||||
|
|
||||||
|
Flux<String> findById = this.messageService.fluxPostAuthorizeBeanFindById(1L) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
} |
||||||
|
|
||||||
|
// Publisher tests
|
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherWhenPermitAllThenAopDoesNotSubscribe() { |
||||||
|
when(this.delegate.publisherFindById(1L)).thenReturn(result); |
||||||
|
|
||||||
|
this.delegate.publisherFindById(1L); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherWhenPermitAllThenSuccess() { |
||||||
|
when(this.delegate.publisherFindById(1L)).thenReturn(publisherJust("success")); |
||||||
|
|
||||||
|
StepVerifier.create(this.delegate.publisherFindById(1L)) |
||||||
|
.expectNext("success") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPreAuthorizeHasRoleWhenGrantedThenSuccess() { |
||||||
|
when(this.delegate.publisherPreAuthorizeHasRoleFindById(1L)).thenReturn(publisherJust("result")); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeHasRoleFindById(1L)) |
||||||
|
.contextStart(withAdmin); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.consumeNextWith( s -> AssertionsForClassTypes.assertThat(s).isEqualTo("result")) |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPreAuthorizeHasRoleWhenNoAuthenticationThenDenied() { |
||||||
|
when(this.delegate.publisherPreAuthorizeHasRoleFindById(1L)).thenReturn(result); |
||||||
|
|
||||||
|
Publisher<String> findById = this.messageService.publisherPreAuthorizeHasRoleFindById(1L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPreAuthorizeHasRoleWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.publisherPreAuthorizeHasRoleFindById(1L)).thenReturn(result); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeHasRoleFindById(1L)) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPreAuthorizeBeanWhenGrantedThenSuccess() { |
||||||
|
when(this.delegate.publisherPreAuthorizeBeanFindById(2L)).thenReturn(publisherJust("result")); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeBeanFindById(2L)) |
||||||
|
.contextStart(withAdmin); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("result") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPreAuthorizeBeanWhenNotAuthenticatedAndGrantedThenSuccess() { |
||||||
|
when(this.delegate.publisherPreAuthorizeBeanFindById(2L)).thenReturn(publisherJust("result")); |
||||||
|
|
||||||
|
Publisher<String> findById = this.messageService.publisherPreAuthorizeBeanFindById(2L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("result") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPreAuthorizeBeanWhenNoAuthenticationThenDenied() { |
||||||
|
when(this.delegate.publisherPreAuthorizeBeanFindById(1L)).thenReturn(result); |
||||||
|
|
||||||
|
Publisher<String> findById = this.messageService.publisherPreAuthorizeBeanFindById(1L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPreAuthorizeBeanWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.publisherPreAuthorizeBeanFindById(1L)).thenReturn(result); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPreAuthorizeBeanFindById(1L)) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
|
||||||
|
result.assertNoSubscribers(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPostAuthorizeWhenAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.publisherPostAuthorizeFindById(1L)).thenReturn(publisherJust("user")); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeFindById(1L)) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("user") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPostAuthorizeWhenNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.publisherPostAuthorizeBeanFindById(1L)).thenReturn(publisherJust("not-authorized")); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeBeanFindById(1L)) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPostAuthorizeWhenBeanAndAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.publisherPostAuthorizeBeanFindById(2L)).thenReturn(publisherJust("user")); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeBeanFindById(2L)) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("user") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPostAuthorizeWhenBeanAndNotAuthenticatedAndAuthorizedThenSuccess() { |
||||||
|
when(this.delegate.publisherPostAuthorizeBeanFindById(2L)).thenReturn(publisherJust("anonymous")); |
||||||
|
|
||||||
|
Publisher<String> findById = this.messageService.publisherPostAuthorizeBeanFindById(2L); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectNext("anonymous") |
||||||
|
.verifyComplete(); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void publisherPostAuthorizeWhenBeanAndNotAuthorizedThenDenied() { |
||||||
|
when(this.delegate.publisherPostAuthorizeBeanFindById(1L)).thenReturn(publisherJust("not-authorized")); |
||||||
|
|
||||||
|
Publisher<String> findById = Flux.from(this.messageService.publisherPostAuthorizeBeanFindById(1L)) |
||||||
|
.contextStart(withUser); |
||||||
|
StepVerifier |
||||||
|
.create(findById) |
||||||
|
.expectError(AccessDeniedException.class) |
||||||
|
.verify(); |
||||||
|
} |
||||||
|
|
||||||
|
static <T> Publisher<T> publisher(Flux<T> flux) { |
||||||
|
return subscriber -> flux.subscribe(subscriber); |
||||||
|
} |
||||||
|
|
||||||
|
static <T> Publisher<T> publisherJust(T... data) { |
||||||
|
return publisher(Flux.just(data)); |
||||||
|
} |
||||||
|
|
||||||
|
@EnableReactiveMethodSecurity |
||||||
|
static class Config { |
||||||
|
ReactiveMessageService delegate = mock(ReactiveMessageService.class); |
||||||
|
|
||||||
|
@Bean |
||||||
|
public DelegatingReactiveMessageService defaultMessageService() { |
||||||
|
return new DelegatingReactiveMessageService(delegate); |
||||||
|
} |
||||||
|
|
||||||
|
@Bean |
||||||
|
public Authz authz() { |
||||||
|
return new Authz(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.method.configuration; |
||||||
|
|
||||||
|
import org.reactivestreams.Publisher; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
|
||||||
|
public interface ReactiveMessageService { |
||||||
|
Mono<String> monoFindById(long id); |
||||||
|
Mono<String> monoPreAuthorizeHasRoleFindById(long id); |
||||||
|
Mono<String> monoPostAuthorizeFindById(long id); |
||||||
|
Mono<String> monoPreAuthorizeBeanFindById(long id); |
||||||
|
Mono<String> monoPostAuthorizeBeanFindById(long id); |
||||||
|
|
||||||
|
Flux<String> fluxFindById(long id); |
||||||
|
Flux<String> fluxPreAuthorizeHasRoleFindById(long id); |
||||||
|
Flux<String> fluxPostAuthorizeFindById(long id); |
||||||
|
Flux<String> fluxPreAuthorizeBeanFindById(long id); |
||||||
|
Flux<String> fluxPostAuthorizeBeanFindById(long id); |
||||||
|
|
||||||
|
Publisher<String> publisherFindById(long id); |
||||||
|
Publisher<String> publisherPreAuthorizeHasRoleFindById(long id); |
||||||
|
Publisher<String> publisherPostAuthorizeFindById(long id); |
||||||
|
Publisher<String> publisherPreAuthorizeBeanFindById(long id); |
||||||
|
Publisher<String> publisherPostAuthorizeBeanFindById(long id); |
||||||
|
} |
||||||
@ -0,0 +1,147 @@ |
|||||||
|
/* |
||||||
|
* |
||||||
|
* * Copyright 2002-2017 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 |
||||||
|
* * |
||||||
|
* * http://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.access.method; |
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInterceptor; |
||||||
|
import org.aopalliance.intercept.MethodInvocation; |
||||||
|
import org.reactivestreams.Publisher; |
||||||
|
import org.springframework.security.access.AccessDeniedException; |
||||||
|
import org.springframework.security.access.ConfigAttribute; |
||||||
|
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.expression.method.ExpressionBasedPostInvocationAdvice; |
||||||
|
import org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice; |
||||||
|
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler; |
||||||
|
import org.springframework.security.access.method.MethodSecurityMetadataSource; |
||||||
|
import org.springframework.security.access.prepost.PostInvocationAttribute; |
||||||
|
import org.springframework.security.access.prepost.PostInvocationAuthorizationAdvice; |
||||||
|
import org.springframework.security.access.prepost.PreInvocationAttribute; |
||||||
|
import org.springframework.security.access.prepost.PreInvocationAuthorizationAdvice; |
||||||
|
import org.springframework.security.authentication.AnonymousAuthenticationToken; |
||||||
|
import org.springframework.security.core.Authentication; |
||||||
|
import org.springframework.security.core.authority.AuthorityUtils; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import reactor.core.Exceptions; |
||||||
|
import reactor.core.publisher.Flux; |
||||||
|
import reactor.core.publisher.Mono; |
||||||
|
import reactor.util.context.Context; |
||||||
|
|
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Collection; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Rob Winch |
||||||
|
* @since 5.0 |
||||||
|
*/ |
||||||
|
public class PrePostAdviceMethodInterceptor implements MethodInterceptor { |
||||||
|
private Authentication anonymous = new AnonymousAuthenticationToken("key", "anonymous", |
||||||
|
AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS")); |
||||||
|
|
||||||
|
private final MethodSecurityMetadataSource attributeSource; |
||||||
|
|
||||||
|
private PostInvocationAuthorizationAdvice postAdvice; |
||||||
|
|
||||||
|
private PreInvocationAuthorizationAdvice preAdvice; |
||||||
|
|
||||||
|
public PrePostAdviceMethodInterceptor(MethodSecurityMetadataSource attributeSource) { |
||||||
|
this.attributeSource = attributeSource; |
||||||
|
|
||||||
|
MethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler(); |
||||||
|
this.postAdvice = new ExpressionBasedPostInvocationAdvice(handler); |
||||||
|
this.preAdvice = new ExpressionBasedPreInvocationAdvice(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setPostAdvice(PostInvocationAuthorizationAdvice postAdvice) { |
||||||
|
Assert.notNull(postAdvice, "postAdvice cannot be null"); |
||||||
|
this.postAdvice = postAdvice; |
||||||
|
} |
||||||
|
|
||||||
|
public void setPreAdvice(PreInvocationAuthorizationAdvice preAdvice) { |
||||||
|
Assert.notNull(preAdvice, "preAdvice cannot be null"); |
||||||
|
this.preAdvice = preAdvice; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public Object invoke(final MethodInvocation invocation) |
||||||
|
throws Throwable { |
||||||
|
Method method = invocation.getMethod(); |
||||||
|
Class<?> returnType = method.getReturnType(); |
||||||
|
Class<?> targetClass = invocation.getThis().getClass(); |
||||||
|
Collection<ConfigAttribute> attributes = this.attributeSource |
||||||
|
.getAttributes(method, targetClass); |
||||||
|
|
||||||
|
PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes); |
||||||
|
Mono<Authentication> toInvoke = Mono.currentContext() |
||||||
|
.defaultIfEmpty(Context.empty()) |
||||||
|
.flatMap( cxt -> cxt.getOrDefault(Authentication.class, Mono.just(anonymous))) |
||||||
|
.filter( auth -> this.preAdvice.before(auth, invocation, preAttr)) |
||||||
|
.switchIfEmpty(Mono.error(new AccessDeniedException("Denied"))); |
||||||
|
|
||||||
|
|
||||||
|
PostInvocationAttribute attr = findPostInvocationAttribute(attributes); |
||||||
|
|
||||||
|
if(Mono.class.isAssignableFrom(returnType)) { |
||||||
|
return toInvoke |
||||||
|
.flatMap( auth -> this.<Mono<?>>proceed(invocation) |
||||||
|
.map( r -> attr == null ? r : this.postAdvice.after(auth, invocation, attr, r)) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
if(Flux.class.isAssignableFrom(returnType)) { |
||||||
|
return toInvoke |
||||||
|
.flatMapMany( auth -> this.<Flux<?>>proceed(invocation) |
||||||
|
.map( r -> attr == null ? r : this.postAdvice.after(auth, invocation, attr, r)) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
return toInvoke |
||||||
|
.flatMapMany( auth -> Flux.from(this.<Publisher<?>>proceed(invocation)) |
||||||
|
.map( r -> attr == null ? r : this.postAdvice.after(auth, invocation, attr, r)) |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
private<T extends Publisher<?>> T proceed(final MethodInvocation invocation) { |
||||||
|
try { |
||||||
|
return (T) invocation.proceed(); |
||||||
|
} catch(Throwable throwable) { |
||||||
|
throw Exceptions.propagate(throwable); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private static PostInvocationAttribute findPostInvocationAttribute( |
||||||
|
Collection<ConfigAttribute> config) { |
||||||
|
for (ConfigAttribute attribute : config) { |
||||||
|
if (attribute instanceof PostInvocationAttribute) { |
||||||
|
return (PostInvocationAttribute) attribute; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
|
||||||
|
private PreInvocationAttribute findPreInvocationAttribute( |
||||||
|
Collection<ConfigAttribute> config) { |
||||||
|
for (ConfigAttribute attribute : config) { |
||||||
|
if (attribute instanceof PreInvocationAttribute) { |
||||||
|
return (PreInvocationAttribute) attribute; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return null; |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue