|
17 | 17 | package org.springframework.security.config.annotation.method.configuration;
|
18 | 18 |
|
19 | 19 | import java.io.Serializable;
|
| 20 | +import java.lang.annotation.Retention; |
| 21 | +import java.lang.annotation.RetentionPolicy; |
20 | 22 | import java.util.ArrayList;
|
21 | 23 | import java.util.Arrays;
|
22 | 24 | import java.util.List;
|
|
49 | 51 | import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
|
50 | 52 | import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
51 | 53 | import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
|
| 54 | +import org.springframework.security.access.prepost.PostAuthorize; |
| 55 | +import org.springframework.security.access.prepost.PostFilter; |
| 56 | +import org.springframework.security.access.prepost.PreAuthorize; |
| 57 | +import org.springframework.security.access.prepost.PreFilter; |
52 | 58 | import org.springframework.security.authorization.AuthorizationDecision;
|
53 | 59 | import org.springframework.security.authorization.AuthorizationEventPublisher;
|
54 | 60 | import org.springframework.security.authorization.AuthorizationManager;
|
55 | 61 | import org.springframework.security.authorization.method.AuthorizationInterceptorsOrder;
|
| 62 | +import org.springframework.security.authorization.method.AuthorizationManagerAfterMethodInterceptor; |
56 | 63 | import org.springframework.security.authorization.method.AuthorizationManagerBeforeMethodInterceptor;
|
57 | 64 | import org.springframework.security.authorization.method.MethodInvocationResult;
|
| 65 | +import org.springframework.security.authorization.method.PostAuthorizeAuthorizationManager; |
| 66 | +import org.springframework.security.authorization.method.PostFilterAuthorizationMethodInterceptor; |
| 67 | +import org.springframework.security.authorization.method.PreAuthorizeAuthorizationManager; |
| 68 | +import org.springframework.security.authorization.method.PreFilterAuthorizationMethodInterceptor; |
58 | 69 | import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
|
59 | 70 | import org.springframework.security.config.core.GrantedAuthorityDefaults;
|
60 | 71 | import org.springframework.security.config.test.SpringTestContext;
|
@@ -587,6 +598,74 @@ public void allAnnotationsWhenAdviceAfterAllOffsetThenReturnsFilteredList() {
|
587 | 598 | assertThat(filtered).containsExactly("DoNotDrop");
|
588 | 599 | }
|
589 | 600 |
|
| 601 | + @Test |
| 602 | + @WithMockUser |
| 603 | + public void methodeWhenParameterizedPreAuthorizeMetaAnnotationThenPasses() { |
| 604 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 605 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 606 | + assertThat(service.hasRole("USER")).isTrue(); |
| 607 | + } |
| 608 | + |
| 609 | + @Test |
| 610 | + @WithMockUser |
| 611 | + public void methodRoleWhenPreAuthorizeMetaAnnotationHardcodedParameterThenPasses() { |
| 612 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 613 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 614 | + assertThat(service.hasUserRole()).isTrue(); |
| 615 | + } |
| 616 | + |
| 617 | + @Test |
| 618 | + public void methodWhenParameterizedAnnotationThenFails() { |
| 619 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 620 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 621 | + assertThatExceptionOfType(IllegalArgumentException.class) |
| 622 | + .isThrownBy(service::placeholdersOnlyResolvedByMetaAnnotations); |
| 623 | + } |
| 624 | + |
| 625 | + @Test |
| 626 | + @WithMockUser(authorities = "SCOPE_message:read") |
| 627 | + public void methodWhenMultiplePlaceholdersHasAuthorityThenPasses() { |
| 628 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 629 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 630 | + assertThat(service.readMessage()).isEqualTo("message"); |
| 631 | + } |
| 632 | + |
| 633 | + @Test |
| 634 | + @WithMockUser(roles = "ADMIN") |
| 635 | + public void methodWhenMultiplePlaceholdersHasRoleThenPasses() { |
| 636 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 637 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 638 | + assertThat(service.readMessage()).isEqualTo("message"); |
| 639 | + } |
| 640 | + |
| 641 | + @Test |
| 642 | + @WithMockUser |
| 643 | + public void methodWhenPostAuthorizeMetaAnnotationThenAuthorizes() { |
| 644 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 645 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 646 | + service.startsWithDave("daveMatthews"); |
| 647 | + assertThatExceptionOfType(AccessDeniedException.class) |
| 648 | + .isThrownBy(() -> service.startsWithDave("jenniferHarper")); |
| 649 | + } |
| 650 | + |
| 651 | + @Test |
| 652 | + @WithMockUser |
| 653 | + public void methodWhenPreFilterMetaAnnotationThenFilters() { |
| 654 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 655 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 656 | + assertThat(service.parametersContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) |
| 657 | + .containsExactly("dave"); |
| 658 | + } |
| 659 | + |
| 660 | + @Test |
| 661 | + @WithMockUser |
| 662 | + public void methodWhenPostFilterMetaAnnotationThenFilters() { |
| 663 | + this.spring.register(MetaAnnotationPlaceholderConfig.class).autowire(); |
| 664 | + MetaAnnotationService service = this.spring.getContext().getBean(MetaAnnotationService.class); |
| 665 | + assertThat(service.resultsContainDave(new ArrayList<>(List.of("dave", "carla", "vanessa", "paul")))) |
| 666 | + .containsExactly("dave"); |
| 667 | + } |
| 668 | + |
590 | 669 | private static Consumer<ConfigurableWebApplicationContext> disallowBeanOverriding() {
|
591 | 670 | return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
|
592 | 671 | }
|
@@ -890,4 +969,135 @@ Authz authz() {
|
890 | 969 |
|
891 | 970 | }
|
892 | 971 |
|
| 972 | + @Configuration |
| 973 | + @EnableMethodSecurity(prePostEnabled = false) |
| 974 | + static class MetaAnnotationPlaceholderConfig { |
| 975 | + |
| 976 | + @Bean |
| 977 | + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
| 978 | + Advisor preAuthorize(SecurityContextHolderStrategy strategy) { |
| 979 | + PreAuthorizeAuthorizationManager preAuthorize = new PreAuthorizeAuthorizationManager(); |
| 980 | + preAuthorize.setUseTemplates(true); |
| 981 | + AuthorizationManagerBeforeMethodInterceptor interceptor = AuthorizationManagerBeforeMethodInterceptor |
| 982 | + .preAuthorize(preAuthorize); |
| 983 | + interceptor.setSecurityContextHolderStrategy(strategy); |
| 984 | + return interceptor; |
| 985 | + } |
| 986 | + |
| 987 | + @Bean |
| 988 | + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
| 989 | + Advisor postAuthorize(SecurityContextHolderStrategy strategy) { |
| 990 | + PostAuthorizeAuthorizationManager postAuthorize = new PostAuthorizeAuthorizationManager(); |
| 991 | + postAuthorize.setUseTemplates(true); |
| 992 | + AuthorizationManagerAfterMethodInterceptor interceptor = AuthorizationManagerAfterMethodInterceptor |
| 993 | + .postAuthorize(postAuthorize); |
| 994 | + interceptor.setSecurityContextHolderStrategy(strategy); |
| 995 | + return interceptor; |
| 996 | + } |
| 997 | + |
| 998 | + @Bean |
| 999 | + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
| 1000 | + Advisor preFilter(SecurityContextHolderStrategy strategy) { |
| 1001 | + PreFilterAuthorizationMethodInterceptor preFilter = new PreFilterAuthorizationMethodInterceptor(); |
| 1002 | + preFilter.setUseTemplates(true); |
| 1003 | + preFilter.setSecurityContextHolderStrategy(strategy); |
| 1004 | + return preFilter; |
| 1005 | + } |
| 1006 | + |
| 1007 | + @Bean |
| 1008 | + @Role(BeanDefinition.ROLE_INFRASTRUCTURE) |
| 1009 | + Advisor postFilter(SecurityContextHolderStrategy strategy) { |
| 1010 | + PostFilterAuthorizationMethodInterceptor postFilter = new PostFilterAuthorizationMethodInterceptor(); |
| 1011 | + postFilter.setUseTemplates(true); |
| 1012 | + postFilter.setSecurityContextHolderStrategy(strategy); |
| 1013 | + return postFilter; |
| 1014 | + } |
| 1015 | + |
| 1016 | + @Bean |
| 1017 | + MetaAnnotationService methodSecurityService() { |
| 1018 | + return new MetaAnnotationService(); |
| 1019 | + } |
| 1020 | + |
| 1021 | + } |
| 1022 | + |
| 1023 | + static class MetaAnnotationService { |
| 1024 | + |
| 1025 | + @RequireRole(role = "#role") |
| 1026 | + boolean hasRole(String role) { |
| 1027 | + return true; |
| 1028 | + } |
| 1029 | + |
| 1030 | + @RequireRole(role = "'USER'") |
| 1031 | + boolean hasUserRole() { |
| 1032 | + return true; |
| 1033 | + } |
| 1034 | + |
| 1035 | + @PreAuthorize("hasRole({role})") |
| 1036 | + void placeholdersOnlyResolvedByMetaAnnotations() { |
| 1037 | + } |
| 1038 | + |
| 1039 | + @HasClaim(claim = "message:read", roles = { "'ADMIN'" }) |
| 1040 | + String readMessage() { |
| 1041 | + return "message"; |
| 1042 | + } |
| 1043 | + |
| 1044 | + @ResultStartsWith("dave") |
| 1045 | + String startsWithDave(String value) { |
| 1046 | + return value; |
| 1047 | + } |
| 1048 | + |
| 1049 | + @ParameterContains("dave") |
| 1050 | + List<String> parametersContainDave(List<String> list) { |
| 1051 | + return list; |
| 1052 | + } |
| 1053 | + |
| 1054 | + @ResultContains("dave") |
| 1055 | + List<String> resultsContainDave(List<String> list) { |
| 1056 | + return list; |
| 1057 | + } |
| 1058 | + |
| 1059 | + } |
| 1060 | + |
| 1061 | + @Retention(RetentionPolicy.RUNTIME) |
| 1062 | + @PreAuthorize("hasRole({role})") |
| 1063 | + @interface RequireRole { |
| 1064 | + |
| 1065 | + String role(); |
| 1066 | + |
| 1067 | + } |
| 1068 | + |
| 1069 | + @Retention(RetentionPolicy.RUNTIME) |
| 1070 | + @PreAuthorize("hasAuthority('SCOPE_{claim}') || hasAnyRole({roles})") |
| 1071 | + @interface HasClaim { |
| 1072 | + |
| 1073 | + String claim(); |
| 1074 | + |
| 1075 | + String[] roles() default {}; |
| 1076 | + |
| 1077 | + } |
| 1078 | + |
| 1079 | + @Retention(RetentionPolicy.RUNTIME) |
| 1080 | + @PostAuthorize("returnObject.startsWith('{value}')") |
| 1081 | + @interface ResultStartsWith { |
| 1082 | + |
| 1083 | + String value(); |
| 1084 | + |
| 1085 | + } |
| 1086 | + |
| 1087 | + @Retention(RetentionPolicy.RUNTIME) |
| 1088 | + @PreFilter("filterObject.contains('{value}')") |
| 1089 | + @interface ParameterContains { |
| 1090 | + |
| 1091 | + String value(); |
| 1092 | + |
| 1093 | + } |
| 1094 | + |
| 1095 | + @Retention(RetentionPolicy.RUNTIME) |
| 1096 | + @PostFilter("filterObject.contains('{value}')") |
| 1097 | + @interface ResultContains { |
| 1098 | + |
| 1099 | + String value(); |
| 1100 | + |
| 1101 | + } |
| 1102 | + |
893 | 1103 | }
|
0 commit comments