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源码

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方法,来获取当前访问的路径资源

/** * 动态权限数据源,用于获取动态权限规则 *  */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接口来实现权限校验,对于没有配置资源的接口我们直接允许访问,对于配置了资源的接口,我们把访问所需资源和用户拥有的资源进行比对,如果匹配则允许访问。

/** * 动态权限决策管理器,用于判断用户是否有访问权限 *  */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对象,它是我自定义的一个动态权限业务接口,其主要用于加载所有的后台资源规则。

/** * 动态权限相关业务类 * */public interface DynamicSecurityService {    /**     * 加载资源ANT通配符和资源对应MAP     */    Map<String, ConfigAttribute> loadDataSource();}

结合SpringSecurity实现接口的动态管理权限基本已经实现,明天后天准备讲解一下Redis+AOP优化权限管理

公众号

SpringSecurity实现动态管理权限(三)
公众号https://mp.weixin.qq.com/s/nfat2WWWUXdmfUGFBAVEuA

更多相关文章

  1. k8s rbac 权限管理控制创建过程+理论知识
  2. 小程序静态资源如何设置防盗链?
  3. 全栈资源共享 一起成长,努力成为你想成为的样子
  4. 用 NodeJS 充分利用多核 CPU 的资源[每日前端夜话0xCB]
  5. 在 HTML 中包含资源的新思路[每日前端夜话0xC3]
  6. 在Access中利用Jquery技术实现专业的界面和权限控制的通用程序
  7. IOS学习之WebView加载本地HTML代码或网络资源
  8. 小程序:Java下载单页HTML(可下载引用资源)
  9. 您试图显示配置为只允许执行和脚本权限的目录中的 HTML 页

随机推荐

  1. android studio创建新项目color.xml文件
  2. java.lang.NoClassDefFoundError: com.go
  3. 真正的机器人操作系统---Android
  4. 【ubuntu】Ubuntu中Android(安卓)SDK下载
  5. Android视频采集
  6. 实例分析android中的Binder通信机制(1)
  7. 如何在Android中使用OpenCV
  8. 【转】不要被虚张声势的 Android 忽悠了
  9. Android 读取硬件信息技巧
  10. windows 8环境—android studio初步体验(