1、技术栈
主框架:springboot
响应层:springMVC
持久层:mybatis
事务控制:jta
前端技术:easyui
2、数据库设计
【1】数据库图解
sh_user:用户表,一个用户可以有多个角色
sh_role:角色表,一个角色可以有多个资源
sh_resource:资源表
sh_user_role:用户角色中间表
sh_role_resource:角色资源中间表
【2】数据库脚本
sh_user
CREATE TABLE `sh_user` (
`ID` varchar(36) NOT NULL COMMENT '主键',
`LOGIN_NAME` varchar(36) DEFAULT NULL COMMENT '登录名称',
`REAL_NAME` varchar(36) DEFAULT NULL COMMENT '真实姓名',
`NICK_NAME` varchar(36) DEFAULT NULL COMMENT '昵称',
`PASS_WORD` varchar(150) DEFAULT NULL COMMENT '密码',
`SALT` varchar(36) DEFAULT NULL COMMENT '加密因子',
`SEX` int(11) DEFAULT NULL COMMENT '性别',
`ZIPCODE` varchar(36) DEFAULT NULL COMMENT '邮箱',
`ADDRESS` varchar(36) DEFAULT NULL COMMENT '地址',
`TEL` varchar(36) DEFAULT NULL COMMENT '固定电话',
`MOBIL` varchar(36) DEFAULT NULL COMMENT '电话',
`EMAIL` varchar(36) DEFAULT NULL COMMENT '邮箱',
`DUTIES` varchar(36) DEFAULT NULL COMMENT '职务',
`SORT_NO` int(11) DEFAULT NULL COMMENT '排序',
`ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户表';
sh_role
CREATE TABLE `sh_role` (
`ID` varchar(36) NOT NULL COMMENT '主键',
`ROLE_NAME` varchar(36) DEFAULT NULL COMMENT '角色名称',
`LABEL` varchar(36) DEFAULT NULL COMMENT '角色标识',
`DESCRIPTION` varchar(200) DEFAULT NULL COMMENT '角色描述',
`SORT_NO` int(36) DEFAULT NULL COMMENT '排序',
`ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户角色表';
sh_resource
CREATE TABLE `sh_resource` (
`ID` varchar(36) NOT NULL COMMENT '主键',
`PARENT_ID` varchar(36) DEFAULT NULL COMMENT '父资源',
`RESOURCE_NAME` varchar(36) DEFAULT NULL COMMENT '资源名称',
`REQUEST_PATH` varchar(200) DEFAULT NULL COMMENT '资源路径',
`LABEL` varchar(200) DEFAULT NULL COMMENT '资源标签',
`ICON` varchar(20) DEFAULT NULL COMMENT '图标',
`IS_LEAF` varchar(18) DEFAULT NULL COMMENT '是否叶子节点',
`RESOURCE_TYPE` varchar(36) DEFAULT NULL COMMENT '资源类型',
`SORT_NO` int(11) DEFAULT NULL COMMENT '排序',
`DESCRIPTION` varchar(200) DEFAULT NULL COMMENT '描述',
`SYSTEM_CODE` varchar(36) DEFAULT NULL COMMENT '系统code',
`IS_SYSTEM_ROOT` varchar(18) DEFAULT NULL COMMENT '是否根节点',
`ENABLE_FLAG` varchar(18) DEFAULT NULL COMMENT '是否有效',
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='资源表';
sh_role_resource
CREATE TABLE `sh_role_resource` (
`ID` varchar(36) NOT NULL,
`ENABLE_FLAG` varchar(18) DEFAULT NULL,
`ROLE_ID` varchar(36) DEFAULT NULL,
`RESOURCE_ID` varchar(36) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='角色资源表';
sh_user_role
CREATE TABLE `sh_user_role` (
`ID` varchar(36) NOT NULL,
`ENABLE_FLAG` varchar(18) DEFAULT NULL,
`USER_ID` varchar(36) DEFAULT NULL,
`ROLE_ID` varchar(36) DEFAULT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户角色表';
3、项目骨架
4、ShiroDbRealm定义
【1】图解
【2】原理分析
(1)、ShiroDbRealmImpl继承ShiroDbRealm向上继承AuthorizingRealm,ShiroDbRealmImpl实例化时会创建密码匹配器HashedCredentialsMatcher实例,HashedCredentialsMatcher指定hash次数与方式,交于AuthenticatingRealm
(2)、调用login方法后,最终调用doGetAuthenticationInfo(AuthenticationToken authcToken)方法,拿到SimpleToken的对象,调用UserBridgeService的查找用户方法,把ShiroUser对象、密码和salt交于SimpleAuthenticationInfo去认证
(3)、访问需要鉴权时,调用doGetAuthorizationInfo(PrincipalCollection principals)方法,然后调用UserBridgeService的授权验证
【3】核心类代码
【3.1】ShiroDbRealm
package com.itheima.shiro.core; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import javax.annotation.PostConstruct; /** * * @Description shiro自定义realm */ public abstract class ShiroDbRealm extends AuthorizingRealm { /** * @Description 认证 * @param authcToken token对象 * @return */ public abstract AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) ; /** * @Description 鉴权 * @param principals 令牌 * @return */ public abstract AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals); /** * @Description 密码匹配器 */ @PostConstruct public abstract void initCredentialsMatcher() ; }
【3.2】ShiroDbRealmImpl
package com.itheima.shiro.core.impl; import com.itheima.shiro.constant.SuperConstant; import com.itheima.shiro.core.base.ShiroUser; import com.itheima.shiro.core.base.SimpleToken; import com.itheima.shiro.core.ShiroDbRealm; import com.itheima.shiro.core.bridge.UserBridgeService; import com.itheima.shiro.pojo.User; import com.itheima.shiro.utils.BeanConv; import com.itheima.shiro.utils.DigestsUtil; import com.itheima.shiro.utils.EmptyUtil; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.util.ByteSource; import org.springframework.beans.factory.annotation.Autowired; /** * @Description:自定义shiro的实现 */ public class ShiroDbRealmImpl extends ShiroDbRealm { @Autowired private UserBridgeService userBridgeService; /** * @Description 认证方法 * @param authcToken 校验传入令牌 * @return AuthenticationInfo */ @Override public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) { SimpleToken token = (SimpleToken)authcToken; User user = userBridgeService.findUserByLoginName(token.getUsername()); if(EmptyUtil.isNullOrEmpty(user)){ throw new UnknownAccountException("账号不存在"); } ShiroUser shiroUser = BeanConv.toBean(user, ShiroUser.class); shiroUser.setResourceIds(userBridgeService.findResourcesIdsList(user.getId())); String salt = user.getSalt(); String password = user.getPassWord(); return new SimpleAuthenticationInfo(shiroUser, password, ByteSource.Util.bytes(salt), getName()); } /** * @Description 授权方法 * @param principals SimpleAuthenticationInfo对象第一个参数 * @return */ @Override public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { ShiroUser shiroUser = (ShiroUser) principals.getPrimaryPrincipal(); return userBridgeService.getAuthorizationInfo(shiroUser); } /** * @Description 加密方式 */ @Override public void initCredentialsMatcher() { HashedCredentialsMatcher matcher = new HashedCredentialsMatcher(SuperConstant.HASH_ALGORITHM); matcher.setHashIterations(SuperConstant.HASH_INTERATIONS); setCredentialsMatcher(matcher); } }
【3.3】SimpleToken
package com.itheima.shiro.core.base; import org.apache.shiro.authc.UsernamePasswordToken; /** * @Description 自定义tooken */ public class SimpleToken extends UsernamePasswordToken { /** serialVersionUID */ private static final long serialVersionUID = -4849823851197352099L; private String tokenType; private String quickPassword; /** * Constructor for SimpleToken * @param tokenType */ public SimpleToken(String tokenType, String username,String password) { super(username,password); this.tokenType = tokenType; } public SimpleToken(String tokenType, String username,String password,String quickPassword) { super(username,password); this.tokenType = tokenType; this.quickPassword = quickPassword; } public String getTokenType() { return tokenType; } public void setTokenType(String tokenType) { this.tokenType = tokenType; } public String getQuickPassword() { return quickPassword; } public void setQuickPassword(String quickPassword) { this.quickPassword = quickPassword; } }
【3.4】ShiroUser
package com.itheima.shiro.core.base; import com.itheima.shiro.utils.ToString; import lombok.Data; import java.util.List; /** * @Description 自定义Authentication对象,使得Subject除了携带用户的登录名外还可以携带更多信息 */ @Data public class ShiroUser extends ToString { /** serialVersionUID */ private static final long serialVersionUID = -5024855628064590607L; /** * 主键 */ private String id; /** * 登录名称 */ private String loginName; /** * 真实姓名 */ private String realName; /** * 昵称 */ private String nickName; /** * 密码 */ private String passWord; /** * 加密因子 */ private String salt; /** * 性别 */ private Integer sex; /** * 邮箱 */ private String zipcode; /** * 地址 */ private String address; /** * 固定电话 */ private String tel; /** * 电话 */ private String mobil; /** * 邮箱 */ private String email; /** * 职务 */ private String duties; /** * 排序 */ private Integer sortNo; /** * 是否有效 */ private String enableFlag; private List<String> resourceIds; public ShiroUser() { super(); } public ShiroUser(String id, String loginName) { super(); this.id = id; this.loginName = loginName; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((email == null) ? 0 : email.hashCode()); result = prime * result + ((id == null) ? 0 : id.hashCode()); result = prime * result + ((loginName == null) ? 0 : loginName.hashCode()); result = prime * result + ((mobil == null) ? 0 : mobil.hashCode()); return result; } @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 (email == null) { if (other.email != null) return false; } else if (!email.equals(other.email)) return false; if (id == null) { if (other.id != null) return false; } else if (!id.equals(other.id)) return false; if (loginName == null) { if (other.loginName != null) return false; } else if (!loginName.equals(other.loginName)) return false; if (mobil == null) { if (other.mobil != null) return false; } else if (!mobil.equals(other.mobil)) return false; return true; } }
【3.5】UserBridgeService
package com.itheima.shiro.core.bridge; import com.itheima.shiro.core.base.ShiroUser; import com.itheima.shiro.pojo.User; import org.apache.shiro.authz.AuthorizationInfo; import java.util.List; /** * @Description:用户信息桥接(后期会做缓存) */ public interface UserBridgeService { /** * @Description 查找用户信息 * @param loginName 用户名称 * @return user对象 */ User findUserByLoginName(String loginName); /** * @Description 鉴权方法 * @param shiroUser 令牌对象 * @return 鉴权信息 */ AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser); /** * @Description 查询用户对应角色标识list * @param userId 用户id * @return 角色标识集合 */ List<String> findRoleList(String userId); /** * @Description 查询用户对应资源标识list * @param userId 用户id * @return 资源标识集合 */ List<String> findResourcesList(String userId); /** * @Description 查询资源ids * @param userId 用户id * @return 资源id集合 */ List<String> findResourcesIds(String userId); }
【3.6】UserBridgeServiceImpl
package com.itheima.shiro.core.bridge.impl; import com.itheima.shiro.core.adapter.UserAdapter; import com.itheima.shiro.core.base.ShiroUser; import com.itheima.shiro.core.bridge.UserBridgeService; import com.itheima.shiro.pojo.Resource; import com.itheima.shiro.pojo.Role; import com.itheima.shiro.pojo.User; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; /** * @Description:用户信息桥接(后期会做缓存) */ @Component("userBridgeService") public class UserBridgeServiceImpl implements UserBridgeService { @Autowired UserAdapter userAdapter; @Override public User findUserByLoginName(String loginName) { return userAdapter.findUserByLoginName(loginName); } @Override public AuthorizationInfo getAuthorizationInfo(ShiroUser shiroUser) { //查询用户对应的角色标识 List<String> roleList = this.findRoleList(shiroUser.getId()); //查询用户对于的资源标识 List<String> resourcesList = this.findResourcesList(shiroUser.getId()); //构建鉴权信息对象 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); simpleAuthorizationInfo.addRoles(roleList); simpleAuthorizationInfo.addStringPermissions(resourcesList); return simpleAuthorizationInfo; } @Override public List<String> findRoleList(String userId){ List<Role> roles = userAdapter.findRoleByUserId(userId); List<String> roleLabel = new ArrayList<>(); for (Role role : roles) { roleLabel.add(role.getLabel()); } return roleLabel; } @Override public List<String> findResourcesList(String userId){ List<Resource> resources = userAdapter.findResourceByUserId(userId); List<String> resourceLabel = new ArrayList<>(); for (Resource resource : resources) { resourceLabel.add(resource.getLabel()); } return resourceLabel; } @Override public List<String> findResourcesIds(String userId) { List<Resource> resources = userAdapter.findResourceByUserId(userId); List<String> ids = new ArrayList<>(); for (Resource resource : resources) { ids.add(resource.getId()); } return ids; } }
【3.7】UserAdapter
package com.itheima.shiro.core.adapter; import com.itheima.shiro.pojo.Resource; import com.itheima.shiro.pojo.Role; import com.itheima.shiro.pojo.User; import java.util.List; /** * @Description 后台登陆用户适配器接口 */ public interface UserAdapter { /** * @Description 按用户名查找用户 * @param loginName 登录名 * @return */ User findUserByLoginName(String loginName); /** * @Description 查找用户所有角色 * @param userId 用户Id * @return */ List<Role> findRoleByUserId(String userId); /** * @Description 查询用户有那些资源 * @param userId 用户Id * @return */ List<Resource> findResourceByUserId(String userId); }
【3.8】UserAdapterImpl
package com.itheima.shiro.core.adapter.impl; import com.itheima.shiro.constant.SuperConstant; import com.itheima.shiro.core.adapter.UserAdapter; import com.itheima.shiro.mapper.UserMapper; import com.itheima.shiro.mappercustom.UserAdapterMapper; import com.itheima.shiro.pojo.Resource; import com.itheima.shiro.pojo.Role; import com.itheima.shiro.pojo.User; import com.itheima.shiro.pojo.UserExample; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; /** * @Description 后台登陆用户适配器接口实现 */ @Component("userAdapter") public class UserAdapterImpl implements UserAdapter { @Autowired private UserMapper userMapper; @Autowired private UserAdapterMapper userAdapterMapper; @Override public User findUserByLoginName(String loginName) { UserExample userExample = new UserExample(); userExample.createCriteria().andEnableFlagEqualTo(SuperConstant.YES).andLoginNameEqualTo(loginName); List<User> userList = userMapper.selectByExample(userExample); if (userList.size()==1) { return userList.get(0); }else { return null; } } @Override public List<Role> findRoleByUserId(String userId) { Map<String, Object> values = new HashMap<String, Object>(); values.put("userId", userId); values.put("enableFlag", SuperConstant.YES); List<Role> list = userAdapterMapper.findRoleByUserId(values); return list; } @Override public List<Resource> findResourceByUserId(String userId) { Map<String, Object> values = new HashMap<String, Object>(); values.put("userId", userId); values.put("enableFlag", SuperConstant.YES); List<Resource> list=userAdapterMapper.findResourceByUserId(values); return list; } }
5、ShiroConfig配置
【1】图解
【2】原理分析
(1)、创建SimpleCookie,访问项目时,会在客户端中cookie中存放ShiroSession的对
(2)、创建DefaultWebSessionManager会话管理器定义cookie机制、定时刷新、全局会话超时时间然后交
于DefaultWebSecurityManager权限管理器管理
(3)、创建自定义ShiroDbRealm实现,用于权限认证、授权、加密方式的管理,同时从数据库中取得相关的
角色、资源、用户的信息,然后交于DefaultWebSecurityManager权限管理器管理
(4)、创建DefaultWebSecurityManager权限管理器用于管理DefaultWebSessionManager会话管理器、ShiroDbRealm
(5)、创建lifecycleBeanPostProcessor和DefaultAdvisorAutoProxyCreator相互配合事项注解的权限鉴权
(6)、创建ShiroFilterFactoryBean的shiro过滤器指定权限管理器、同时启动连接链及登录URL、未登录的URL
的跳转
【3】ShiroConfig代码
package com.itheima.shiro.config; import com.itheima.shiro.core.ShiroDbRealm; import com.itheima.shiro.core.impl.ShiroDbRealmImpl; import com.itheima.shiro.properties.PropertiesUtil; import lombok.extern.log4j.Log4j2; import org.apache.shiro.spring.LifecycleBeanPostProcessor; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.apache.shiro.web.session.mgt.DefaultWebSessionManager; import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** * @Description:权限配置类 */ @Configuration @ComponentScan(basePackages = "com.itheima.shiro.core") @Log4j2 public class ShiroConfig { /** * @Description 创建cookie对象 */ @Bean(name="sessionIdCookie") public SimpleCookie simpleCookie(){ SimpleCookie simpleCookie = new SimpleCookie(); simpleCookie.setName("ShiroSession"); return simpleCookie; } /** * @Description 权限管理器 */ @Bean(name="securityManager") public DefaultWebSecurityManager defaultWebSecurityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(shiroDbRealm()); securityManager.setSessionManager(shiroSessionManager()); return securityManager; } /** * @Description 自定义RealmImpl */ @Bean(name="shiroDbRealm") public ShiroDbRealm shiroDbRealm(){ return new ShiroDbRealmImpl(); } /** * @Description 会话管理器 */ @Bean(name="sessionManager") public DefaultWebSessionManager shiroSessionManager(){ DefaultWebSessionManager sessionManager = new DefaultWebSessionManager(); sessionManager.setSessionValidationSchedulerEnabled(false); sessionManager.setSessionIdCookieEnabled(true); sessionManager.setSessionIdCookie(simpleCookie()); sessionManager.setGlobalSessionTimeout(3600000); return sessionManager; } /** * @Description 保证实现了Shiro内部lifecycle函数的bean执行 */ @Bean(name = "lifecycleBeanPostProcessor") public static LifecycleBeanPostProcessor getLifecycleBeanPostProcessor() { return new LifecycleBeanPostProcessor(); } /** * @Description AOP式方法级权限检查 */ @Bean @DependsOn("lifecycleBeanPostProcessor") public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } /** * @Description 配合DefaultAdvisorAutoProxyCreator事项注解权限校验 */ @Bean public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor() { AuthorizationAttributeSourceAdvisor aasa = new AuthorizationAttributeSourceAdvisor(); aasa.setSecurityManager(defaultWebSecurityManager()); return new AuthorizationAttributeSourceAdvisor(); } /** * @Description 过滤器链 */ private Map<String, String> filterChainDefinition(){ List<Object> list = PropertiesUtil.propertiesShiro.getKeyList(); Map<String, String> map = new LinkedHashMap<>(); for (Object object : list) { String key = object.toString(); String value = PropertiesUtil.getShiroValue(key); log.info("读取防止盗链控制:---key{},---value:{}",key,value); map.put(key, value); } return map; } /** * @Description Shiro过滤器 */ @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(defaultWebSecurityManager()); shiroFilter.setFilterChainDefinitionMap(filterChainDefinition()); shiroFilter.setLoginUrl("/login"); shiroFilter.setUnauthorizedUrl("/login"); return shiroFilter; } }
6、Shiro过滤器、过滤器链
【1】过滤器
Shiro内置了很多默认的过滤器,比如身份验证、授权等相关的。默认过滤器可以参考org.apache.shiro.web.filter.mgt.DefaultFilter中的枚举过滤器
【2】过滤器链
定义:authentication.properties
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#其他链接是需要登录的
/**=authc
注意:这里定义的过滤器是有执行顺序的,从上向下执行
【3】加载原理分析
定义:PropertiesUtil,从classpath中加载authentication.properties
package com.itheima.shiro.properties; import com.itheima.shiro.utils.EmptyUtil; import lombok.extern.log4j.Log4j2; /** * @Description 读取Properties的工具类 */ @Log4j2 public class PropertiesUtil { public static LinkProperties propertiesShiro = new LinkProperties(); /** * 读取properties配置文件信息 */ static { String sysName = System.getProperty("sys.name"); if (EmptyUtil.isNullOrEmpty(sysName)) { sysName = "application.properties"; } else { sysName += ".properties"; } try { propertiesShiro.load(PropertiesUtil.class.getClassLoader() .getResourceAsStream("authentication.properties")); } catch (Exception e) { log.warn("资源路径中不存在authentication.properties权限文件,忽略读取!"); } } /** * 根据key得到value的值 */ public static String getShiroValue(String key) { return propertiesShiro.getProperty(key); } }
定义LinkProperties,这个类保证了Properties类的有序
package com.itheima.shiro.properties; import java.io.*; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Properties; /** * @Description 有序Properties类 */ public class LinkProperties extends Properties{ /** serialVersionUID */ private static final long serialVersionUID = 7573016303908223266L; private List<Object> keyList = new ArrayList<Object>(); /** * 默认构造方法 */ public LinkProperties() { } /** * 从指定路径加载信息到Properties * @param path */ public LinkProperties(String path) { try { InputStream is = new FileInputStream(path); this.load(is); } catch (FileNotFoundException e) { e.printStackTrace(); throw new RuntimeException("指定文件不存在!"); } catch (IOException e) { e.printStackTrace(); } } /** * 重写put方法,按照property的存入顺序保存key到keyList,遇到重复的后者将覆盖前者。 */ @Override public synchronized Object put(Object key, Object value) { this.removeKeyIfExists(key); keyList.add(key); return super.put(key, value); } /** * 重写remove方法,删除属性时清除keyList中对应的key。 */ @Override public synchronized Object remove(Object key) { this.removeKeyIfExists(key); return super.remove(key); } /** * keyList中存在指定的key时则将其删除 */ private void removeKeyIfExists(Object key) { keyList.remove(key); } /** * 获取Properties中key的有序集合 * @return */ public List<Object> getKeyList() { return keyList; } /** * 保存Properties到指定文件,默认使用UTF-8编码 * @param path 指定文件路径 */ public void store(String path) { this.store(path, "UTF-8"); } /** * 保存Properties到指定文件,并指定对应存放编码 * @param path 指定路径 * @param charset 文件编码 */ public void store(String path, String charset) { if (path != null && !"".equals(path)) { try { OutputStream os = new FileOutputStream(path); BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(os, charset)); this.store(bw, null); bw.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } else { throw new RuntimeException("存储路径不能为空!"); } } /** * 重写keys方法,返回根据keyList适配的Enumeration,且保持HashTable keys()方法的原有语义, * 每次都调用返回一个新的Enumeration对象,且和之前的不产生冲突 */ @Override public synchronized Enumeration<Object> keys() { return new EnumerationAdapter<Object>(keyList); } /** * List到Enumeration的适配器 */ private class EnumerationAdapter<T> implements Enumeration<T> { private int index = 0; private final List<T> list; private final boolean isEmpty; public EnumerationAdapter(List<T> list) { this.list = list; this.isEmpty = list.isEmpty(); } public boolean hasMoreElements() { //isEmpty的引入是为了更贴近HashTable原有的语义,在HashTable中添加元素前调用其keys()方法获得一个Enumeration的引用, //之后往HashTable中添加数据后,调用之前获取到的Enumeration的hasMoreElements()将返回false,但如果此时重新获取一个 //Enumeration的引用,则新Enumeration的hasMoreElements()将返回true,而且之后对HashTable数据的增、删、改都是可以在 //nextElement中获取到的。 return !isEmpty && index < list.size(); } public T nextElement() { if (this.hasMoreElements()) { return list.get(index++); } return null; } } }
查看shirocConfig
加载完整之后交于ShiroFilterFactoryBean使用setFilterChainDefinitionMap使得过滤生效
【4】自定义过滤器
上面我们使用了shiro的默认过滤器,但是由于业务需求,咱们可能要定义自己的过滤器,那么咱们定义呢?
这里我们先查看RolesAuthorizationFilter
分析:改源码表示,例如:/admin/order= roles["admin, root"] ,只有当放问该接口同时具备admin和root两种角色时,才可以被访问。
【5】自定义过滤器使用
【5.1】需求
1、实现只要有其中一个角色,则可访问对应路径
【5.2】RolesOrAuthorizationFilter
新建filter层,新建类RolesOrAuthorizationFilter
package com.itheima.shiro.filter; import org.apache.shiro.subject.Subject; import org.apache.shiro.util.CollectionUtils; import org.apache.shiro.web.filter.authz.AuthorizationFilter; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import java.io.IOException; import java.util.Set; /** * @Description:角色或关系 */ public class RolesOrAuthorizationFilter extends AuthorizationFilter { //TODO - complete JavaDoc @SuppressWarnings({"unchecked"}) public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException { Subject subject = getSubject(request, response); String[] rolesArray = (String[]) mappedValue; if (rolesArray == null || rolesArray.length == 0) { //no roles specified, so nothing to check - allow access. return true; } Set<String> roles = CollectionUtils.asSet(rolesArray); //循环roles判断只要有角色则返回true for (String role : roles) { if(subject.hasRole(role)){ return true; } } return false; } }
【5.3】编辑ShiroConfig
在ShiroConfig类中添加如下内容
/** * @Description 自定义过滤器定义 */ private Map<String, Filter> filters() { Map<String, Filter> map = new HashMap<String, Filter>(); map.put("role-or", new RolesOrAuthorizationFilter()); return map; } /** * @Description Shiro过滤器 */ @Bean("shiroFilter") public ShiroFilterFactoryBean shiroFilterFactoryBean(){ ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean(); shiroFilter.setSecurityManager(defaultWebSecurityManager()); //使自定义过滤器生效 shiroFilter.setFilters(filters()); shiroFilter.setFilterChainDefinitionMap(filterChainDefinition()); shiroFilter.setLoginUrl("/login"); shiroFilter.setUnauthorizedUrl("/login"); return shiroFilter; }
【2.2.3】编辑authentication.properties
#静态资源不过滤
/static/**=anon
#登录链接不过滤
/login/**=anon
#访问/resource/**需要有admin的角色
/resource/**=role-or[admin]
#其他链接是需要登录的
/**=authc
7、注解方式鉴权
【1】注解介绍
以下为常用注解
注解 | 说明 |
@RequiresAuthentication | 表明当前用户需是经过认证的用户 |
@ RequiresGuest | 表明该用户需为”guest”用户 |
@RequiresPermissions | 当前用户需拥有指定权限 |
@RequiresRoles | 当前用户需拥有指定角色 |
@ RequiresUser | 当前用户需为已认证用户或已记住用户 |
例如RoleAction类中我们添加
/** *@Description: 跳转到角色的初始化页面 */ @RequiresRoles(value ={"SuperAdmin","dev"},logical = Logical.OR) @RequestMapping(value = "listInitialize") public ModelAndView listInitialize(){ return new ModelAndView("/role/role-listInitialize"); }