origin
In standard RABC, permissions need to support dynamic configuration, spring security defaults to agree on permissions in the code. Real business scenarios usually require that role access permissions be dynamically configured, that is, to configure the access roles corresponding to URLs at runtime.
Based on spring security, how to achieve this requirement?
The easiest way is to customize a Filter to complete permission judgment, but this is separated from the spring security framework. How to implement it elegantly based on spring security?
spring security authorization review
spring security FilterChainProxy is used as a filter to register with the web. FilterChainProxy contains multiple built-in filters at once. First, we need to understand the various filters built-in in spring security:
| Alias | Filter Class | Namespace Element or Attribute |
|---|---|---|
| CHANNEL_FILTER | ChannelProcessingFilter | http/intercept-url@requires-channel |
| SECURITY_CONTEXT_FILTER | SecurityContextPersistenceFilter | http |
| CONCURRENT_SESSION_FILTER | ConcurrentSessionFilter | session-management/concurrency-control |
| HEADERS_FILTER | HeaderWriterFilter | http/headers |
| CSRF_FILTER | CsrfFilter | http/csrf |
| LOGOUT_FILTER | LogoutFilter | http/logout |
| X509_FILTER | X509AuthenticationFilter | http/x509 |
| PRE_AUTH_FILTER | AbstractPreAuthenticatedProcessingFilter Subclasses | N/A |
| CAS_FILTER | CasAuthenticationFilter | N/A |
| FORM_LOGIN_FILTER | UsernamePasswordAuthenticationFilter | http/form-login |
| BASIC_AUTH_FILTER | BasicAuthenticationFilter | http/http-basic |
| SERVLET_API_SUPPORT_FILTER | SecurityContextHolderAwareRequestFilter | http/@servlet-api-provision |
| JAAS_API_SUPPORT_FILTER | JaasApiIntegrationFilter | http/@jaas-api-provision |
| REMEMBER_ME_FILTER | RememberMeAuthenticationFilter | http/remember-me |
| ANONYMOUS_FILTER | AnonymousAuthenticationFilter | http/anonymous |
| SESSION_MANAGEMENT_FILTER | SessionManagementFilter | session-management |
| EXCEPTION_TRANSLATION_FILTER | ExceptionTranslationFilter | http |
| FILTER_SECURITY_INTERCEPTOR | FilterSecurityInterceptor | http |
| SWITCH_USER_FILTER | SwitchUserFilter | N/A |
The most important thing is FilterSecurityInterceptor, which implements the main authentication logic, and the most core code is here:
protected InterceptorStatusToken beforeInvocation(Object object) { // Get the permissions required to access the URL Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object); Authentication authenticated = authenticateIfRequired(); // Authentication via accessDecisionManager try { this.accessDecisionManager.docide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } if (debug) { logger.debug("Authorization successful"); } if (publishAuthorizationSuccessful"); } if (publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs == null) { if (debug) { logger.debug("RunAsManager did not change Authentication object"); } // no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); } else { if (debug) { logger.debug("Switching to RunAs Authentication: " + runAs); } SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } }As can be seen from the above, to achieve dynamic authentication, we can start from two aspects:
Let’s see how to implement it separately.
Custom AccessDecisionManager
The three official AccessDecisionManagers are all based on AccessDecisionVoter to implement permission authentication, so we only need to customize an AccessDecisionVoter.
Customization mainly implements the AccessDecisionVoter interface. We can implement a following the official RoleVoter:
public class RoleBasedVoter implements AccessDecisionVoter<Object> { @Override public boolean supports(ConfigAttribute attribute) { return true; } @Override public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) { if(authentication == null) { return ACCESS_DENIED; } int result = ACCESS_ABSTAIN; Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication); for (ConfigAttribute attribute : attributes) { if(attribute.getAttribute()==null){ continue; } if (this.supports(attribute)) { result = ACCESS_DENIED; // Attempt to find a matching granted authority for (GrantedAuthority authority : authorities) { if (attribute.getAttribute().equals(authority.getAuthority())) { return ACCESS_GRANTED; } } } } return result; } Collection<? extends GrantedAuthority> extractAuthorities( Authentication authentication) { return authentication.getAuthorities(); } @Override public boolean supports(Class clazz) { return true; }}How to add dynamic permissions?
The type of Object object in vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) is FilterInvocation, and you can get the URL of the current request through getRequestUrl:
FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl();
Therefore, there is a lot of room for expansion here. You can dynamically load it from the DB and then judge the URL's ConfigAttribute.
How to use this RoleBasedVoter? Use the accessDecisionManager method to customize in configuration. We should use the official UnanimousBased and then add the customized RoleBasedVoter.
@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling() .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport) .and() .csrf() .disable() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // Custom accessDecisionManager .accessDecisionManager(accessDecisionManager()) .and() .apply(securityConfigurerAdapter()); } @Bean public AccessDecisionManager accessDecisionManager() { List<AccessDecisionVoter<? extends Object>> decisionVoters = Arrays.asList( new WebExpressionVoter(), // new RoleVoter(), new RoleBasedVoter(), new AuthenticatedVoter()); return new UnanimousBased(decisionVoters); }Customize SecurityMetadataSource
Custom FilterInvocationSecurityMetadataSource only needs to implement the interface, and dynamically load rules from DB in the interface.
In order to reuse the definitions in the code, we can take the SecurityMetadataSource generated in the code and pass in the default FilterInvocationSecurityMetadataSource in the constructor.
public class AppFilterInvocationSecurityMetadataSource implements org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource { private FilterInvocationSecurityMetadataSource superMetadataSource; @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } public AppFilterInvocationSecurityMetadataSource(FilterInvocationSecurityMetadataSource expressionBasedFilterInvocationSecurityMetadataSource){ this.superMetadataSource = expressionBasedFilterInvocationSecurityMetadataSource; // TODO Load permission configuration from database} private final AntPathMatcher antPathMatcher = new AntPathMatcher(); // Here you need to load private final Map<String,String> urlRoleMap = new HashMap<String,String>(){{ put("/open/**","ROLE_ANONYMOUS"); put("/health","ROLE_ANONYMOUS"); put("/restart","ROLE_ADMIN"); put("/demo","ROLE_USER"); }}; @Override public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException { FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl(); for(Map.Entry<String,String> entry:urlRoleMap.entrySet()){ if(antPathMatcher.match(entry.getKey(),url)){ return SecurityConfig.createList(entry.getValue()); } } // Return the default configuration defined by the code return superMetadataSource.getAttributes(object); } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); }}How to use it? Unlike accessDecisionManager, ExpressionUrlAuthorizationConfigurer does not provide a set method to set the FilterInvocationSecurityMetadataSource of FilterSecurityInterceptor, how to do?
Discover an extension method withObjectPostProcessor, through which customizes an ObjectPostProcessor that handles FilterSecurityInterceptor type, you can modify FilterSecurityInterceptor.
@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class) .exceptionHandling() .authenticationEntryPoint(problemSupport) .accessDeniedHandler(problemSupport) .and() .csrf() .disable() .headers() .frameOptions() .disable() .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // Custom FilterInvocationSecurityMetadataSource .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() { @Override public <O extends FilterSecurityInterceptor> O postProcess( O fsi) { fsi.setSecurityMetadataSource(mySecurityMetadataSource(fsi.getSecurityMetadataSource())); return fsi; } }) .and() .apply(securityConfigurerAdapter()); } @Bean public AppFilterInvocationSecurityMetadataSource mySecurityMetadataSource(FilterInvocationSecurityMetadataSource filterInvocationSecurityMetadataSource) { AppFilterInvocationSecurityMetadataSource securityMetadataSource = new AppFilterInvocationSecurityMetadataSource(filterInvocationSecurityMetadataSource); return securityMetadataSource;}summary
This article introduces two methods to implement dynamic permissions based on spring security. One is to customize the accessDecisionManager, and the other is to customize the FilterInvocationSecurityMetadataSource. You can choose flexibly according to your needs in actual projects.
Further reading:
Spring Security Architecture and Source Code Analysis
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for everyone's study or work. If you have any questions, you can leave a message to communicate. Thank you for your support to Wulin.com.