SpringSecurity實現動態管理權限(三)

SpringBoot整合SpringSecurity實現接口動態管理權限

接上一篇權限管理是後臺管理不可缺乏的部分,今天結合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

公衆號

SpringSecurity實現動態管理權限(三)
公衆號https://mp.weixin.qq.com/s/nfat2WWWUXdmfUGFBAVEuAcode

相關文章
相關標籤/搜索