36 changed files with 2461 additions and 61 deletions
@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.method.configuration; |
||||
|
||||
public class MyMasker { |
||||
|
||||
public String getMask(String value) { |
||||
return value + "-masked"; |
||||
} |
||||
|
||||
public String getMask() { |
||||
return "mask"; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,220 @@
@@ -0,0 +1,220 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.method.configuration; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
import org.junit.jupiter.api.extension.ExtendWith; |
||||
import reactor.test.StepVerifier; |
||||
|
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.security.access.AccessDeniedException; |
||||
import org.springframework.security.config.test.SpringTestContext; |
||||
import org.springframework.security.config.test.SpringTestContextExtension; |
||||
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners; |
||||
import org.springframework.security.test.context.support.WithMockUser; |
||||
import org.springframework.test.context.junit.jupiter.SpringExtension; |
||||
|
||||
@ExtendWith({ SpringExtension.class, SpringTestContextExtension.class }) |
||||
@SecurityTestExecutionListeners |
||||
public class PrePostReactiveMethodSecurityConfigurationTests { |
||||
|
||||
public final SpringTestContext spring = new SpringTestContext(this); |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void getCardNumberWhenPostAuthorizeAndNotAdminThenReturnMasked() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.CardNumberMaskingPostProcessor.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.postAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111")) |
||||
.expectNext("****-****-****-1111") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void getCardNumberWhenPreAuthorizeAndNotAdminThenReturnMasked() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.preAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111")) |
||||
.expectNext("***") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void getCardNumberWhenPreAuthorizeAndNotAdminAndChildHandlerThenResolveCorrectHandlerAndReturnMasked() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class, |
||||
ReactiveMethodSecurityService.StartMaskingHandlerChild.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.preAuthorizeWithHandlerChildGetCardNumberIfAdmin("4444-3333-2222-1111")) |
||||
.expectNext("***-child") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenNotHandled() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.StarMaskingHandler.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.preAuthorizeThrowAccessDeniedManually()) |
||||
.expectError(AccessDeniedException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationHandler.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.preAuthorizeDeniedMethodWithMaskAnnotation()) |
||||
.expectNext("methodmask") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationHandler.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.preAuthorizeDeniedMethodWithNoMaskAnnotation()) |
||||
.expectNext("classmask") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenNotHandled() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.PostMaskingPostProcessor.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.postAuthorizeThrowAccessDeniedManually()) |
||||
.expectError(AccessDeniedException.class) |
||||
.verify(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, ReactiveMethodSecurityService.NullPostProcessor.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.postAuthorizeDeniedWithNullDenied()).verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.postAuthorizeDeniedMethodWithMaskAnnotation()) |
||||
.expectNext("methodmask") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.postAuthorizeDeniedMethodWithNoMaskAnnotation()) |
||||
.expectNext("classmask") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class, MyMasker.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.postAuthorizeWithMaskAnnotationUsingBean()) |
||||
.expectNext("ok-masked") |
||||
.verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
void postAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationPostProcessor.class, MyMasker.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.postAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser |
||||
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationHandler.class, MyMasker.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("mask").verifyComplete(); |
||||
} |
||||
|
||||
@Test |
||||
@WithMockUser(roles = "ADMIN") |
||||
void preAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() { |
||||
this.spring |
||||
.register(MethodSecurityServiceEnabledConfig.class, |
||||
ReactiveMethodSecurityService.MaskAnnotationHandler.class, MyMasker.class) |
||||
.autowire(); |
||||
ReactiveMethodSecurityService service = this.spring.getContext().getBean(ReactiveMethodSecurityService.class); |
||||
StepVerifier.create(service.preAuthorizeWithMaskAnnotationUsingBean()).expectNext("ok").verifyComplete(); |
||||
} |
||||
|
||||
@Configuration |
||||
@EnableReactiveMethodSecurity |
||||
static class MethodSecurityServiceEnabledConfig { |
||||
|
||||
@Bean |
||||
ReactiveMethodSecurityService methodSecurityService() { |
||||
return new ReactiveMethodSecurityServiceImpl(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,222 @@
@@ -0,0 +1,222 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.method.configuration; |
||||
|
||||
import java.lang.annotation.ElementType; |
||||
import java.lang.annotation.Inherited; |
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
import java.lang.annotation.Target; |
||||
|
||||
import org.aopalliance.intercept.MethodInvocation; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.context.ApplicationContext; |
||||
import org.springframework.core.annotation.AnnotationUtils; |
||||
import org.springframework.expression.EvaluationContext; |
||||
import org.springframework.expression.Expression; |
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler; |
||||
import org.springframework.security.access.prepost.PostAuthorize; |
||||
import org.springframework.security.access.prepost.PreAuthorize; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedHandler; |
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; |
||||
import org.springframework.security.authorization.method.MethodInvocationResult; |
||||
import org.springframework.security.core.context.SecurityContextHolder; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* @author Rob Winch |
||||
*/ |
||||
@ReactiveMethodSecurityService.Mask("classmask") |
||||
public interface ReactiveMethodSecurityService { |
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class) |
||||
Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber); |
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class) |
||||
Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber); |
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class) |
||||
Mono<String> preAuthorizeThrowAccessDeniedManually(); |
||||
|
||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class) |
||||
Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber); |
||||
|
||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class) |
||||
Mono<String> postAuthorizeThrowAccessDeniedManually(); |
||||
|
||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class) |
||||
@Mask("methodmask") |
||||
Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation(); |
||||
|
||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class) |
||||
Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation(); |
||||
|
||||
@NullDenied(role = "ADMIN") |
||||
Mono<String> postAuthorizeDeniedWithNullDenied(); |
||||
|
||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class) |
||||
@Mask("methodmask") |
||||
Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation(); |
||||
|
||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class) |
||||
Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation(); |
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class) |
||||
@Mask(expression = "@myMasker.getMask()") |
||||
Mono<String> preAuthorizeWithMaskAnnotationUsingBean(); |
||||
|
||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class) |
||||
@Mask(expression = "@myMasker.getMask(returnObject)") |
||||
Mono<String> postAuthorizeWithMaskAnnotationUsingBean(); |
||||
|
||||
class StarMaskingHandler implements MethodAuthorizationDeniedHandler { |
||||
|
||||
@Override |
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { |
||||
return "***"; |
||||
} |
||||
|
||||
} |
||||
|
||||
class StartMaskingHandlerChild extends StarMaskingHandler { |
||||
|
||||
@Override |
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { |
||||
return super.handle(methodInvocation, result) + "-child"; |
||||
} |
||||
|
||||
} |
||||
|
||||
class MaskAnnotationHandler implements MethodAuthorizationDeniedHandler { |
||||
|
||||
MaskValueResolver maskValueResolver; |
||||
|
||||
MaskAnnotationHandler(ApplicationContext context) { |
||||
this.maskValueResolver = new MaskValueResolver(context); |
||||
} |
||||
|
||||
@Override |
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { |
||||
Mask mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod(), Mask.class); |
||||
if (mask == null) { |
||||
mask = AnnotationUtils.getAnnotation(methodInvocation.getMethod().getDeclaringClass(), Mask.class); |
||||
} |
||||
return this.maskValueResolver.resolveValue(mask, methodInvocation, null); |
||||
} |
||||
|
||||
} |
||||
|
||||
class MaskAnnotationPostProcessor implements MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
MaskValueResolver maskValueResolver; |
||||
|
||||
MaskAnnotationPostProcessor(ApplicationContext context) { |
||||
this.maskValueResolver = new MaskValueResolver(context); |
||||
} |
||||
|
||||
@Override |
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, |
||||
AuthorizationResult authorizationResult) { |
||||
MethodInvocation mi = methodInvocationResult.getMethodInvocation(); |
||||
Mask mask = AnnotationUtils.getAnnotation(mi.getMethod(), Mask.class); |
||||
if (mask == null) { |
||||
mask = AnnotationUtils.getAnnotation(mi.getMethod().getDeclaringClass(), Mask.class); |
||||
} |
||||
return this.maskValueResolver.resolveValue(mask, mi, methodInvocationResult.getResult()); |
||||
} |
||||
|
||||
} |
||||
|
||||
class MaskValueResolver { |
||||
|
||||
DefaultMethodSecurityExpressionHandler expressionHandler; |
||||
|
||||
MaskValueResolver(ApplicationContext context) { |
||||
this.expressionHandler = new DefaultMethodSecurityExpressionHandler(); |
||||
this.expressionHandler.setApplicationContext(context); |
||||
} |
||||
|
||||
Mono<String> resolveValue(Mask mask, MethodInvocation mi, Object returnObject) { |
||||
if (StringUtils.hasText(mask.value())) { |
||||
return Mono.just(mask.value()); |
||||
} |
||||
Expression expression = this.expressionHandler.getExpressionParser().parseExpression(mask.expression()); |
||||
EvaluationContext evaluationContext = this.expressionHandler |
||||
.createEvaluationContext(() -> SecurityContextHolder.getContext().getAuthentication(), mi); |
||||
if (returnObject != null) { |
||||
this.expressionHandler.setReturnObject(returnObject, evaluationContext); |
||||
} |
||||
return Mono.just(expression.getValue(evaluationContext, String.class)); |
||||
} |
||||
|
||||
} |
||||
|
||||
class PostMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
@Override |
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { |
||||
return "***"; |
||||
} |
||||
|
||||
} |
||||
|
||||
class CardNumberMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
static String MASK = "****-****-****-"; |
||||
|
||||
@Override |
||||
public Object postProcessResult(MethodInvocationResult contextObject, AuthorizationResult result) { |
||||
String cardNumber = (String) contextObject.getResult(); |
||||
return MASK + cardNumber.substring(cardNumber.length() - 4); |
||||
} |
||||
|
||||
} |
||||
|
||||
class NullPostProcessor implements MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
@Override |
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, |
||||
AuthorizationResult authorizationResult) { |
||||
return null; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE }) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Inherited |
||||
@interface Mask { |
||||
|
||||
String value() default ""; |
||||
|
||||
String expression() default ""; |
||||
|
||||
} |
||||
|
||||
@Target({ ElementType.METHOD, ElementType.TYPE }) |
||||
@Retention(RetentionPolicy.RUNTIME) |
||||
@Inherited |
||||
@PostAuthorize(value = "hasRole('{value}')", postProcessorClass = NullPostProcessor.class) |
||||
@interface NullDenied { |
||||
|
||||
String role(); |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,85 @@
@@ -0,0 +1,85 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.method.configuration; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.security.access.AccessDeniedException; |
||||
|
||||
public class ReactiveMethodSecurityServiceImpl implements ReactiveMethodSecurityService { |
||||
|
||||
@Override |
||||
public Mono<String> preAuthorizeGetCardNumberIfAdmin(String cardNumber) { |
||||
return Mono.just(cardNumber); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber) { |
||||
return Mono.just(cardNumber); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> preAuthorizeThrowAccessDeniedManually() { |
||||
return Mono.error(new AccessDeniedException("Access Denied")); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> postAuthorizeGetCardNumberIfAdmin(String cardNumber) { |
||||
return Mono.just(cardNumber); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> postAuthorizeThrowAccessDeniedManually() { |
||||
return Mono.error(new AccessDeniedException("Access Denied")); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> preAuthorizeDeniedMethodWithMaskAnnotation() { |
||||
return Mono.just("ok"); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> preAuthorizeDeniedMethodWithNoMaskAnnotation() { |
||||
return Mono.just("ok"); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> postAuthorizeDeniedWithNullDenied() { |
||||
return Mono.just("ok"); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> postAuthorizeDeniedMethodWithMaskAnnotation() { |
||||
return Mono.just("ok"); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> postAuthorizeDeniedMethodWithNoMaskAnnotation() { |
||||
return Mono.just("ok"); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> preAuthorizeWithMaskAnnotationUsingBean() { |
||||
return Mono.just("ok"); |
||||
} |
||||
|
||||
@Override |
||||
public Mono<String> postAuthorizeWithMaskAnnotationUsingBean() { |
||||
return Mono.just("ok"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.method.configuration; |
||||
|
||||
import org.springframework.security.access.prepost.PostAuthorize; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
import org.springframework.security.authorization.method.MethodAuthorizationDeniedPostProcessor; |
||||
import org.springframework.security.authorization.method.MethodInvocationResult; |
||||
|
||||
public class UserRecordWithEmailProtected { |
||||
|
||||
private final String name; |
||||
|
||||
private final String email; |
||||
|
||||
public UserRecordWithEmailProtected(String name, String email) { |
||||
this.name = name; |
||||
this.email = email; |
||||
} |
||||
|
||||
public String name() { |
||||
return this.name; |
||||
} |
||||
|
||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = EmailMaskingPostProcessor.class) |
||||
public String email() { |
||||
return this.email; |
||||
} |
||||
|
||||
public static class EmailMaskingPostProcessor implements MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
@Override |
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, |
||||
AuthorizationResult authorizationResult) { |
||||
String email = (String) methodInvocationResult.getResult(); |
||||
return email.replaceAll("(^[^@]{3}|(?!^)\\G)[^@]", "$1*"); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization; |
||||
|
||||
import org.springframework.security.access.AccessDeniedException; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An {@link AccessDeniedException} that contains the {@link AuthorizationResult} |
||||
* |
||||
* @author Marcus da Coregio |
||||
* @since 6.3 |
||||
*/ |
||||
public class AuthorizationDeniedException extends AccessDeniedException { |
||||
|
||||
private final AuthorizationResult result; |
||||
|
||||
public AuthorizationDeniedException(String msg, AuthorizationResult authorizationResult) { |
||||
super(msg); |
||||
Assert.notNull(authorizationResult, "authorizationResult cannot be null"); |
||||
Assert.isTrue(!authorizationResult.isGranted(), "Granted authorization results are not supported"); |
||||
this.result = authorizationResult; |
||||
} |
||||
|
||||
public AuthorizationResult getAuthorizationResult() { |
||||
return this.result; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.aopalliance.intercept.MethodInvocation; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
|
||||
/** |
||||
* An interface used to define a strategy to handle denied method invocations |
||||
* |
||||
* @author Marcus da Coregio |
||||
* @since 6.3 |
||||
* @see org.springframework.security.access.prepost.PreAuthorize |
||||
*/ |
||||
public interface MethodAuthorizationDeniedHandler { |
||||
|
||||
/** |
||||
* Handle denied method invocations, implementations might either throw an |
||||
* {@link org.springframework.security.access.AccessDeniedException} or a replacement |
||||
* result instead of invoking the method, e.g. a masked value. |
||||
* @param methodInvocation the {@link MethodInvocation} related to the authorization |
||||
* denied |
||||
* @param authorizationResult the authorization denied result |
||||
* @return a replacement result for the denied method invocation, or null, or a |
||||
* {@link reactor.core.publisher.Mono} for reactive applications |
||||
*/ |
||||
@Nullable |
||||
Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult); |
||||
|
||||
} |
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
|
||||
/** |
||||
* An interface to define a strategy to handle denied method invocation results |
||||
* |
||||
* @author Marcus da Coregio |
||||
* @since 6.3 |
||||
* @see org.springframework.security.access.prepost.PostAuthorize |
||||
*/ |
||||
public interface MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
/** |
||||
* Post-process the denied result produced by a method invocation, implementations |
||||
* might either throw an |
||||
* {@link org.springframework.security.access.AccessDeniedException} or return a |
||||
* replacement result instead of the denied result, e.g. a masked value. |
||||
* @param methodInvocationResult the object containing the method invocation and the |
||||
* result produced |
||||
* @param authorizationResult the {@link AuthorizationResult} containing the |
||||
* authorization denied details |
||||
* @return a replacement result for the denied result, or null, or a |
||||
* {@link reactor.core.publisher.Mono} for reactive applications |
||||
*/ |
||||
@Nullable |
||||
Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult authorizationResult); |
||||
|
||||
} |
||||
@ -0,0 +1,41 @@
@@ -0,0 +1,41 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.springframework.expression.Expression; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision; |
||||
import org.springframework.util.Assert; |
||||
|
||||
class PostAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision |
||||
implements MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor postProcessor; |
||||
|
||||
PostAuthorizeAuthorizationDecision(boolean granted, Expression expression, |
||||
MethodAuthorizationDeniedPostProcessor postProcessor) { |
||||
super(granted, expression); |
||||
Assert.notNull(postProcessor, "postProcessor cannot be null"); |
||||
this.postProcessor = postProcessor; |
||||
} |
||||
|
||||
@Override |
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) { |
||||
return this.postProcessor.postProcessResult(methodInvocationResult, result); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.springframework.expression.Expression; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An {@link ExpressionAttribute} that carries additional properties for |
||||
* {@code @PostAuthorize}. |
||||
* |
||||
* @author Marcus da Coregio |
||||
*/ |
||||
class PostAuthorizeExpressionAttribute extends ExpressionAttribute { |
||||
|
||||
private final MethodAuthorizationDeniedPostProcessor postProcessor; |
||||
|
||||
PostAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedPostProcessor postProcessor) { |
||||
super(expression); |
||||
Assert.notNull(postProcessor, "postProcessor cannot be null"); |
||||
this.postProcessor = postProcessor; |
||||
} |
||||
|
||||
MethodAuthorizationDeniedPostProcessor getPostProcessor() { |
||||
return this.postProcessor; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.aopalliance.intercept.MethodInvocation; |
||||
|
||||
import org.springframework.expression.Expression; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
import org.springframework.security.authorization.ExpressionAuthorizationDecision; |
||||
import org.springframework.util.Assert; |
||||
|
||||
class PreAuthorizeAuthorizationDecision extends ExpressionAuthorizationDecision |
||||
implements MethodAuthorizationDeniedHandler { |
||||
|
||||
private final MethodAuthorizationDeniedHandler handler; |
||||
|
||||
PreAuthorizeAuthorizationDecision(boolean granted, Expression expression, |
||||
MethodAuthorizationDeniedHandler handler) { |
||||
super(granted, expression); |
||||
Assert.notNull(handler, "handler cannot be null"); |
||||
this.handler = handler; |
||||
} |
||||
|
||||
@Override |
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { |
||||
return this.handler.handle(methodInvocation, result); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,42 @@
@@ -0,0 +1,42 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.springframework.expression.Expression; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* An {@link ExpressionAttribute} that carries additional properties for |
||||
* {@code @PreAuthorize}. |
||||
* |
||||
* @author Marcus da Coregio |
||||
*/ |
||||
class PreAuthorizeExpressionAttribute extends ExpressionAttribute { |
||||
|
||||
private final MethodAuthorizationDeniedHandler handler; |
||||
|
||||
PreAuthorizeExpressionAttribute(Expression expression, MethodAuthorizationDeniedHandler handler) { |
||||
super(expression); |
||||
Assert.notNull(handler, "handler cannot be null"); |
||||
this.handler = handler; |
||||
} |
||||
|
||||
MethodAuthorizationDeniedHandler getHandler() { |
||||
return this.handler; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.aopalliance.intercept.MethodInvocation; |
||||
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
|
||||
/** |
||||
* An implementation of {@link MethodAuthorizationDeniedHandler} that throws |
||||
* {@link AuthorizationDeniedException} |
||||
* |
||||
* @author Marcus da Coregio |
||||
* @since 6.3 |
||||
*/ |
||||
public final class ThrowingMethodAuthorizationDeniedHandler implements MethodAuthorizationDeniedHandler { |
||||
|
||||
@Override |
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult result) { |
||||
throw new AuthorizationDeniedException("Access Denied", result); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
/* |
||||
* Copyright 2002-2024 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.authorization.method; |
||||
|
||||
import org.springframework.security.authorization.AuthorizationDeniedException; |
||||
import org.springframework.security.authorization.AuthorizationResult; |
||||
|
||||
/** |
||||
* An implementation of {@link MethodAuthorizationDeniedPostProcessor} that throws |
||||
* {@link AuthorizationDeniedException} |
||||
* |
||||
* @author Marcus da Coregio |
||||
* @since 6.3 |
||||
*/ |
||||
public final class ThrowingMethodAuthorizationDeniedPostProcessor implements MethodAuthorizationDeniedPostProcessor { |
||||
|
||||
@Override |
||||
public Object postProcessResult(MethodInvocationResult methodInvocationResult, AuthorizationResult result) { |
||||
throw new AuthorizationDeniedException("Access Denied", result); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue