package com.iplatform.security; import com.iplatform.base.SecurityConstants; import com.iplatform.base.cache.MenuCacheProvider; import com.walker.infrastructure.utils.StringUtils; import com.walker.web.Constants; import com.walker.web.security.ResourceLoadProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.web.FilterInvocation; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 默认资源加载提供者,security框架需要的权限点集合。 * @author 时克英 * @date 2022-11-02 */ public class DefaultResourceLoaderProvider implements ResourceLoadProvider { protected final transient Logger logger = LoggerFactory.getLogger(getClass()); private MenuCacheProvider menuCacheProvider = null; private Map> resultMap = new HashMap>(); /** 请求匹配对象放入Map中,避免重复创建。key = url,value = AntPathRequestMatcher */ private Map requestMatchers = new ConcurrentHashMap(); private Collection emptyAttributes = null; private Collection anonymousAttributes = null; // 2023-03-21 activiti7工作流必须拦截权限,变态。 private Collection activiti7Attributes = null; private List permitAccessUrls = null; private Map anonymousUrlMap = null; /** * 设置可匿名访问的公开地址集合,如: ["/login","/register", "/image/**"] * @param anonymousUrlList */ public void setAnonymousUrlList(List anonymousUrlList) { if(!StringUtils.isEmptyList(anonymousUrlList)){ this.anonymousUrlMap = new HashMap<>(); for(String url : anonymousUrlList){ this.anonymousUrlMap.put(url, StringUtils.EMPTY_STRING); } } } /** * 设置一些只要认证用户都可以访问的常用url,如: /api/**, /report/** 等 * @param permitAccessUrls */ public void setPermitAccessUrls(List permitAccessUrls) { this.permitAccessUrls = permitAccessUrls; } public void setMenuCacheProvider(MenuCacheProvider menuCacheProvider) { this.menuCacheProvider = menuCacheProvider; } @Override public Map> loadResource() { resultMap.clear(); requestMatchers.clear(); // 1-把菜单和功能点url角色关系整理到对象中:urlRoleMap Map> urlRoleMap = new HashMap>(); // 2-所有角色对应的URL都加入 List roleUrlMap = this.menuCacheProvider.getAllRoleMenuMap(); if(roleUrlMap != null && roleUrlMap.size() > 0){ String url = null; for(String[] entry : roleUrlMap){ url = entry[1]; if(StringUtils.isEmpty(url)){ throw new NullPointerException("url is not null! role = " + entry[0]); } setUrlRoleMap(urlRoleMap, url, entry[0]); } } // 3-超级管理员默认加上所有菜单权限 List allMenuUrl = this.menuCacheProvider.getAllMenuUrlList(); if(!StringUtils.isEmptyList(allMenuUrl)){ for(String url : allMenuUrl){ setUrlRoleMap(urlRoleMap, url, SecurityConstants.ROLE_SUPER_ADMIN); } } setUrlRoleMap(urlRoleMap, "/supervisor/**", SecurityConstants.ROLE_SUPER_ADMIN); // setUrlRoleMap(urlRoleMap, "/wf/**", SecurityConstants.ROLE_SUPER_ADMIN); // 4-所有已登录用户,都可以访问默认公开地址: /permit setUrlRoleMap(urlRoleMap, "/permit/**", SecurityConstants.ROLE_USER); // 5-其他一些可用地址,已认证用户都可以访问,无需单独配置权限 if(!StringUtils.isEmptyList(this.permitAccessUrls)){ for(String url : this.permitAccessUrls){ setUrlRoleMap(urlRoleMap, url, SecurityConstants.ROLE_USER); // logger.debug("公共访问资源:" + url); // logger.debug(urlRoleMap.toString()); } } // 6-匿名URL不拦截 if(this.anonymousUrlMap != null){ for(String url : this.anonymousUrlMap.keySet()){ setUrlRoleMap(urlRoleMap, url, Constants.ROLE_ANONYMOUS); } } // 7-activiti7工作流权限,必须加上。2023-03-21 this.setUrlRoleMap(urlRoleMap, "/wf/**", Constants.ROLE_ACTIVITI_USER); logger.info("共加载权限点: " + urlRoleMap.size()); // if(logger.isDebugEnabled()){ // for(Map.Entry> entry : urlRoleMap.entrySet()){ // logger.debug(entry.getKey() + " = " + entry.getValue()); // } // } // 6-转成spring security 特定权限属性 if(urlRoleMap.size() > 0){ List caList = null; for(Map.Entry> entry : urlRoleMap.entrySet()){ caList = new ArrayList(entry.getValue().size()+1); for(String s : entry.getValue()){ ConfigAttribute ca = new SecurityConfig(s); caList.add(ca); } resultMap.put(entry.getKey(), caList); } } // 7-初始化请求路径匹配校验对象 Iterator it = resultMap.keySet().iterator(); String resURL = null; AntPathRequestMatcher requestMatcher = null; while(it.hasNext()){ resURL = it.next(); requestMatcher = requestMatchers.get(resURL); // 把requestMatcher对象放到Map中,避免重复创建 if(requestMatcher == null){ requestMatcher = new AntPathRequestMatcher(resURL); requestMatchers.put(resURL, requestMatcher); } } return resultMap; } @Override public void reloadResource() { this.loadResource(); logger.info("ResourceLoaderProvider reload success!"); } @Override public Collection getAttributes(Object object) { if(resultMap == null) return getEmptyAttributes(); Iterator it = resultMap.keySet().iterator(); String srcURL = null; String resURL = null; AntPathRequestMatcher requestMatcher = null; while(it.hasNext()){ resURL = it.next(); requestMatcher = requestMatchers.get(resURL); // 把requestMatcher对象放到Map中,避免重复创建 if(requestMatcher == null){ requestMatcher = new AntPathRequestMatcher(resURL); requestMatchers.put(resURL, requestMatcher); } srcURL = ((FilterInvocation)object).getRequestUrl(); if(requestMatcher.matches(((FilterInvocation)object).getRequest())){ logger.debug("............> 找到了匹配的资源: " + resURL + ", 请求的资源: " + srcURL); // 2023-03-21 activiti7修改,当访问流程相关功能,给予流程专用角色 // 这是activiti7机制需要的,变态! if(resURL.startsWith(SecurityConstants.URL_WORKFLOW_PREFIX)){ logger.debug("返回activiti7的角色集合"); return this.getActiviti7Attributes(); } return resultMap.get(resURL); } } // 2022-11-13 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // 如果通过表达式没有匹配到URL,这里要检查是否匿名路径 // 注意:不能把判断放在前面,因为用户配置匿名地址可能带有通配符,如:/nano/** // 但是这里request获得的URI是特定地址,如: /nano/123 // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ if(this.anonymousUrlMap != null && this.anonymousUrlMap.get(srcURL) != null){ return this.getAnonymousAttributes(); } // 如果根据URL没有找到对应的角色集合,就返回一个空集合; // 因为spring security默认对于空集合都放行。 return getEmptyAttributes(); } @Override public Collection getAttributesByUri(String uri) { return null; } private void setUrlRoleMap(Map> urlRoleMap, String _url, String _roleId){ List _roles = urlRoleMap.get(_url); if(_roles == null){ _roles = new ArrayList(8); _roles.add(_roleId); urlRoleMap.put(_url, _roles); } else if(!_roles.contains(_roleId)){ _roles.add(_roleId); } } protected Collection getEmptyAttributes(){ if(emptyAttributes == null){ emptyAttributes = new ArrayList(2); emptyAttributes.add(new SecurityConfig(Constants.ROLE_EMPTY)); } return emptyAttributes; } protected Collection getAnonymousAttributes(){ if(anonymousAttributes == null){ anonymousAttributes = new ArrayList(2); anonymousAttributes.add(new SecurityConfig(Constants.ROLE_ANONYMOUS)); } return emptyAttributes; } /** * 返回Activiti7需要的角色集合。 * @return * @date 2023-03-21 */ protected Collection getActiviti7Attributes(){ if(this.activiti7Attributes == null){ activiti7Attributes = new ArrayList(2); activiti7Attributes.add(new SecurityConfig(Constants.ROLE_ACTIVITI_USER)); } return this.activiti7Attributes; } }