JAVAWEB开发之权限管理(一)——权限管理详解(权限管理原理以及方案)、不使用权限框架的原始授权方式详解
2017-07-13 13:35
1186 查看
知识清单
1.了解基于资源的权限管理方式2. 掌握权限数据模型
3. 掌握基于url的权限管理(不使用Shiro权限框架的情况下实现权限管理)
4. shiro实现用户认证
5. shiro实现用户授权
6. shiro与企业web项目整合开发的方法
权限管理原理知识
什么是权限管理
只要有用户参与的系统一般都要有权限管理,权限管理实现对用户访问系统的控制。按照安全规则或安全策略控制用户可以访问而且只能访问自己被授权的资源。权限管理包括用户认证和用户授权两部分。
用户认证
用户认证概念
用户认证—— 用户去访问系统,系统需要验证用户身份的合法性。最常用的用户身份认证方法:1.用户密码方式、2.指纹打卡机、3.基于证书的验证方法。系统验证用户身份合法,用户方可访问系统的资源。用户认证流程
关键对象
subject:主体,理解为用户,可能是程序,都要去访问系统的资源,系统需要对subject进行身份认证。principal:身份信息,通常是唯一的,一个主体可以有多个身份信息,但是只能有一个主身份信息(primary principal)。
credential:凭证信息,可以是密码、证书、指纹等。
总结:主体在进行身份认证时需要提供身份信息和凭证信息。
用户授权
用户授权概念
用户授权,简单理解为访问控制,在用户认证通过后,系统对用户访问资源进行控制,当用户具有资源的访问权限方可访问。授权流程
其中橙色为授权流程
关键对象
授权的过程可以理解为 who 对 what(which) 进行how操作who:主体,即subject,subject在认证通过后,系统进行访问控制。
what(which):资源(Resource) ,subject必须具备资源访问权限才可以访问该资源。资源包括很多方面比如:用户列表页面、商品修改菜单、商品id为001的商品信息。
资源分为资源类型和资源实例:
例如系统的用户信息就是资源类型,相当于Java类。
系统中id为001的用户就是资源实例,相当于new的Java对象。
how:权限/许可(permission),针对资源的权限或许可,subject必须具有permission方可访问资源,如何访问/操作需要定义permission,权限比如:用户添加、用户添加、商品删除。
权限模型
主体(账号、密码)资源(资源名称,访问地址)
权限(权限名称、资源id)
角色(角色名称)
角色和权限关系(角色id、权限id)
如下图:
通常企业开发中将资源和权限合并为一张权限表,如下:
资源(资源名称、访问地址)
权限(权限名称、资源id)
合并为:
权限(权限名称、资源名称、资源访问地址)
上图被称为权限管理的通用模型,不过在企业开发中根据系统自身特点还会对上图进行修改,但是用户、角色、权限、用户角色关系、角色权限关系是必不可少的。
分配权限
用户需要分配相应的权限才可以访问相应的资源。权限是对资源的操作许可。通常给用户分配资源权限需要将权限信息持久化,比如存储在关系数据库中。
把用户信息、权限管理、用户分配的权限信息写入到数据库(权限数据模型)。
权限控制(授权核心)
基于角色的访问控制
RBAC (Role based access control) 基于角色的访问控制比如:
系统角色包括:部门经理、总经理...(角色针对用户进行划分)
系统中代码实现:
//如果该user是部门经理则可以访问if中的代码
if(user.getRole("部门经理")){
// 系统资源内容
// 用户报表查看
}
问题:
角色是针对人进行划分的,人作为用户在系统中属于活动内容,如果该角色可以访问的资源出现变更,则需要修改代码,比如:需要变更为部门经理和总经理都可以进行用户报表查看,代码改为:
if(user.getRole("部门经理") || user.getRole("总经理")){
// 系统资源内容
// 用户报表查看
}
由此可以发现基于角色的访问控制是不利于系统维护的(可扩展性不强)
基于资源的访问控制
RBAC (Resource based access control) 基于资源的访问控制资源在系统中是不变的,比如资源有:类中的方法,页面中的按钮
对资源的访问需要具有permission权限,代码可以写为:
if(user.hasPermission("用户报表查看(权限标识符)")){
// 系统资源内容
// 用户报表查看
}
上面的方法就可以解决用户角色变更而不用修改上边权限控制的代码。
如果需要变更权限只需要在分配权限模块去操作,给部门经理或总经理增加或解除权限
建议使用基于资源的访问控制实现权限管理。
权限管理解决方案
什么是粗粒度权限和细粒度权限?
粗粒度权限管理,是对资源类型的管理,资源类型比如:菜单、url连接、用户添加页面、用户信息、类方法、页面中按钮。粗粒度权限管理比如:超级管理员可以访问用户添加页面、用户信息等全部页面。
部门管理员可以访问用户信息页面,包括页面中所有按钮。
细粒度的权限管理,对资源实例的权限管理。资源实例就是资源类型的具体化,比如:用户id为001的修改连接,1110班的用户信息、行政部的员工。
细粒度的权限管理就是数据级别的权限管理。
细粒度权限管理比如:部门经理只可以访问本部门的员工信息,用户只可以看到自己的菜单,大区经理只能查看本辖区的销售订单...
粗粒度和细粒度例子:
系统中有一个用户查询页面,对用户列表查询分权限,如粗粒度管理,张三和李四都有用户列表查询的权限,张三和李四都可以访问用户列表查询。
进一步进行细粒度的管理,张三(行政部)和李四(开发部)只可以查询自己本部门的用户信息,张三只能查看行政部的用户信息,李四只能查询开发部门的用户信息。细粒度的权限管理就是数据级别的权限管理。
如何实现粗粒度和细粒度的权限管理
如何实现粗粒度的权限管理?粗粒度权限管理比较容易将权限管理代码抽取出来在系统架构级别统一管理。比如:通过SpringMVC的拦截器实现授权。
如何实现细粒度的权限管理?
对细粒度的权限管理在数据级别是没有共性可言的,针对细粒度的权限管理就是系统业务逻辑的一部分,如果在业务层去处理相对简单,如果将细粒度的权限管理统一在系统架构级别去抽取,比较困难,即使进行了抽取,功能也可能存在扩展性不全的弊端。建议细粒度权限管理放在业务层去控制。比如:部门经理只查询本部门员工信息,在Service接口提供一个部门id的参数,controller中根据当前用户信息得到该用户属于哪个部门,调用service时将部门id传入service,实现该用户只查询本部门的员工。
基于url拦截的方式实现
基于url拦截的方式实现在实际开发中是比较常用的一种方式。对于web系统,通过filter过滤器实现url拦截,也可以通过SpringMVC的拦截器实现基于URL的拦截。
使用权限管理框架来实现
对于粗粒度的权限管理,建议使用优秀的权限管理框架进行实现,节省开发成本,提高开发效率。Shiro就是一个优秀的权限管理框架。
基于URL的权限管理
基于url的权限管理流程
搭建环境
数据库
MySQL数据库中创建表:用户表、角色表、权限表(实质是权限和资源的结合)、用户角色关系表、角色权限关系表新建数据库shiro, 为了节约测试时间,在SpringMVC+mybatis基础之上进行整合(导入以前的基本数据),并导入权限数据如下:
有关权限的SQL脚本如下:
shiro_sql_table.sql
/* SQLyog v10.2 MySQL - 5.1.72-community : Database - shiro ********************************************************************* */ /*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=''*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; /*Table structure for table `sys_permission` */ CREATE TABLE `sys_permission` ( `id` bigint(20) NOT NULL COMMENT '主键', `name` varchar(128) NOT NULL COMMENT '资源名称', `type` varchar(32) NOT NULL COMMENT '资源类型:menu,button,', `url` varchar(128) DEFAULT NULL COMMENT '访问url地址', `percode` varchar(128) DEFAULT NULL COMMENT '权限代码字符串', `parentid` bigint(20) DEFAULT NULL COMMENT '父结点id', `parentids` varchar(128) DEFAULT NULL COMMENT '父结点id列表串', `sortstring` varchar(128) DEFAULT NULL COMMENT '排序号', `available` char(1) DEFAULT NULL COMMENT '是否可用,1:可用,0不可用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Table structure for table `sys_role` */ CREATE TABLE `sys_role` ( `id` varchar(36) NOT NULL, `name` varchar(128) NOT NULL, `available` char(1) DEFAULT NULL COMMENT '是否可用,1:可用,0不可用', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Table structure for table `sys_role_permission` */ CREATE TABLE `sys_role_permission` ( `id` varchar(36) NOT NULL, `sys_role_id` varchar(32) NOT NULL COMMENT '角色id', `sys_permission_id` varchar(32) NOT NULL COMMENT '权限id', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Table structure for table `sys_user` */ CREATE TABLE `sys_user` ( `id` varchar(36) NOT NULL COMMENT '主键', `usercode` varchar(32) NOT NULL COMMENT '账号', `username` varchar(64) NOT NULL COMMENT '姓名', `password` varchar(32) NOT NULL COMMENT '密码', `salt` varchar(64) DEFAULT NULL COMMENT '盐', `locked` char(1) DEFAULT NULL COMMENT '账号是否锁定,1:锁定,0未锁定', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*Table structure for table `sys_user_role` */ CREATE TABLE `sys_user_role` ( `id` varchar(36) NOT NULL, `sys_user_id` varchar(32) NOT NULL, `sys_role_id` varchar(32) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;shiro_sql_table_data.sql
/* SQLyog v10.2 MySQL - 5.1.72-community : Database - shiro ********************************************************************* */ /*!40101 SET NAMES utf8 */; /*!40101 SET SQL_MODE=''*/; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; /*Data for the table `sys_permission` */ insert into `sys_permission`(`id`,`name`,`type`,`url`,`percode`,`parentid`,`parentids`,`sortstring`,`available`) values (1,'权限','','',NULL,0,'0/','0','1'),(11,'商品管理','menu','/item/queryItem.action',NULL,1,'0/1/','1.','1'), (12,'商品新增','permission','/item/add.action','item:create',11,'0/1/11/','','1'), (13,'商品修改','permission','/item/editItem.action','item:update',11,'0/1/11/','','1'), (14,'商品删除','permission','','item:delete',11,'0/1/11/','','1'), (15,'商品查询','permission','/item/queryItem.action','item:query',11,'0/1/15/',NULL,'1'), (21,'用户管理','menu','/user/query.action','user:query',1,'0/1/','2.','1'), (22,'用户新增','permission','','user:create',21,'0/1/21/','','1'), (23,'用户修改','permission','','user:update',21,'0/1/21/','','1'), (24,'用户删除','permission','','user:delete',21,'0/1/21/','','1'); /*Data for the table `sys_role` */ insert into `sys_role`(`id`,`name`,`available`) values ('ebc8a441-c6f9-11e4-b137-0adc305c3f28','商品管理员','1'), ('ebc9d647-c6f9-11e4-b137-0adc305c3f28','用户管理员','1'); /*Data for the table `sys_role_permission` */ insert into `sys_role_permission`(`id`,`sys_role_id`,`sys_permission_id`) values ('ebc8a441-c6f9-11e4-b137-0adc305c3f21','ebc8a441-c6f9-11e4-b137-0adc305c','12'), ('ebc8a441-c6f9-11e4-b137-0adc305c3f22','ebc8a441-c6f9-11e4-b137-0adc305c','11'), ('ebc8a441-c6f9-11e4-b137-0adc305c3f24','ebc9d647-c6f9-11e4-b137-0adc305c','21'), ('ebc8a441-c6f9-11e4-b137-0adc305c3f25','ebc8a441-c6f9-11e4-b137-0adc305c','15'), ('ebc9d647-c6f9-11e4-b137-0adc305c3f23','ebc9d647-c6f9-11e4-b137-0adc305c','22'), ('ebc9d647-c6f9-11e4-b137-0adc305c3f26','ebc8a441-c6f9-11e4-b137-0adc305c','13'); /*Data for the table `sys_user` */ insert into `sys_user`(`id`,`usercode`,`username`,`password`,`salt`,`locked`) values ('lisi','lisi','李四','bf07fd8bbc73b6f70b8319f2ebb87483','uiwueylm','0'), ('zhangsan','zhangsan','张三','cb571f7bd7a6f73ab004a70322b963d5','eteokues','0'); /*Data for the table `sys_user_role` */ insert into `sys_user_role`(`id`,`sys_user_id`,`sys_role_id`) values ('ebc8a441-c6f9-11e4-b137-0adc305c3f28','zhangsan','ebc8a441-c6f9-11e4-b137-0adc305c'), ('ebc9d647-c6f9-11e4-b137-0adc305c3f28','lisi','ebc9d647-c6f9-11e4-b137-0adc305c'); /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;查看对应权限模型的数据如下:
sys_user 用户表数据
sys_role 角色表
sys_permission 权限表
sys_user_role 用户角色关系表
sys_role_permission 角色权限关系表
开发环境
JDK1.8MyEclipse
技术架构:SpringMVC+Mybatis+jQuery easyUI
系统工程架构
系统登录
系统登录相当于用户身份认证,用户登录成功,要在Session中记录用户的身份信息。操作流程:
用户进入登录页面。
输入用户名和密码进行登陆。
进行用户名和密码校验。
如果校验通过,在Session中记录用户身份信息。
用户的身份信息
创建专门类用于记录用户身份信息。/** * 用户身份信息,存入Session 由于Tomcat正常关闭时会将Session序列化的本地硬盘上,所以实现Serializable接口 * @author liuxun * */ public class ActiveUser implements Serializable { private String userid; //用户id(主键) private String usercode; // 用户账号 private String username; // 用户姓名 .... .... }
mapper
mapper接口:根据用户账号查询用户(sys_user)信息 (使用逆向工程生成权限相关的PO类和mapper接口)如下所示:
将生成的代码拷贝到项目中
service(进行用户名和密码校验)
接口功能:根据用户的身份和密码进行认证,如果认证通过,返回用户身份信息。认证过程:
根据用户身份(账号)查询数据库,如果查询不到 则抛出用户不存在
对输入的密码和数据库密码进行比对,如果一致,认证通过。
新建权限管理Service接口 添加身份认证方法
/** * 认证授权服务接口 * @author liuxun * */ public interface SysService { //根据用户的身份和密码进行认证,如果认证通过,返回用户身份信息 public ActiveUser authenticat(String usercode,String password) throws Exception; //根据用户账号查询用户信息 public SysUser findSysUserByUserCode(String userCode) throws Exception; ...... }方法实现:
public class SysServiceImpl implements SysService { @Autowired private SysUserMapper sysUserMapper; public ActiveUser authenticat(String usercode, String password) throws Exception { /** * 认证过程: 根据用户身份(账号)查询数据库,如果查询不到则用户不存在 * 对输入的密码和数据库密码进行比对,如果一致则认证通过 */ // 根据用户账号查询数据库 SysUser sysUser = this.findSysUserByUserCode(usercode); if (sysUser == null) { // 抛出异常 throw new CustomException("用户账号不存在"); } // 数据库密码(MD5加密后的密码) String password_db = sysUser.getPassword(); // 对输入的密码和数据库密码进行比对,如果一致,认证通过 // 对页面输入的密码进行MD5加密 String password_input_md5 = new MD5().getMD5ofStr(password); if (!password_db.equalsIgnoreCase(password_input_md5)) { //抛出异常 throw new CustomException("用户名或密码错误"); } //得到用户id String userid = sysUser.getId(); //认证通过,返回用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(userid); activeUser.setUsercode(usercode); activeUser.setUsername(sysUser.getUsername()); return activeUser; } public SysUser findSysUserByUserCode(String userCode) throws Exception { SysUserExample sysUserExample = new SysUserExample(); SysUserExample.Criteria criteria = sysUserExample.createCriteria(); criteria.andUsercodeEqualTo(userCode); List<SysUser> list = sysUserMapper.selectByExample(sysUserExample); if (list != null && list.size() > 0) { return list.get(0); } return null; } ...... }配置Service,往类Service中使用@Autowire 需要注册Service 注册有两种方法(注解或配置文件),在架构时没有配置扫描Service 需要在配置文件中注册Service
<!-- 认证和授权的Service --> <bean id="sysService" class="liuxun.ssm.service.impl.SysServiceImpl"></bean>
controller(记录Session)
//用户登录提交方法 @RequestMapping("/login") public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{ // 校验验证码,防止恶性攻击 // 从Session中获取正确的验证码 String validateCode = (String) session.getAttribute("validateCode"); //输入的验证码和Session中的验证码进行对比 if (!randomcode.equalsIgnoreCase(validateCode)) { //抛出异常 throw new CustomException("验证码输入错误"); } //调用Service校验用户账号和密码的正确性 ActiveUser activeUser = sysService.authenticat(usercode, password); //如果Service校验通过,将用户身份记录到Session session.setAttribute("activeUser", activeUser); //重定向到商品查询页面 return "redirect:/first.action"; }
用户认证拦截器
anonymousURL.properties配置匿名URL
配置可以匿名访问的URL编写身份认证拦截器
//用于用户认证校验、用户权限校验 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到请求的url String url = request.getRequestURI(); //判断是否是公开地址 //实际开发中需要将公开地址配置在配置文件中 //从配置文件中取出可以匿名访问的URL List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL"); for (String open_url : open_urls) { if (url.indexOf(open_url)>=0) { //如果是公开地址 则放行 return true; } } //判断用户身份在Session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //如果用户身份在session中存在则放行 if (activeUser!=null) { return true; } //执行到这里拦截,跳转到登录页面,用户进行身份认证 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); //如果返回false表示拦截器不继续执行handler,如果返回true表示放行 return false; }
配置认证拦截器
<!-- 拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 用户认证拦截 --> <mvc:mapping path="/**"/> <bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>
用户授权
commonURL.properties配置公用访问地址
在此配置文件中配置公用访问地址,公用访问地址只需要通过用户认证,不需要对公用访问地址分配权限即可访问。获取用户权限范围的菜单
思路:在用户认证时,认证通过,根据用户id从数据库获取用户权限范围内的菜单,将菜单的集合存储在Session中。
编辑存储用户身份信息的类ActiveUser 如下所示:
public class ActiveUser implements Serializable { private String userid; //用户id(主键) private String usercode; // 用户账号 private String username; // 用户姓名 private List<SysPermission> menus; //菜单 //......setter和getter方法 }自定义权限Mapper
因为使用逆向工程生成的Mapper是不建议去修改的 因为它的代码联系非常紧密,一旦修改错误 就会牵一发而动全身。所以需要自定义一个权限的Mapper(SysPermissionMapperCustom)
在SysPermissionMapperCustom.xml中添加根据用户id查询用户权限的菜单
<!-- 根据用户id查询菜单 --> <select id="findMenuListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission"> SELECT * FROM sys_permission WHERE TYPE = 'menu' AND id IN (SELECT sys_permission_id FROM sys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROM sys_user_role WHERE sys_user_id = #{userid})) </select>在SysPermissionMapperCustom.java接口中添加对应的方法
public interface SysPermissionMapperCustom { //根据用户id查询菜单 public List<SysPermission> findMenuListByUserId(String userid) throws Exception; ....... }在权限Service接口中添加对应的方法 在实现中注入SysPermissionMapperCustom
SysServiceImpl.java中添加如下内容
@Override public List<SysPermission> findMenuListByUserId(String userid) throws Exception { return sysPermissionMapperCustom.findMenuListByUserId(userid); }
获取用户权限范围的URL
思路:在用户认证时,认证通过后,根据用户id从数据库中获取用户权限范围的URL,将URL的集合存储在Session中。
修改ActiveUser 添加URL的权限集合
public class ActiveUser implements Serializable { private String userid; //用户id(主键) private String usercode; // 用户账号 private String username; // 用户姓名 private List<SysPermission> menus; //菜单 private List<SysPermission> permissions; //权限 //...setter和getter方法 }在SysPermissionMapperCustom.xml中添加根据用户id查询用户权限的URL
<!-- 根据用户id查询URL --> <select id="findPermissionListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission"> SELECT * FROM sys_permission WHERE TYPE = 'permission' AND id IN (SELECT sys_permission_id FROM sys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROM sys_user_role WHERE sys_user_id = #{userid})) </select>
在SysPermissionMapperCustom.java接口中添加对应的方法
//根据用户id查询权限URL public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;SysServiceImpl.java中添加如下内容
@Override public List<SysPermission> findPermissionListByUserId(String userid) throws Exception { return sysPermissionMapperCustom.findPermissionListByUserId(userid); }
用户认证通过后取出菜单和URL放入Session
修改权限SysServiceImpl中用户认证方法的代码//得到用户id String userid = sysUser.getId(); //根据用户id查询菜单 List<SysPermission> menus = this.findMenuListByUserId(userid); //根据用户id查询权限url List<SysPermission> permissions = this.findPermissionListByUserId(userid); //认证通过,返回用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(userid); activeUser.setUsercode(usercode); activeUser.setUsername(sysUser.getUsername()); //放入权限范围的菜单和url activeUser.setMenus(menus); activeUser.setPermissions(permissions);
菜单动态显示
<c:if test="${activeUser.menus!=null }"> <ul> <c:forEach items="${activeUser.menus }" var="menu"> <li><div> <a title="${menu.name }" ref="1_1" href="#" rel="${baseurl }/${menu.url }" icon="icon-log"><span class="icon icon-log"> </span><span class="nav"><a href=javascript:addTab('${menu.name }','${baseurl }/${menu.url }')>${menu.name }</a></span></a> </div></li> </c:forEach> </ul> </c:if>
授权拦截器
public class PermissionInterceptor implements HandlerInterceptor{ //在执行handler之前执行的 //用于用户认证校验、用户权限校验 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到请求的url String url = request.getRequestURI(); //判断是否是公开地址 //实际开发中需要将公开地址配置在配置文件中 //从配置文件中取出可以匿名访问的URL List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL"); for (String open_url : open_urls) { if (url.indexOf(open_url)>=0) { //如果是公开地址 则放行 return true; } } //从配置文件中获取公用访问url List<String> common_urls = ResourcesUtil.getKeyList("commonURL"); //遍历公用地址 如果是公开地址则放行 for (String common_url : common_urls) { if (url.indexOf(common_url)>0) { //如果是公开,则放行 return true; } } //判断用户身份在Session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //从Session中取出权限范围的URL List<SysPermission> permissions = activeUser.getPermissions(); for (SysPermission sysPermission : permissions) { //权限url String permission_url = sysPermission.getUrl(); if (url.indexOf(permission_url)>0) { return true; } } //执行到这里拦截,跳转到无权访问的提示页面 request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response); //如果返回false表示拦截器不继续执行handler,如果返回true表示放行 return false; } ...... }
配置授权拦截器
注意:要将授权拦截器配置在用户认证拦截器的下边,这是因为SpringMVC拦截器的放行方法是顺序执行的,如果是Struts的话则正好相反。<!-- 拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 用户认证拦截 --> <mvc:mapping path="/**"/> <bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <!-- 资源拦截 --> <mvc:mapping path="/**"/> <bean class="liuxun.ssm.controller.interceptor.PermissionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>运行测试:
此项目Demo已上传GitHub(https://github.com/LX1993728/permission_web_noshiro)
其关键代码如下:
PO类ActiveUser.java 存放用户身份和权限信息的类
package liuxun.ssm.po; import java.io.Serializable; import java.util.List; /** * 用户身份信息,存入Session 由于Tomcat正常关闭时会将Session序列化的本地硬盘上,所以实现Serializable接口 * @author liuxun * */ public class ActiveUser implements Serializable { private static final long serialVersionUID = 1L; private String userid; //用户id(主键) private String usercode; // 用户账号 private String username; // 用户姓名 private List<SysPermission> menus; //菜单 private List<SysPermission> permissions; //权限 // 提供对应setter和getter方法 ...... }自定义权限的Mapper
SysPermissionMapperCustom.java
package liuxun.ssm.mapper;SysPermissionMapperCustom.xml
import java.util.List;
import liuxun.ssm.po.SysPermission;
import liuxun.ssm.po.SysPermissionExample;
import org.apache.ibatis.annotations.Param;
/**
* 权限mapper
* @author liuxun
*
*/
public interface SysPermissionMapperCustom {
//根据用户id查询菜单
public List<SysPermission> findMenuListByUserId(String userid) throws Exception;
//根据用户id查询权限URL public List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
}
<?xml version="1.0" encoding="UTF-8"?>自定义权限的Service接口以及实现类
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="liuxun.ssm.mapper.SysPermissionMapperCustom">
<!-- 根据用户id查询菜单 --> <select id="findMenuListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission"> SELECT * FROM sys_permission WHERE TYPE = 'menu' AND id IN (SELECT sys_permission_id FROM sys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROM sys_user_role WHERE sys_user_id = #{userid})) </select>
<!-- 根据用户id查询URL --> <select id="findPermissionListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission"> SELECT * FROM sys_permission WHERE TYPE = 'permission' AND id IN (SELECT sys_permission_id FROM sys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROM sys_user_role WHERE sys_user_id = #{userid})) </select>
</mapper>
SysService.java
package liuxun.ssm.service; import java.util.List; import liuxun.ssm.po.ActiveUser; import liuxun.ssm.po.SysPermission; import liuxun.ssm.po.SysUser; /** * 认证授权服务接口 * @author liuxun * */ public interface SysService { //根据用户的身份和密码进行认证,如果认证通过,返回用户身份信息 public ActiveUser authenticat(String usercode,String password) throws Exception; //根据用户账号查询用户信息 public SysUser findSysUserByUserCode(String userCode) throws Exception; //根据用户id查询权限范围内的菜单 public List<SysPermission> findMenuListByUserId(String userid) throws Exception; //根据用户id查询权限范围内的url public List<SysPermission> findPermissionListByUserId(String userid) throws Exception; }SysServiceImpl.java
package liuxun.ssm.service.impl;登录控制器
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import liuxun.ssm.exception.CustomException;
import liuxun.ssm.mapper.SysPermissionMapperCustom;
import liuxun.ssm.mapper.SysUserMapper;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.po.SysPermission;
import liuxun.ssm.po.SysUser;
import liuxun.ssm.po.SysUserExample;
import liuxun.ssm.service.SysService;
import liuxun.ssm.util.MD5;
public class SysServiceImpl implements SysService {
@Autowired
private SysUserMapper sysUserMapper;
@Autowired
private SysPermissionMapperCustom sysPermissionMapperCustom;
public ActiveUser authenticat(String usercode, String password) throws Exception {
/**
* 认证过程: 根据用户身份(账号)查询数据库,如果查询不到则用户不存在
* 对输入的密码和数据库密码进行比对,如果一致则认证通过
*/
// 根据用户账号查询数据库
SysUser sysUser = this.findSysUserByUserCode(usercode);
if (sysUser == null) {
// 抛出异常
throw new CustomException("用户账号不存在");
}
// 数据库密码(MD5加密后的密码)
String password_db = sysUser.getPassword();
// 对输入的密码和数据库密码进行比对,如果一致,认证通过
// 对页面输入的密码进行MD5加密
String password_input_md5 = new MD5().getMD5ofStr(password);
if (!password_db.equalsIgnoreCase(password_input_md5)) {
//抛出异常
throw new CustomException("用户名或密码错误");
}
//得到用户id String userid = sysUser.getId(); //根据用户id查询菜单 List<SysPermission> menus = this.findMenuListByUserId(userid); //根据用户id查询权限url List<SysPermission> permissions = this.findPermissionListByUserId(userid); //认证通过,返回用户身份信息 ActiveUser activeUser = new ActiveUser(); activeUser.setUserid(userid); activeUser.setUsercode(usercode); activeUser.setUsername(sysUser.getUsername()); //放入权限范围的菜单和url activeUser.setMenus(menus); activeUser.setPermissions(permissions);
return activeUser;
}
public SysUser findSysUserByUserCode(String userCode) throws Exception {
SysUserExample sysUserExample = new SysUserExample();
SysUserExample.Criteria criteria = sysUserExample.createCriteria();
criteria.andUsercodeEqualTo(userCode);
List<SysUser> list = sysUserMapper.selectByExample(sysUserExample);
if (list != null && list.size() > 0) {
return list.get(0);
}
return null;
}
@Override public List<SysPermission> findMenuListByUserId(String userid) throws Exception { return sysPermissionMapperCustom.findMenuListByUserId(userid); }
@Override public List<SysPermission> findPermissionListByUserId(String userid) throws Exception { return sysPermissionMapperCustom.findPermissionListByUserId(userid); }
}
package liuxun.ssm.controller;身份认证拦截器LoginInterceptor.java
import javax.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import liuxun.ssm.exception.CustomException;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.service.SysService;
/**
* 登录和退出
* @author liuxun
*
*/
@Controller
public class LoginController {
@Autowired
private SysService sysService;
//用户登录提交方法 @RequestMapping("/login") public String login(HttpSession session,String randomcode,String usercode,String password) throws Exception{ // 校验验证码,防止恶性攻击 // 从Session中获取正确的验证码 String validateCode = (String) session.getAttribute("validateCode"); //输入的验证码和Session中的验证码进行对比 if (!randomcode.equalsIgnoreCase(validateCode)) { //抛出异常 throw new CustomException("验证码输入错误"); } //调用Service校验用户账号和密码的正确性 ActiveUser activeUser = sysService.authenticat(usercode, password); //如果Service校验通过,将用户身份记录到Session session.setAttribute("activeUser", activeUser); //重定向到商品查询页面 return "redirect:/first.action"; }
//用户退出
@RequestMapping("/logout")
public String logout(HttpSession session) throws Exception{
//session失效
session.invalidate();
//重定向到商品查询页面
return "redirect:/first.action";
}
}
package liuxun.ssm.controller.interceptor;资源授权拦截器PermissionInterceptor
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import liuxun.ssm.po.ActiveUser;
import liuxun.ssm.util.ResourcesUtil;
/**
* 测试拦截器1
* @author liuxun
*
*/
public class LoginInterceptor implements HandlerInterceptor{
//在执行handler之前执行的
//用于用户认证校验、用户权限校验 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到请求的url String url = request.getRequestURI(); //判断是否是公开地址 //实际开发中需要将公开地址配置在配置文件中 //从配置文件中取出可以匿名访问的URL List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL"); for (String open_url : open_urls) { if (url.indexOf(open_url)>=0) { //如果是公开地址 则放行 return true; } } //判断用户身份在Session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //如果用户身份在session中存在则放行 if (activeUser!=null) { return true; } //执行到这里拦截,跳转到登录页面,用户进行身份认证 request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response); //如果返回false表示拦截器不继续执行handler,如果返回true表示放行 return false; }
//在执行handler返回modelAndView之前执行
//如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
throws Exception {
System.out.println("HandlerInterceptor2...postHandle");
}
//执行handler之后执行此方法
//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长
//实现系统,统一日志记录
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)
throws Exception {
System.out.println("HandlerInterceptor2...afterCompletion");
}
}
package liuxun.ssm.controller.interceptor; import java.security.acl.Permission; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; import liuxun.ssm.po.ActiveUser; import liuxun.ssm.po.SysPermission; import liuxun.ssm.util.ResourcesUtil; /** * 授权拦截器 * @author liuxun * */ public class PermissionInterceptor implements HandlerInterceptor{ //在执行handler之前执行的 //用于用户认证校验、用户权限校验 @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { //得到请求的url String url = request.getRequestURI(); //判断是否是公开地址 //实际开发中需要将公开地址配置在配置文件中 //从配置文件中取出可以匿名访问的URL List<String> open_urls = ResourcesUtil.getKeyList("anonymousURL"); for (String open_url : open_urls) { if (url.indexOf(open_url)>=0) { //如果是公开地址 则放行 return true; } } //从配置文件中获取公用访问url List<String> common_urls = ResourcesUtil.getKeyList("commonURL"); //遍历公用地址 如果是公开地址则放行 for (String common_url : common_urls) { if (url.indexOf(common_url)>0) { //如果是公开,则放行 return true; } } //判断用户身份在Session中是否存在 HttpSession session = request.getSession(); ActiveUser activeUser = (ActiveUser) session.getAttribute("activeUser"); //从Session中取出权限范围的URL List<SysPermission> permissions = activeUser.getPermissions(); for (SysPermission sysPermission : permissions) { //权限url String permission_url = sysPermission.getUrl(); if (url.indexOf(permission_url)>0) { return true; } } //执行到这里拦截,跳转到无权访问的提示页面 request.getRequestDispatcher("/WEB-INF/jsp/refuse.jsp").forward(request, response); //如果返回false表示拦截器不继续执行handler,如果返回true表示放行 return false; } //在执行handler返回modelAndView之前执行 //如果需要向页面提供一些公用的数据或配置一些视图信息,使用此方法实现 从modelAndView入手 @Override public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("HandlerInterceptor2...postHandle"); } //执行handler之后执行此方法 //作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长 //实现系统,统一日志记录 @Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView) throws Exception { System.out.println("HandlerInterceptor2...afterCompletion"); } }拦截器配置
<!-- 拦截器 --> <mvc:interceptors> <mvc:interceptor> <!-- 用户认证拦截 --> <mvc:mapping path="/**"/> <bean class="liuxun.ssm.controller.interceptor.LoginInterceptor"></bean> </mvc:interceptor> <mvc:interceptor> <!-- 资源拦截 --> <mvc:mapping path="/**"/> <bean class="liuxun.ssm.controller.interceptor.PermissionInterceptor"></bean> </mvc:interceptor> </mvc:interceptors>使用URL拦截总结:
使用基于URL拦截的权限管理方式,实现起来比较简单,不依赖框架使用过滤器或拦截器就可以实现
弊端:需要将所有的URL全部配置起来,比较繁琐,不易维护,URL(资源)和权限表示方式不规范
相关文章推荐
- JAVAWEB开发之权限管理(二)——shiro入门详解以及使用方法、shiro认证与shiro授权
- JAVAWEB开发之工作流详解(二)——Activiti核心API的使用(流程定义和流程实例的管理、流程变量、监听器...)以及与Spring的集成
- JAVAWEB开发之Hibernate详解(一)——Hibernate的框架概述、开发流程、CURD操作和核心配置与API以及Hibernate日志的使用
- JAVAWEB开发之SpringMVC详解(一)——SpringMVC的框架原理、架构简介、与mybatis整合和注解方式的使用、
- JAVAWEB开发之SpringMVC详解(一)——SpringMVC的框架原理、架构简介、与mybatis整合和注解方式的使用、
- JAVAWEB开发之Hibernate详解(一)——Hibernate的框架概述、开发流程、CURD操作和核心配置与API以及Hibernate日志的使用
- JAVAWEB开发之Servlet Filter(过滤器)详解包括post和get编码过滤器、URL访问权限控制、自动登录。以及装饰模式的使用
- AndroidAnnotations开发框架在Eclipse中的搭建和使用以及框架实现的原理
- Winform开发框架之权限管理系统改进的经验总结(1)-TreeListLookupEdit控件的使用
- JAVAWEB开发之国际化与Ajax(重点)详解(以及jsonlib、xsream的使用)
- SpringBoot开发详解(六)-- 异常统一管理以及AOP的使用
- Golang开发环境搭建(Notepad++、LiteIDE两种方式以及martini框架使用)
- JAVAWEB开发之Maven的入门详解——Maven的安装以及项目的结构和Maven的使用以及私服的搭建与配置
- Golang开发环境搭建(Notepad++、LiteIDE两种方式以及martini框架使用)
- JAVAWEB开发之mybatis详解(一)——mybatis的入门(实现增删改查操作)、自定义别名、抽取代码块以及动态SQL的使用
- JAVAWEB开发之Spring详解之——Spring事务管理详解(四种事务管理方式)
- SpringBoot开发详解(六)-- 异常统一管理以及AOP的使用
- Golang开发环境搭建(Notepad++、LiteIDE两种方式以及martini框架使用)
- JAVAWEB开发之Session的追踪创建和销毁、JSP详解(指令,标签,内置对象,动作即转发和包含)、JavaBean及内省技术以及EL表达式获取内容的使用
- JAVAWEB开发之数据库简介、MySQL的安装与卸载(处理中文乱码)、以及MySQL语言(DDL、DML、DCL、DQL)的详解和使用