核心思想:
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都有。
难道大家没有这个需求?以前好像听白衣说没有必要,各位老大,能描述一下为什么吗?
回复