Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Authorization Denied Handlers for Method Security #14712

Merged
merged 2 commits into from
Apr 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
PrePostMethodSecurityConfiguration configuration, ApplicationContext context) {
PreAuthorizeAuthorizationManager manager = new PreAuthorizeAuthorizationManager();
manager.setApplicationContext(context);
AuthorizationManagerBeforeMethodInterceptor preAuthorize = AuthorizationManagerBeforeMethodInterceptor
.preAuthorize(manager(manager, registryProvider));
preAuthorize.setOrder(preAuthorize.getOrder() + configuration.interceptorOrderOffset);
Expand All @@ -121,6 +122,7 @@ static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
ObjectProvider<ObservationRegistry> registryProvider, ObjectProvider<RoleHierarchy> roleHierarchyProvider,
PrePostMethodSecurityConfiguration configuration, ApplicationContext context) {
PostAuthorizeAuthorizationManager manager = new PostAuthorizeAuthorizationManager();
manager.setApplicationContext(context);
AuthorizationManagerAfterMethodInterceptor postAuthorize = AuthorizationManagerAfterMethodInterceptor
.postAuthorize(manager(manager, registryProvider));
postAuthorize.setOrder(postAuthorize.getOrder() + configuration.interceptorOrderOffset);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Role;
Expand Down Expand Up @@ -74,9 +75,10 @@ static MethodInterceptor preFilterAuthorizationMethodInterceptor(MethodSecurityE
static MethodInterceptor preAuthorizeAuthorizationMethodInterceptor(
MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PreAuthorizeReactiveAuthorizationManager manager = new PreAuthorizeReactiveAuthorizationManager(
expressionHandler);
manager.setApplicationContext(context);
ReactiveAuthorizationManager<MethodInvocation> authorizationManager = manager(manager, registryProvider);
AuthorizationAdvisor interceptor = AuthorizationManagerBeforeReactiveMethodInterceptor
.preAuthorize(authorizationManager);
Expand All @@ -99,9 +101,10 @@ static MethodInterceptor postFilterAuthorizationMethodInterceptor(MethodSecurity
static MethodInterceptor postAuthorizeAuthorizationMethodInterceptor(
MethodSecurityExpressionHandler expressionHandler,
ObjectProvider<PrePostTemplateDefaults> defaultsObjectProvider,
ObjectProvider<ObservationRegistry> registryProvider) {
ObjectProvider<ObservationRegistry> registryProvider, ApplicationContext context) {
PostAuthorizeReactiveAuthorizationManager manager = new PostAuthorizeReactiveAuthorizationManager(
expressionHandler);
manager.setApplicationContext(context);
ReactiveAuthorizationManager<MethodInvocationResult> authorizationManager = manager(manager, registryProvider);
AuthorizationAdvisor interceptor = AuthorizationManagerAfterReactiveMethodInterceptor
.postAuthorize(authorizationManager);
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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")
Expand Down Expand Up @@ -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;
}

}

}
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -18,6 +18,7 @@

import java.util.List;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

Expand Down Expand Up @@ -126,4 +127,74 @@ public List<String> allAnnotations(List<String> list) {
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");
}

}
Loading
Loading