Add Authorization Denied Handlers for Method Security
Closes gh-14601
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -16,23 +16,42 @@
|
||||
|
||||
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 java.util.List;
|
||||
|
||||
import jakarta.annotation.security.DenyAll;
|
||||
import jakarta.annotation.security.PermitAll;
|
||||
import jakarta.annotation.security.RolesAllowed;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
|
||||
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.annotation.Secured;
|
||||
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
|
||||
import org.springframework.security.access.prepost.PostAuthorize;
|
||||
import org.springframework.security.access.prepost.PostFilter;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.access.prepost.PreFilter;
|
||||
import org.springframework.security.authorization.AuthorizationResult;
|
||||
import org.springframework.security.authorization.method.AuthorizeReturnObject;
|
||||
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.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.parameters.P;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
@MethodSecurityService.Mask("classmask")
|
||||
public interface MethodSecurityService {
|
||||
|
||||
@PreAuthorize("denyAll")
|
||||
@@ -108,4 +127,196 @@ public interface MethodSecurityService {
|
||||
@RequireAdminRole
|
||||
void repeatedAnnotations();
|
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
|
||||
String preAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StartMaskingHandlerChild.class)
|
||||
String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = StarMaskingHandler.class)
|
||||
String preAuthorizeThrowAccessDeniedManually();
|
||||
|
||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = CardNumberMaskingPostProcessor.class)
|
||||
String postAuthorizeGetCardNumberIfAdmin(String cardNumber);
|
||||
|
||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = PostMaskingPostProcessor.class)
|
||||
String postAuthorizeThrowAccessDeniedManually();
|
||||
|
||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
|
||||
@Mask("methodmask")
|
||||
String preAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
|
||||
@PreAuthorize(value = "denyAll()", handlerClass = MaskAnnotationHandler.class)
|
||||
String preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
|
||||
@NullDenied(role = "ADMIN")
|
||||
String postAuthorizeDeniedWithNullDenied();
|
||||
|
||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@Mask("methodmask")
|
||||
String postAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
|
||||
@PostAuthorize(value = "denyAll()", postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
String postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = MaskAnnotationHandler.class)
|
||||
@Mask(expression = "@myMasker.getMask()")
|
||||
String preAuthorizeWithMaskAnnotationUsingBean();
|
||||
|
||||
@PostAuthorize(value = "hasRole('ADMIN')", postProcessorClass = MaskAnnotationPostProcessor.class)
|
||||
@Mask(expression = "@myMasker.getMask(returnObject)")
|
||||
String postAuthorizeWithMaskAnnotationUsingBean();
|
||||
|
||||
@AuthorizeReturnObject
|
||||
UserRecordWithEmailProtected getUserRecordWithEmailProtected();
|
||||
|
||||
@PreAuthorize(value = "hasRole('ADMIN')", handlerClass = UserFallbackDeniedHandler.class)
|
||||
UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
String resolveValue(Mask mask, MethodInvocation mi, Object returnObject) {
|
||||
if (StringUtils.hasText(mask.value())) {
|
||||
return 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 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();
|
||||
|
||||
}
|
||||
|
||||
class UserFallbackDeniedHandler implements MethodAuthorizationDeniedHandler {
|
||||
|
||||
private static final UserRecordWithEmailProtected FALLBACK = new UserRecordWithEmailProtected("Protected",
|
||||
"Protected");
|
||||
|
||||
@Override
|
||||
public Object handle(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
|
||||
return FALLBACK;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2023 the original author or authors.
|
||||
* 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.
|
||||
@@ -18,6 +18,7 @@ package org.springframework.security.config.annotation.method.configuration;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
@@ -126,4 +127,74 @@ public class MethodSecurityServiceImpl implements MethodSecurityService {
|
||||
public void repeatedAnnotations() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAuthorizeGetCardNumberIfAdmin(String cardNumber) {
|
||||
return cardNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preAuthorizeGetCardNumberIfAdmin(String cardNumber) {
|
||||
return cardNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preAuthorizeWithHandlerChildGetCardNumberIfAdmin(String cardNumber) {
|
||||
return cardNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preAuthorizeThrowAccessDeniedManually() {
|
||||
throw new AccessDeniedException("Access Denied");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAuthorizeThrowAccessDeniedManually() {
|
||||
throw new AccessDeniedException("Access Denied");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preAuthorizeDeniedMethodWithMaskAnnotation() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preAuthorizeDeniedMethodWithNoMaskAnnotation() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAuthorizeDeniedWithNullDenied() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAuthorizeDeniedMethodWithMaskAnnotation() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAuthorizeDeniedMethodWithNoMaskAnnotation() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String preAuthorizeWithMaskAnnotationUsingBean() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String postAuthorizeWithMaskAnnotationUsingBean() {
|
||||
return "ok";
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserRecordWithEmailProtected getUserRecordWithEmailProtected() {
|
||||
return new UserRecordWithEmailProtected("username", "useremail@example.com");
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserRecordWithEmailProtected getUserWithFallbackWhenUnauthorized() {
|
||||
return new UserRecordWithEmailProtected("username", "useremail@example.com");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -743,6 +743,188 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getCardNumberWhenPostAuthorizeAndNotAdminThenReturnMasked() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class,
|
||||
MethodSecurityService.CardNumberMaskingPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String cardNumber = service.postAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111");
|
||||
assertThat(cardNumber).isEqualTo("****-****-****-1111");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getCardNumberWhenPreAuthorizeAndNotAdminThenReturnMasked() {
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String cardNumber = service.preAuthorizeGetCardNumberIfAdmin("4444-3333-2222-1111");
|
||||
assertThat(cardNumber).isEqualTo("***");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getCardNumberWhenPreAuthorizeAndNotAdminAndChildHandlerThenResolveCorrectHandlerAndReturnMasked() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class,
|
||||
MethodSecurityService.StartMaskingHandlerChild.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String cardNumber = service.preAuthorizeWithHandlerChildGetCardNumberIfAdmin("4444-3333-2222-1111");
|
||||
assertThat(cardNumber).isEqualTo("***-child");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void preAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPreAuthorizeThenNotHandled() {
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.StarMaskingHandler.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(service::preAuthorizeThrowAccessDeniedManually);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.preAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
assertThat(result).isEqualTo("methodmask");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.preAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
assertThat(result).isEqualTo("classmask");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void postAuthorizeWhenHandlerAndAccessDeniedNotThrownFromPostAuthorizeThenNotHandled() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.PostMaskingPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
assertThatExceptionOfType(AccessDeniedException.class)
|
||||
.isThrownBy(service::postAuthorizeThrowAccessDeniedManually);
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void postAuthorizeWhenNullDeniedMetaAnnotationThanWorks() {
|
||||
this.spring.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.NullPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.postAuthorizeDeniedWithNullDenied();
|
||||
assertThat(result).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationThenHandlerCanUseMaskFromOtherAnnotation() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.postAuthorizeDeniedMethodWithMaskAnnotation();
|
||||
assertThat(result).isEqualTo("methodmask");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationInClassThenHandlerCanUseMaskFromOtherAnnotation() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.postAuthorizeDeniedMethodWithNoMaskAnnotation();
|
||||
assertThat(result).isEqualTo("classmask");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void postAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class,
|
||||
MyMasker.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.postAuthorizeWithMaskAnnotationUsingBean();
|
||||
assertThat(result).isEqualTo("ok-masked");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void postAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationPostProcessor.class,
|
||||
MyMasker.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.postAuthorizeWithMaskAnnotationUsingBean();
|
||||
assertThat(result).isEqualTo("ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void preAuthorizeWhenDeniedAndHandlerWithCustomAnnotationUsingBeanThenHandlerCanUseMaskFromOtherAnnotation() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class,
|
||||
MyMasker.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.preAuthorizeWithMaskAnnotationUsingBean();
|
||||
assertThat(result).isEqualTo("mask");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(roles = "ADMIN")
|
||||
void preAuthorizeWhenAllowedAndHandlerWithCustomAnnotationUsingBeanThenInvokeMethodNormally() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.MaskAnnotationHandler.class,
|
||||
MyMasker.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
String result = service.preAuthorizeWithMaskAnnotationUsingBean();
|
||||
assertThat(result).isEqualTo("ok");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getUserWhenAuthorizedAndUserEmailIsProtectedAndNotAuthorizedThenReturnEmailMasked() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class,
|
||||
UserRecordWithEmailProtected.EmailMaskingPostProcessor.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
UserRecordWithEmailProtected user = service.getUserRecordWithEmailProtected();
|
||||
assertThat(user.email()).isEqualTo("use******@example.com");
|
||||
assertThat(user.name()).isEqualTo("username");
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser
|
||||
void getUserWhenNotAuthorizedAndHandlerFallbackValueThenReturnFallbackValue() {
|
||||
this.spring
|
||||
.register(MethodSecurityServiceEnabledConfig.class, MethodSecurityService.UserFallbackDeniedHandler.class)
|
||||
.autowire();
|
||||
MethodSecurityService service = this.spring.getContext().getBean(MethodSecurityService.class);
|
||||
UserRecordWithEmailProtected user = service.getUserWithFallbackWhenUnauthorized();
|
||||
assertThat(user.email()).isEqualTo("Protected");
|
||||
assertThat(user.name()).isEqualTo("Protected");
|
||||
}
|
||||
|
||||
private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
||||
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
||||
}
|
||||
@@ -756,6 +938,16 @@ public class PrePostMethodSecurityConfigurationTests {
|
||||
return advisor;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AuthzConfig {
|
||||
|
||||
@Bean
|
||||
Authz authz() {
|
||||
return new Authz();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableCustomMethodSecurity
|
||||
static class CustomMethodSecurityServiceConfig {
|
||||
|
||||
@@ -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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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*");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user