核心思想:
1:对 org.apache.shiro.web.filter.mgt.DefaultFilterChainManager.filterChains 执行清空,它维护的是个map.否则原先的没有被覆盖的filterChains还会存在!
2:对org.apache.shiro.web.filter.PathMatchingFilter.appliedPaths执行清空,它维护的是个map,否则原先的没有被覆盖的filter还会存在!
3:使用org.apache.shiro.web.filter.mgt.DefaultFilterChainManager.createChain(String, String)进行filterChains与appliedPaths的覆盖
package com.capitalbio.soft.service.account; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.annotation.PostConstruct; import javax.servlet.Filter; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.DisabledAccountException; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.cache.Cache; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.util.ByteSource; import org.apache.shiro.web.filter.PathMatchingFilter; import org.apache.shiro.web.filter.mgt.DefaultFilterChainManager; import org.apache.shiro.web.filter.mgt.PathMatchingFilterChainResolver; import org.apache.shiro.web.servlet.AbstractShiroFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.capitalbio.soft.core.cache.SysCache; import com.capitalbio.soft.core.shiro.FilterChainDefinitionsLoader; import com.capitalbio.soft.core.utils.encoding.EncodeUtils; import com.capitalbio.soft.core.utils.exception.ExceptionUtils; import com.capitalbio.soft.core.utils.reflection.ReflectionUtils; import com.capitalbio.soft.core.utils.security.Digests; import com.capitalbio.soft.core.utils.spring.SpringContextHolder; import com.capitalbio.soft.entity.account.Authority; import com.capitalbio.soft.entity.account.Role; import com.capitalbio.soft.entity.account.User; import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; @Component("shiroDbRealm") public class ShiroDbRealm extends AuthorizingRealm { private static Logger logger = LoggerFactory.getLogger(ShiroDbRealm.class); @Autowired private UserService userService; /** * 认证回调函数,登录时调用. */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) authcToken; User user = userService.getByLoginName(token.getUsername(),false); if (user != null) { if(user.isUserDisabled()) { // DisabledAccountException || AuthenticationInfo org.apache.shiro.realm.SimpleAccountRealm.doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException throw new DisabledAccountException(); // throw new LockedAccountException("Account [" + user.getLoginName() + "] is locked."); } byte[] salt = EncodeUtils.decodeHex(user.getSalt()); return new SimpleAuthenticationInfo(new ShiroUser(user), user.getPassword(), ByteSource.Util.bytes(salt), getName()); } else { return null; } } /** * 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.在配有缓存的情况下,只加载一次. */ @Override public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal(); User user = userService.getByLoginName(shiroUser.getLoginName(),true); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); Set<String> roleSet = Sets.newLinkedHashSet(); // 角色集合 Set<String> authoritySet = Sets.newLinkedHashSet(); // 权限集合 Set<Authority> menuSet = Sets.newLinkedHashSet(); // 菜单集合 Map<String, List<Authority>> toolbars = Maps.newLinkedHashMap(); // 按钮集合 Set<Authority> authFilters = Sets.newLinkedHashSet(); if(null!=user && !user.isUserDisabled()) { // 判断是否用户是否禁用 for(Role r : user.getRoleList()) { if(!r.isRoleDisabled()) { // 判断角色是否禁用 if(StringUtils.isNotBlank(r.getCode())) roleSet.add(r.getCode()); for(Authority a : r.getAuthorityList()) { // 判断权限是否禁用 if(authFilters.add(a)) { // 过滤已经处理过的权限 if(!a.isAuthorityDisabled()) { if(StringUtils.isNotBlank(a.getCode())) authoritySet.add(a.getCode()); // 增加权限编码 if(1==a.getAuthorityType()) { menuSet.add(a); // 增加菜单 }else if(2==a.getAuthorityType() && null!=a.getParent()) { // 遍历增加按钮权限 String purl = a.getParent().getUrl(); if(StringUtils.isNotBlank(purl)) { if(!toolbars.containsKey(purl)) { toolbars.put(purl, new ArrayList<Authority>()); } toolbars.get(purl).add(a); } } } } } } } } info.setRoles(roleSet); // 设置角色 info.setStringPermissions(authoritySet); // 设置权限 shiroUser.setAuthorities(buildSubMenu(null,Lists.newArrayList(menuSet.toArray(new Authority[]{})))); // 设置菜单 shiroUser.setToolbars(toolbars); // 设置工具栏 logger.debug("{}当前用户权限:{}{}",SysCache.newline,SysCache.newline,authoritySet.toString().substring(1).replace(", ", SysCache.newline)); return info; } /** * 更新用户授权信息缓存. */ public void clearCachedAuthorizationInfo(Object principal) { SimplePrincipalCollection principals = new SimplePrincipalCollection(principal, getName()); clearCachedAuthorizationInfo(principals); } /** * 清除所有用户授权信息缓存. */ public void clearAllCachedAuthorizationInfo() { Cache<Object, AuthorizationInfo> cache = getAuthorizationCache(); if (cache != null) { for (Object key : cache.keys()) { cache.remove(key); } } } /** * 设定Password校验的Hash算法与迭代次数. */ @PostConstruct public void initCredentialsMatcher() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(HASH_ALGORITHM); matcher.setHashIterations(HASH_INTERATIONS); setCredentialsMatcher(matcher); } /** * 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息. */ public static class ShiroUser extends User implements Serializable { private static final long serialVersionUID = 1778589964992915025L; public ShiroUser() { } public ShiroUser(String loginName) { super(loginName); } public ShiroUser(User user) { super(user.getId(),user.getLoginName(), user.getName(), user.getEmail(), user.getNickname(), user.getPhone(), user.getDisabled(), user.getSex(), user.getCreateDate()); } /** * 本函数输出将作为默认的<shiro:principal/>输出. */ @Override public String toString() { return loginName; } /** * 重载hashCode,只计算loginName; */ @Override public int hashCode() { return Objects.hashCode(loginName); } /** * 重载equals,只计算loginName; */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ShiroUser other = (ShiroUser) obj; if (loginName == null) { if (other.loginName != null) return false; } else if (!loginName.equals(other.loginName)) return false; return true; } } public static final String HASH_ALGORITHM = "SHA-1"; // 使用的加密算法 public static final int HASH_INTERATIONS = 1024; // 加密迭代次数 private static final int SALT_SIZE = 8; // 加密盐的大小 /** * 设定安全的密码,生成随机的salt并经过1024次 sha-1 hash */ public static User entryptUserPassword(User user) { byte[] salt = Digests.generateSalt(SALT_SIZE); user.setSalt(EncodeUtils.encodeHex(salt)); byte[] hashPassword = Digests.sha1(user.getPassword().getBytes(), salt, HASH_INTERATIONS); user.setPassword(EncodeUtils.encodeHex(hashPassword)); return user; } /** * 得到当前登录用户 * @return */ public static ShiroUser getLoginUser() { return (ShiroUser)SecurityUtils.getSubject().getPrincipal(); } /** * 通过登录名移除权限缓存 * @param loginId * @return boolean */ public static boolean removeAuthCacheByLoginName(String loginName) { try { SpringContextHolder.getBean(ShiroDbRealm.class).clearCachedAuthorizationInfo(new ShiroUser(loginName)); return true; } catch (Exception e) { e.printStackTrace(); logger.error(ExceptionUtils.getStackTraceAsString(e)); return false; } } /** * 重新载入过滤器url * @param chains eg:keys:/static/**, value:authc,user,roles[admin] */ @SuppressWarnings("unchecked") public static void reloadShiroFilterChains(Map<String, String> chains) throws Exception { DefaultFilterChainManager filterManager = getFilterChainManager(); // logger.debug(getFiltersDescription(filterManager)); // 此句可注释掉... for(Entry<String, Filter> filterEntry : filterManager.getFilters().entrySet()) { if(PathMatchingFilter.class.isInstance(filterEntry.getValue())) { PathMatchingFilter filter = PathMatchingFilter.class.cast(filterEntry.getValue()); Map<String, Object> appliedPaths = (Map<String, Object>)ReflectionUtils.getFieldValue(filter, "appliedPaths"); synchronized (appliedPaths) { appliedPaths.clear(); } } } synchronized (filterManager.getFilterChains()) { // filterManager.getFilters().clear(); logger.debug(""+filterManager.getFilterConfig()); filterManager.getFilterChains().clear(); for(Entry<String,String> chain : chains.entrySet()){ filterManager.createChain(chain.getKey(), chain.getValue()); // logger.debug("{} = {}",chain.getKey(),chain.getValue()); } } logger.debug(getFiltersDescription(filterManager)); // 此句可注释掉... } /** * 描述当前过滤器 */ @SuppressWarnings("unchecked") public static String getFiltersDescription(DefaultFilterChainManager filterManager) { StringBuilder filtersDesc = new StringBuilder(); for(Entry<String, Filter> filterEntryDesc : filterManager.getFilters().entrySet()) { filtersDesc.append(SysCache.newline).append(filterEntryDesc.getKey()).append(":").append(SysCache.newline); if(PathMatchingFilter.class.isInstance(filterEntryDesc.getValue())) { PathMatchingFilter filter = PathMatchingFilter.class.cast(filterEntryDesc.getValue()); Map<String, Object> appliedPaths = (Map<String, Object>)ReflectionUtils.getFieldValue(filter, "appliedPaths"); for(Entry<String, Object> paths : appliedPaths.entrySet()) { filtersDesc.append(paths.getKey()).append(" = ").append(Arrays.toString((String[])paths.getValue())).append(SysCache.newline); } } } return filtersDesc.toString(); } /** * 重新载入过滤器url */ public static void reloadShiroFilterChains() throws Exception { Map<String, String> allDefinitionMap = SpringContextHolder.getBean(FilterChainDefinitionsLoader.class).loadAllDefinitionMap(); reloadShiroFilterChains(allDefinitionMap); } /** * 将菜单组织为树形的结构 * 级联取出下级菜单(递归算法) */ public static List<Authority> buildSubMenu(Authority parent,List<Authority> list) { List<Authority> subNodes = Lists.newLinkedList(); for(Authority s : list) { if((null==parent && null==s.getParent()) || (null!=parent && null!=s.getParent() && parent.getId().equals(s.getParent().getId()))) { s.setSubnodes(buildSubMenu(s, list)); subNodes.add(s); // logger.debug((null!=parent? parent.getName() : "") + " > " + s.getName()); } } Authority.sort(subNodes,false); return subNodes; } /** * @return 得到过滤器链 */ public static DefaultFilterChainManager getFilterChainManager() { return ((DefaultFilterChainManager)((PathMatchingFilterChainResolver)SpringContextHolder.getBean(AbstractShiroFilter.class).getFilterChainResolver()).getFilterChainManager()); } /** * 解决在登录时无法加载菜单项的问题,即在每次登录时重新加载用户权限缓存 */ public static void forceShiroToReloadUserAuthorityCache(String username) { // SpringContextHolder.getBean(ShiroDbRealm.class).doGetAuthorizationInfo(SecurityUtils.getSubject().getPrincipals()); ShiroDbRealm shiroDbRealm = SpringContextHolder.getBean(ShiroDbRealm.class); shiroDbRealm.clearCachedAuthorizationInfo(new ShiroUser(username)); // 清除权限缓存 shiroDbRealm.isPermitted(SecurityUtils.getSubject().getPrincipals(), "强制shiro检查加载用户权限缓存,避免懒加载!" + System.currentTimeMillis()); } }
想法来自于: http://www.infoq.com/cn/articles/apache-shiro
-
写一个读filterDefinitions的方法,然后在spring配置中:
<property name="filterChainDefinitions" value="#{filterChainDefinitionsLoader.loadDefinitions()}"/>
如果要在运行时重新加载filterDefinitions的话可以这样:
从springContext中拿到shiroFilter.PathMatchingFilterChainResolver.DefaultFilterChainManager, 把filterChainManager中的filterChains清空,再调用filterChainManager.createChain来重新生成,注意对filterChains加同步。
[urls]
/assets/** = anon
/user/signup = anon
/user/** = user
/rpc/rest/** = perms[rpc:invoke], authc
/** = authc
上面权限配置,我想放到数据库里面?Shiro好像没有扩展?我以为用spring security都有。
难道大家没有这个需求?以前好像听白衣说没有必要,各位老大,能描述一下为什么吗?
回复