接上一篇權限管理是後臺管理不可缺乏的部分,今天結合SpringSecurity實現接口的動態管理。跨域
SpringSecurity實現權限動態管理,第一步須要建立一個過濾器,doFilter方法須要注意,對於OPTIONS直接放行,不然會出現跨域問題。而且對在上篇文章提到的IgnoreUrlsConfig中的白名單也是直接放行,全部的權限操做都會在super.beforeInvocation(fi)中實現。緩存
/** * 動態權限過濾器,用於實現基於路徑的動態權限過濾 * */ public class DynamicSecurityFilter extends AbstractSecurityInterceptor implements Filter { @Autowired private DynamicSecurityMetadataSource dynamicSecurityMetadataSource; @Autowired private IgnoreUrlsConfig ignoreUrlsConfig; @Autowired public void setMyAccessDecisionManager(DynamicAccessDecisionManager dynamicAccessDecisionManager) { super.setAccessDecisionManager(dynamicAccessDecisionManager); } @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain); //OPTIONS請求直接放行 if(request.getMethod().equals(HttpMethod.OPTIONS.toString())){ fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); return; } //白名單請求直接放行 PathMatcher pathMatcher = new AntPathMatcher(); for (String path : ignoreUrlsConfig.getUrls()) { if(pathMatcher.match(path,request.getRequestURI())){ fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); return; } } //此處會調用AccessDecisionManager中的decide方法進行鑑權操做 InterceptorStatusToken token = super.beforeInvocation(fi); try { fi.getChain().doFilter(fi.getRequest(), fi.getResponse()); } finally { super.afterInvocation(token, null); } } @Override public void destroy() { } @Override public Class<?> getSecureObjectClass() { return FilterInvocation.class; } @Override public SecurityMetadataSource obtainSecurityMetadataSource() { return dynamicSecurityMetadataSource; } }
在DynamicSecurityFilter中調用super.beforeInvocation(fi)方法時會調用AccessDecisionManager中的decide方法用於鑑權操做,而decide方法中的configAttributes參數會經過SecurityMetadataSource中的getAttributes方法來獲取,configAttributes其實就是配置好的訪問當前接口所須要的權限,下面是簡化版的beforeInvocation源碼app
public abstract class AbstractSecurityInterceptor implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware { protected InterceptorStatusToken beforeInvocation(Object object) { //獲取元數據 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource() .getAttributes(object); Authentication authenticated = authenticateIfRequired(); //進行鑑權操做 try { this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException accessDeniedException) { publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, accessDeniedException)); throw accessDeniedException; } } }
上面的介紹,接下來咱們實現SecurityMetadataSource接口的getAttributes方法,來獲取當前訪問的路徑資源ide
/** * 動態權限數據源,用於獲取動態權限規則 * */ public class DynamicSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private static Map<String, ConfigAttribute> configAttributeMap = null; @Autowired private DynamicSecurityService dynamicSecurityService; @PostConstruct public void loadDataSource() { configAttributeMap = dynamicSecurityService.loadDataSource(); } public void clearDataSource() { configAttributeMap.clear(); configAttributeMap = null; } @Override public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException { if (configAttributeMap == null) this.loadDataSource(); List<ConfigAttribute> configAttributes = new ArrayList<>(); //獲取當前訪問的路徑 String url = ((FilterInvocation) o).getRequestUrl(); String path = URLUtil.getPath(url); PathMatcher pathMatcher = new AntPathMatcher(); Iterator<String> iterator = configAttributeMap.keySet().iterator(); //獲取訪問該路徑所需資源 while (iterator.hasNext()) { String pattern = iterator.next(); if (pathMatcher.match(pattern, path)) { configAttributes.add(configAttributeMap.get(pattern)); } } // 未設置操做請求權限,返回空集合 return configAttributes; } @Override public Collection<ConfigAttribute> getAllConfigAttributes() { return null; } @Override public boolean supports(Class<?> aClass) { return true; } }
咱們的後臺資源被規則緩存到了一個MAP對象中,全部當後臺資源變化時,須要清除緩存,在下次查詢的時候從新加載。咱們須要修改MyMesResourceController注入DynamicSecurityMetadataSource,當修改後臺資源時,須要調用clearDataSource方法來清空緩存的數據。優化
/** * 後臺資源管理Controller * */ @Controller @Api(tags = "MyMesResourceController", description = "後臺資源管理") @RequestMapping("/resource") public class MyMesResourceController { @Autowired private MyMesResourceService resourceService; @Autowired private DynamicSecurityMetadataSource dynamicSecurityMetadataSource; @ApiOperation("添加後臺資源") @RequestMapping(value = "/create", method = RequestMethod.POST) @ResponseBody public CommonResult create(@RequestBody UmsResource umsResource) { int count = resourceService.create(umsResource); dynamicSecurityMetadataSource.clearDataSource(); if (count > 0) { return CommonResult.success(count); } else { return CommonResult.failed(); } } }
咱們須要實現AccessDecisionManager接口來實現權限校驗,對於沒有配置資源的接口咱們直接容許訪問,對於配置了資源的接口,咱們把訪問所需資源和用戶擁有的資源進行比對,若是匹配則容許訪問。ui
/** * 動態權限決策管理器,用於判斷用戶是否有訪問權限 * */ public class DynamicAccessDecisionManager implements AccessDecisionManager { @Override public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { // 當接口未被配置資源時直接放行 if (CollUtil.isEmpty(configAttributes)) { return; } Iterator<ConfigAttribute> iterator = configAttributes.iterator(); while (iterator.hasNext()) { ConfigAttribute configAttribute = iterator.next(); //將訪問所需資源或用戶擁有資源進行比對 String needAuthority = configAttribute.getAttribute(); for (GrantedAuthority grantedAuthority : authentication.getAuthorities()) { if (needAuthority.trim().equals(grantedAuthority.getAuthority())) { return; } } } throw new AccessDeniedException("抱歉,您沒有訪問權限"); } @Override public boolean supports(ConfigAttribute configAttribute) { return true; } @Override public boolean supports(Class<?> aClass) { return true; } }
咱們以前在DynamicSecurityMetadataSource中注入了一個DynamicSecurityService對象,它是我自定義的一個動態權限業務接口,其主要用於加載全部的後臺資源規則。this
/** * 動態權限相關業務類 * */ public interface DynamicSecurityService { /** * 加載資源ANT通配符和資源對應MAP */ Map<String, ConfigAttribute> loadDataSource(); }
結合SpringSecurity實現接口的動態管理權限基本已經實現,明天后天準備講解一下Redis+AOP優化權限管理url
公衆號https://mp.weixin.qq.com/s/nfat2WWWUXdmfUGFBAVEuAcode