如何自定义ExpressionInterceptURL动态管理InfoPack系列URL访问权限
Great question—this is a super common pain point when dealing with dynamic security rules that need to scale as your application grows. Let’s break down how to solve this without having to redeploy every time you add a new InfoPack:
1. Use a Custom FilterInvocationSecurityMetadataSource for Dynamic Rule Lookup
Spring Security’s FilterInvocationSecurityMetadataSource is built to fetch security rules at runtime, which is perfect for your use case. Here’s how to implement it:
- First, create a component that implements this interface. It will parse incoming requests, extract the InfoPack name, and fetch the required role (from a database, config center, or cached store):
@Component public class DynamicInfoPackSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private final InfoPackPermissionService permissionService; // Inject a service to fetch up-to-date permission mappings (e.g., from a database) public DynamicInfoPackSecurityMetadataSource(InfoPackPermissionService permissionService) { this.permissionService = permissionService; } @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation filterInvocation = (FilterInvocation) object; String requestUrl = filterInvocation.getRequestUrl(); // Match the InfoPack path pattern and extract the pack name Matcher pathMatcher = Pattern.compile("/InfoPacks/(\\w+)/.*").matcher(requestUrl); if (pathMatcher.matches()) { String infoPackName = pathMatcher.group(1); String requiredRole = "ROLE_INFOPACK" + infoPackName.toUpperCase(); // Validate the role exists (optional but recommended) if (permissionService.isRoleRegistered(requiredRole)) { return SecurityConfig.createList(requiredRole); } } // Return null to fall back to default security rules for non-InfoPack paths return null; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; // Not needed for our dynamic use case } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
2. Configure WebSecurity to Use Your Dynamic Metadata Source
Update your WebSecurityConfig to replace hardcoded antMatchers with your custom metadata source. Use an ObjectPostProcessor to attach it to Spring’s FilterSecurityInterceptor:
@Configuration @EnableWebSecurity public class WebSecurityConfig { private final DynamicInfoPackSecurityMetadataSource dynamicMetadataSource; public WebSecurityConfig(DynamicInfoPackSecurityMetadataSource dynamicMetadataSource) { this.dynamicMetadataSource = dynamicMetadataSource; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() // Base rule for all requests ) // Attach our dynamic metadata source to the security interceptor .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess(O interceptor) { interceptor.setSecurityMetadataSource(dynamicMetadataSource); // Use the default AccessDecisionManager or replace with your own if needed // interceptor.setAccessDecisionManager(customAccessDecisionManager()); return interceptor; } }); // Add other configs like authentication providers, password encoders, etc. return http.build(); } }
3. Make Permissions Dynamically Updatable
To avoid redeploys when adding new InfoPacks:
- Store the InfoPack-to-role mapping in a database (e.g., a table like
info_pack_permissionswith columnsinfo_pack_nameandrequired_role) - Implement
InfoPackPermissionServiceto fetch the latest mappings from the database. Add caching (e.g., Redis) for performance, and invalidate the cache whenever a new InfoPack is added - This way, every incoming request will check the latest permissions without needing code changes or restarts
4. Alternative: Use a Custom Security Expression
If you prefer working with Spring Security expressions, create a custom bean to check permissions dynamically:
@Configuration @EnableWebSecurity public class WebSecurityConfig { private final InfoPackPermissionService permissionService; public WebSecurityConfig(InfoPackPermissionService permissionService) { this.permissionService = permissionService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth // Use our custom expression for InfoPack paths .requestMatchers("/InfoPacks/**").access("@infoPackSecurityChecker.hasAccess(request, authentication)") .anyRequest().authenticated() ); return http.build(); } @Bean public InfoPackSecurityChecker infoPackSecurityChecker() { return new InfoPackSecurityChecker(permissionService); } } // Custom checker bean @Component public class InfoPackSecurityChecker { private final InfoPackPermissionService permissionService; public InfoPackSecurityChecker(InfoPackPermissionService permissionService) { this.permissionService = permissionService; } public boolean hasAccess(HttpServletRequest request, Authentication authentication) { String requestUrl = request.getRequestURI(); Matcher pathMatcher = Pattern.compile("/InfoPacks/(\\w+)/.*").matcher(requestUrl); if (pathMatcher.matches()) { String infoPackName = pathMatcher.group(1); String requiredRole = "ROLE_INFOPACK" + infoPackName.toUpperCase(); // Check if the authenticated user has the required role return authentication.getAuthorities().stream() .anyMatch(authority -> authority.getAuthority().equals(requiredRole)); } return false; } }
Both approaches let you add new InfoPacks anytime—just register the corresponding role in your database, and the security rules will apply immediately without redeployment.
内容的提问来源于stack exchange,提问作者Scaddenp

