知识清单

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.8
MyEclipse
技术架构: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 {@Autowiredprivate 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("用户名或密码错误");}//得到用户idString 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校验通过,将用户身份记录到Sessionsession.setAttribute("activeUser", activeUser);//重定向到商品查询页面return "redirect:/first.action";}

用户认证拦截器

anonymousURL.properties配置匿名URL

配置可以匿名访问的URL

编写身份认证拦截器

//用于用户认证校验、用户权限校验@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//得到请求的urlString url = request.getRequestURI();//判断是否是公开地址//实际开发中需要将公开地址配置在配置文件中//从配置文件中取出可以匿名访问的URLList<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 * FROMsys_permission WHERE TYPE = 'menu' AND id IN (SELECT sys_permission_id FROMsys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROMsys_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中添加如下内容
@Overridepublic 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 * FROMsys_permission WHERE TYPE = 'permission' AND id IN (SELECT sys_permission_id FROMsys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROMsys_user_role WHERE sys_user_id = #{userid}))
</select>
在 SysPermissionMapperCustom .java接口中添加对应的方法
//根据用户id查询权限URLpublic List<SysPermission> findPermissionListByUserId(String userid) throws Exception;

SysServiceImpl.java中添加如下内容

@Overridepublic List<SysPermission> findPermissionListByUserId(String userid) throws Exception {return sysPermissionMapperCustom.findPermissionListByUserId(userid);}

用户认证通过后取出菜单和URL放入Session

修改权限SysServiceImpl中用户认证方法的代码

//得到用户idString userid = sysUser.getId();//根据用户id查询菜单List<SysPermission> menus = this.findMenuListByUserId(userid);//根据用户id查询权限urlList<SysPermission> permissions = this.findPermissionListByUserId(userid);//认证通过,返回用户身份信息ActiveUser activeUser = new ActiveUser();activeUser.setUserid(userid);activeUser.setUsercode(usercode);activeUser.setUsername(sysUser.getUsername());//放入权限范围的菜单和urlactiveUser.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"><spanclass="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之前执行的//用于用户认证校验、用户权限校验@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//得到请求的urlString url = request.getRequestURI();//判断是否是公开地址//实际开发中需要将公开地址配置在配置文件中//从配置文件中取出可以匿名访问的URLList<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");for (String open_url : open_urls) {if (url.indexOf(open_url)>=0) {//如果是公开地址 则放行return true;}}//从配置文件中获取公用访问urlList<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中取出权限范围的URLList<SysPermission> permissions = activeUser.getPermissions();for (SysPermission sysPermission : permissions) {//权限urlString 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;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查询权限URLpublic List<SysPermission> findPermissionListByUserId(String userid) throws Exception;
}

SysPermissionMapperCustom.xml

<?xml version="1.0" encoding="UTF-8"?>
<!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 * FROMsys_permission WHERE TYPE = 'menu' AND id IN (SELECT sys_permission_id FROMsys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROMsys_user_role WHERE sys_user_id = #{userid}))
</select>
<!-- 根据用户id查询URL -->
<select id="findPermissionListByUserId" parameterType="string" resultType="liuxun.ssm.po.SysPermission">SELECT * FROMsys_permission WHERE TYPE = 'permission' AND id IN (SELECT sys_permission_id FROMsys_role_permission WHERE sys_role_id IN (SELECT sys_role_id FROMsys_user_role WHERE sys_user_id = #{userid}))
</select>
</mapper>

自定义权限的Service接口以及实现类

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查询权限范围内的urlpublic 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 {@Autowiredprivate SysUserMapper sysUserMapper;@Autowiredprivate 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("用户名或密码错误");}//得到用户idString userid = sysUser.getId();//根据用户id查询菜单List<SysPermission> menus = this.findMenuListByUserId(userid);//根据用户id查询权限urlList<SysPermission> permissions = this.findPermissionListByUserId(userid);//认证通过,返回用户身份信息ActiveUser activeUser = new ActiveUser();activeUser.setUserid(userid);activeUser.setUsercode(usercode);activeUser.setUsername(sysUser.getUsername());//放入权限范围的菜单和urlactiveUser.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;}@Overridepublic List<SysPermission> findMenuListByUserId(String userid) throws Exception {return sysPermissionMapperCustom.findMenuListByUserId(userid);}@Overridepublic List<SysPermission> findPermissionListByUserId(String userid) throws Exception {return sysPermissionMapperCustom.findPermissionListByUserId(userid);}
}

登录控制器

package liuxun.ssm.controller;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 {@Autowiredprivate 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校验通过,将用户身份记录到Sessionsession.setAttribute("activeUser", activeUser);//重定向到商品查询页面return "redirect:/first.action";}//用户退出@RequestMapping("/logout")public String logout(HttpSession session) throws Exception{//session失效session.invalidate();//重定向到商品查询页面return "redirect:/first.action";}
}

身份认证拦截器LoginInterceptor.java

package liuxun.ssm.controller.interceptor;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之前执行的//用于用户认证校验、用户权限校验@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//得到请求的urlString url = request.getRequestURI();//判断是否是公开地址//实际开发中需要将公开地址配置在配置文件中//从配置文件中取出可以匿名访问的URLList<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入手@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {System.out.println("HandlerInterceptor2...postHandle");}//执行handler之后执行此方法//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长//实现系统,统一日志记录@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception modelAndView)throws Exception {System.out.println("HandlerInterceptor2...afterCompletion");}}

资源授权拦截器PermissionInterceptor

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之前执行的//用于用户认证校验、用户权限校验@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//得到请求的urlString url = request.getRequestURI();//判断是否是公开地址//实际开发中需要将公开地址配置在配置文件中//从配置文件中取出可以匿名访问的URLList<String> open_urls = ResourcesUtil.getKeyList("anonymousURL");for (String open_url : open_urls) {if (url.indexOf(open_url)>=0) {//如果是公开地址 则放行return true;}}//从配置文件中获取公用访问urlList<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中取出权限范围的URLList<SysPermission> permissions = activeUser.getPermissions();for (SysPermission sysPermission : permissions) {//权限urlString 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入手@Overridepublic void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)throws Exception {System.out.println("HandlerInterceptor2...postHandle");}//执行handler之后执行此方法//作为系统统一异常处理,进行方法执行性能监控,在preHandler中设置一个时间点 在afterCompletion设置一个时间点 二者时间差就是执行时长//实现系统,统一日志记录@Overridepublic 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开发之权限管理(一)——权限管理详解(权限管理原理以及方案)、不使用权限框架的原始授权方式详解相关推荐

  1. JavaWeb开发:Excel文件上传、解析、过滤,并存入数据库(基于SSM框架)

    目录 一.效果展示 1.初始状态 2.导入的Excel文件 3.导入后状态 4.文件信息过滤更新 二.实现 1.文件传递 2.Controller层 3.Service层 4.ServiceImple ...

  2. linux设置共享权限设置命令,Linux系列知识详解(三)--------- Linux链接命令和权限管理命令...

    Linux系列知识详解(三)--------- Linux链接命令和权限管理命令 一:链接命令ln 简介:什么是链接命令?链接命令分为软链接和硬链接.其中软链接产生的新文件是以路径的形式表示原文件,类 ...

  3. 企业办公oa系统、医药OA办公后台管理、会议管理、用户管理、物料管理、活动管理、日常拜访、Axure医药内部管理平台、web端后台管理系统原型、医药OA系统、权限管理、Axure原型、rp原型

    企业办公oa系统.医药OA办公后台管理.会议管理.用户管理.物料管理.活动管理.Axure医药内部管理平台.web端后台管理系统原型.医药OA系统.权限管理 Axure原型演示及下载地址:https: ...

  4. Linux 学习笔记3 权限管理 定时任务 网络配置 进程、软件包管理

    权限管理 linux组的介绍 在linux中的每个用户必须属于一个组,不能独立于组外.在linux中每个文件有所有者.所在组.其它组的概念. 1.所有者 2.所在组 3.其它组 4.改变用户所在的组 ...

  5. JAVAWEB开发之工作流详解(二)——Activiti核心API的使用(流程定义和流程实例的管理、流程变量、监听器...)以及与Spring的集成

    管理流程定义 设计流程定义文档 bpmn文件 设置方式可以直接使用插件图形化界面进行设置 为某任务节点指定任务执行者 保存后的BPMN文件可以使用XML编辑器打开 BPMN 2.0根节点是defini ...

  6. java开发底薪加绩效代码,基于jsp的员工绩效管理-JavaEE实现员工绩效管理 - java项目源码...

    基于jsp+servlet+pojo+mysql实现一个javaee/javaweb的员工绩效管理, 该项目可用各类java课程设计大作业中, 员工绩效管理的系统架构分为前后台两部分, 最终实现在线上 ...

  7. python开发企业管理平台_我的第一个python web开发框架(34)——后台管理系统权限设计...

    框架底层和接口终于改造完成了,小白再次找到老菜. 小白:老大,上次你对后台权限系统简单的讲了一下,我一点头绪都没有,现在有空完整的说一说吗? 老菜:说到权限系统,要讲明白真不容易,权限系统并不是越复杂 ...

  8. C#开发微信门户及应用(27)-公众号模板消息管理

    原文:C#开发微信门户及应用(27)-公众号模板消息管理 通过模板消息接口,公众号能向关注其账号的用户发送预设模板的消息.模板消息仅用于公众号向用户发送重要的服务通知,只能用于符合其要求的服务场景中, ...

  9. 企业微信开发实战(四、OA审批之企业假期管理配置、获取成员假期余额、修改成员假期余额)

    文章目录 7.获取企业假期管理配置 7.1概述 7.2代码实战 8.获取成员假期余额 8.1概述 8.2代码实战 8.3试错 9.修改成员假期余额 9.1概述 9.2代码实战 9.3试错 源码 赞赏 ...

最新文章

  1. PHP+socket+SMTP、POP3协议发送、接收邮件
  2. 商汤科技宣布C轮战略融资6亿美元 阿里领投苏宁跟投
  3. oracle错误12518,ORA-12518: 错误 客户端连接不上
  4. 传统的6d位姿估计fangfa1_你的厨房被水淹了!别担心,这只是3D深度估计做出的特效...
  5. 最像windows10的linux,Linuxfx:外观神似Win10的Linux操作系统
  6. vue报错vue-router.esm.js?8c4f:2062 Uncaught (in promise) Error: Avoided redundant navigation to curren
  7. html5 百度地图api文档,开发指南--百度地图JavaScript API大众版.doc
  8. 动态修改attr里的多个属性
  9. 数学建模算法与应用_《数学建模算法与应用》笔记【1】
  10. 装饰模式-包装request和response
  11. jstree禁用父节点点击_Jstree 使用CheckBox插件 选中父节点时被禁用的子节点也会选中问题...
  12. java常用的库_java有哪些常用的库
  13. 病毒软件诈骗帝国 恐惧心理刺激销售
  14. 【架构师实践课】单体和微服务怎么选?单体到微服务怎么转?
  15. 海归学子创新创业座谈会:龙凡教授向浙江省委书记车俊汇报 Conflux 研发进展...
  16. echarts 环形图 不同区域背景色自定义
  17. Python就业都有哪些岗位?
  18. MySQL:一主两从架构(读写分离)
  19. Python从入门到精通--课程目录
  20. Grafana 介绍和使用

热门文章

  1. 芯海钩沉 | 英特尔4004:我最贵,但也最慢
  2. 计算机桌面打开为缩小模式,电脑桌面屏幕缩小了怎么办
  3. 在macOS开发中使用Touch Bar
  4. 好客租房129-百度地图api3使用步骤
  5. CV中,传统视觉算法 vs 深度学习算法
  6. SS49E线性霍尔IC
  7. ng2-dragula 使用报错 Uncaught ReferenceError: global is not defined
  8. HTML+JavaScript为网页添一点小功能【尼尔机械纪元】
  9. shell cat命令
  10. js实现文件流下载文件