这期内容当中小编将会给大家带来有关怎么在spring security中动态配置url权限,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。
spring security 授权回顾
spring security 通过FilterChainProxy作为注册到web的filter,FilterChainProxy里面一次包含了内置的多个过滤器,我们首先需要了解spring security内置的各种filter:
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 |
最重要的是FilterSecurityInterceptor,该过滤器实现了主要的鉴权逻辑,最核心的代码在这里:
protected InterceptorStatusToken beforeInvocation(Object object) { // 获取访问URL所需权限 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object); Authentication authenticated = authenticateIfRequired(); // 通过accessDecisionManager鉴权 try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } if (debug) { logger.debug("Authorization successful"); } 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); } }
从上面可以看出,要实现动态鉴权,可以从两方面着手:
自定义SecurityMetadataSource,实现从数据库加载ConfigAttribute
另外就是可以自定义accessDecisionManager,官方的UnanimousBased其实足够使用,并且他是基于AccessDecisionVoter来实现权限认证的,因此我们只需要自定义一个AccessDecisionVoter就可以了
下面来看分别如何实现。
自定义AccessDecisionManager
官方的三个AccessDecisionManager都是基于AccessDecisionVoter来实现权限认证的,因此我们只需要自定义一个AccessDecisionVoter就可以了。
自定义主要是实现AccessDecisionVoter接口,我们可以仿照官方的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; } }
如何加入动态权限呢?
vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes)
里的Object object的类型是FilterInvocation,可以通过getRequestUrl获取当前请求的URL:
FilterInvocation fi = (FilterInvocation) object; String url = fi.getRequestUrl();
因此这里扩展空间就大了,可以从DB动态加载,然后判断URL的ConfigAttribute就可以了。
如何使用这个RoleBasedVoter呢?在configure里使用accessDecisionManager方法自定义,我们还是使用官方的UnanimousBased,然后将自定义的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() // 自定义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); }
自定义SecurityMetadataSource
自定义FilterInvocationSecurityMetadataSource只要实现接口即可,在接口里从DB动态加载规则。
为了复用代码里的定义,我们可以将代码里生成的SecurityMetadataSource带上,在构造函数里传入默认的FilterInvocationSecurityMetadataSource。
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 从数据库加载权限配置 } private final AntPathMatcher antPathMatcher = new AntPathMatcher(); // 这里的需要从DB加载 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 superMetadataSource.getAttributes(object); } @Override public boolean supports(Class<?> clazz) { return FilterInvocation.class.isAssignableFrom(clazz); } }
怎么使用?和accessDecisionManager不一样,ExpressionUrlAuthorizationConfigurer 并没有提供set方法设置FilterSecurityInterceptor的FilterInvocationSecurityMetadataSource,how to do?
发现一个扩展方法withObjectPostProcessor,通过该方法自定义一个处理FilterSecurityInterceptor类型的ObjectPostProcessor就可以修改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() // 自定义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; }
上述就是小编为大家分享的怎么在spring security中动态配置url权限了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。