数据库
先准备数据库啦。
1 DROP DATABASE IF EXISTS shiro; 2 CREATE DATABASE shiro DEFAULT CHARACTER SET utf8; 3 USE shiro; 4 5 drop table if exists user; 6 drop table if exists role; 7 drop table if exists permission; 8 drop table if exists user_role; 9 drop table if exists role_permission; 10 11 create table user ( 12 id bigint auto_increment, 13 name varchar(100), 14 password varchar(100), 15 salt varchar(100), 16 constraint pk_users primary key(id) 17 ) charset=utf8 ENGINE=InnoDB; 18 19 create table role ( 20 id bigint auto_increment, 21 name varchar(100), 22 desc_ varchar(100), 23 constraint pk_roles primary key(id) 24 ) charset=utf8 ENGINE=InnoDB; 25 26 create table permission ( 27 id bigint auto_increment, 28 name varchar(100), 29 desc_ varchar(100), 30 url varchar(100), 31 constraint pk_permissions primary key(id) 32 ) charset=utf8 ENGINE=InnoDB; 33 34 create table user_role ( 35 id bigint auto_increment, 36 uid bigint, 37 rid bigint, 38 constraint pk_users_roles primary key(id) 39 ) charset=utf8 ENGINE=InnoDB; 40 41 create table role_permission ( 42 id bigint auto_increment, 43 rid bigint, 44 pid bigint, 45 constraint pk_roles_permissions primary key(id) 46 ) charset=utf8 ENGINE=InnoDB; 47 48 INSERT INTO `permission` VALUES (1,'addProduct','增加产品','/addProduct'); 49 INSERT INTO `permission` VALUES (2,'deleteProduct','删除产品','/deleteProduct'); 50 INSERT INTO `permission` VALUES (3,'editeProduct','编辑产品','/editeProduct'); 51 INSERT INTO `permission` VALUES (4,'updateProduct','修改产品','/updateProduct'); 52 INSERT INTO `permission` VALUES (5,'listProduct','查看产品','/listProduct'); 53 INSERT INTO `permission` VALUES (6,'addOrder','增加订单','/addOrder'); 54 INSERT INTO `permission` VALUES (7,'deleteOrder','删除订单','/deleteOrder'); 55 INSERT INTO `permission` VALUES (8,'editeOrder','编辑订单','/editeOrder'); 56 INSERT INTO `permission` VALUES (9,'updateOrder','修改订单','/updateOrder'); 57 INSERT INTO `permission` VALUES (10,'listOrder','查看订单','/listOrder'); 58 INSERT INTO `role` VALUES (1,'admin','超级管理员'); 59 INSERT INTO `role` VALUES (2,'productManager','产品管理员'); 60 INSERT INTO `role` VALUES (3,'orderManager','订单管理员'); 61 INSERT INTO `role_permission` VALUES (1,1,1); 62 INSERT INTO `role_permission` VALUES (2,1,2); 63 INSERT INTO `role_permission` VALUES (3,1,3); 64 INSERT INTO `role_permission` VALUES (4,1,4); 65 INSERT INTO `role_permission` VALUES (5,1,5); 66 INSERT INTO `role_permission` VALUES (6,1,6); 67 INSERT INTO `role_permission` VALUES (7,1,7); 68 INSERT INTO `role_permission` VALUES (8,1,8); 69 INSERT INTO `role_permission` VALUES (9,1,9); 70 INSERT INTO `role_permission` VALUES (10,1,10); 71 INSERT INTO `role_permission` VALUES (11,2,1); 72 INSERT INTO `role_permission` VALUES (12,2,2); 73 INSERT INTO `role_permission` VALUES (13,2,3); 74 INSERT INTO `role_permission` VALUES (14,2,4); 75 INSERT INTO `role_permission` VALUES (15,2,5); 76 INSERT INTO `role_permission` VALUES (50,3,10); 77 INSERT INTO `role_permission` VALUES (51,3,9); 78 INSERT INTO `role_permission` VALUES (52,3,8); 79 INSERT INTO `role_permission` VALUES (53,3,7); 80 INSERT INTO `role_permission` VALUES (54,3,6); 81 INSERT INTO `role_permission` VALUES (55,3,1); 82 INSERT INTO `role_permission` VALUES (56,5,11); 83 INSERT INTO `user` VALUES (1,'zhang3','a7d59dfc5332749cb801f86a24f5f590','e5ykFiNwShfCXvBRPr3wXg=='); 84 INSERT INTO `user` VALUES (2,'li4','43e28304197b9216e45ab1ce8dac831b','jPz19y7arvYIGhuUjsb6sQ=='); 85 INSERT INTO `user_role` VALUES (43,2,2); 86 INSERT INTO `user_role` VALUES (45,1,1);
基于前面的知识点继续进行
下面只展示基于前面的代码做修改
PageController.java
首先是PageController.java 里原本通过注解方式的@RequiresPermissions和@RequiresRoles 注释掉了
1 package com.how2java.controller; 2 3 import org.springframework.stereotype.Controller; 4 import org.springframework.web.bind.annotation.RequestMapping; 5 import org.springframework.web.bind.annotation.RequestMethod; 6 7 //专门用于显示页面的控制器 8 @Controller 9 @RequestMapping("") 10 public class PageController { 11 12 @RequestMapping("index") 13 public String index(){ 14 return "index"; 15 } 16 17 // @RequiresPermissions("deleteOrder") 18 @RequestMapping("deleteOrder") 19 public String deleteOrder(){ 20 return "deleteOrder"; 21 } 22 // @RequiresRoles("productManager") 23 @RequestMapping("deleteProduct") 24 public String deleteProduct(){ 25 return "deleteProduct"; 26 } 27 @RequestMapping("listProduct") 28 public String listProduct(){ 29 return "listProduct"; 30 } 31 32 @RequestMapping(value="/login",method=RequestMethod.GET) 33 public String login(){ 34 return "login"; 35 } 36 @RequestMapping("unauthorized") 37 public String noPerms(){ 38 return "unauthorized"; 39 } 40 41 }
PermissionService.java
增加了两个方法 needInterceptor,listPermissionURLs
1 package com.how2java.service; 2 3 import java.util.List; 4 import java.util.Set; 5 6 import com.how2java.pojo.Permission; 7 import com.how2java.pojo.Role; 8 9 public interface PermissionService { 10 public Set<String> listPermissions(String userName); 11 12 public List<Permission> list(); 13 14 public void add(Permission permission); 15 16 public void delete(Long id); 17 18 public Permission get(Long id); 19 20 public void update(Permission permission); 21 22 public List<Permission> list(Role role); 23 24 public boolean needInterceptor(String requestURI); 25 26 public Set<String> listPermissionURLs(String userName); 27 28 }
PermissionServiceImpl.java
为两个方法 needInterceptor,listPermissionURLs 增加了实现
needInterceptor 表示是否要进行拦截,判断依据是如果访问的某个url,在权限系统里存在,就要进行拦截。 如果不存在,就放行了。
这一种策略,也可以切换成另一个,即,访问的地址如果不存在于权限系统中,就提示没有拦截。 这两种做法没有对错之分,取决于业务上希望如何制定权限策略。
listPermissionURLs(User user) 用来获取某个用户所拥有的权限地址集合
1 package com.how2java.service.impl; 2 3 import java.util.ArrayList; 4 import java.util.HashSet; 5 import java.util.List; 6 import java.util.Set; 7 8 import org.springframework.beans.factory.annotation.Autowired; 9 import org.springframework.stereotype.Service; 10 11 import com.how2java.mapper.PermissionMapper; 12 import com.how2java.mapper.RolePermissionMapper; 13 import com.how2java.pojo.Permission; 14 import com.how2java.pojo.PermissionExample; 15 import com.how2java.pojo.Role; 16 import com.how2java.pojo.RolePermission; 17 import com.how2java.pojo.RolePermissionExample; 18 import com.how2java.service.PermissionService; 19 import com.how2java.service.RoleService; 20 import com.how2java.service.UserService; 21 22 @Service 23 public class PermissionServiceImpl implements PermissionService { 24 25 @Autowired 26 PermissionMapper permissionMapper; 27 @Autowired 28 UserService userService; 29 @Autowired 30 RoleService roleService; 31 @Autowired 32 RolePermissionMapper rolePermissionMapper; 33 34 @Override 35 public Set<String> listPermissions(String userName) { 36 Set<String> result = new HashSet<>(); 37 List<Role> roles = roleService.listRoles(userName); 38 39 List<RolePermission> rolePermissions = new ArrayList<>(); 40 41 for (Role role : roles) { 42 RolePermissionExample example = new RolePermissionExample(); 43 example.createCriteria().andRidEqualTo(role.getId()); 44 List<RolePermission> rps = rolePermissionMapper.selectByExample(example); 45 rolePermissions.addAll(rps); 46 } 47 48 for (RolePermission rolePermission : rolePermissions) { 49 Permission p = permissionMapper.selectByPrimaryKey(rolePermission.getPid()); 50 result.add(p.getName()); 51 } 52 53 return result; 54 } 55 56 @Override 57 public void add(Permission u) { 58 permissionMapper.insert(u); 59 } 60 61 @Override 62 public void delete(Long id) { 63 permissionMapper.deleteByPrimaryKey(id); 64 } 65 66 @Override 67 public void update(Permission u) { 68 permissionMapper.updateByPrimaryKeySelective(u); 69 } 70 71 @Override 72 public Permission get(Long id) { 73 return permissionMapper.selectByPrimaryKey(id); 74 } 75 76 @Override 77 public List<Permission> list() { 78 PermissionExample example = new PermissionExample(); 79 example.setOrderByClause("id desc"); 80 return permissionMapper.selectByExample(example); 81 82 } 83 84 @Override 85 public List<Permission> list(Role role) { 86 List<Permission> result = new ArrayList<>(); 87 RolePermissionExample example = new RolePermissionExample(); 88 example.createCriteria().andRidEqualTo(role.getId()); 89 List<RolePermission> rps = rolePermissionMapper.selectByExample(example); 90 for (RolePermission rolePermission : rps) { 91 result.add(permissionMapper.selectByPrimaryKey(rolePermission.getPid())); 92 } 93 94 return result; 95 } 96 97 @Override 98 public boolean needInterceptor(String requestURI) { 99 List<Permission> ps = list(); 100 for (Permission p : ps) { 101 if (p.getUrl().equals(requestURI)) 102 return true; 103 } 104 return false; 105 } 106 107 @Override 108 public Set<String> listPermissionURLs(String userName) { 109 Set<String> result = new HashSet<>(); 110 List<Role> roles = roleService.listRoles(userName); 111 112 List<RolePermission> rolePermissions = new ArrayList<>(); 113 114 for (Role role : roles) { 115 RolePermissionExample example = new RolePermissionExample(); 116 example.createCriteria().andRidEqualTo(role.getId()); 117 List<RolePermission> rps = rolePermissionMapper.selectByExample(example); 118 rolePermissions.addAll(rps); 119 } 120 121 for (RolePermission rolePermission : rolePermissions) { 122 Permission p = permissionMapper.selectByPrimaryKey(rolePermission.getPid()); 123 result.add(p.getUrl()); 124 } 125 126 return result; 127 } 128 129 }
URLPathMatchingFilter.java
PathMatchingFilter 是shiro 内置过滤器 PathMatchingFilter 继承了这个它。
基本思路如下:
1. 如果没登录就跳转到登录
2. 如果当前访问路径没有在权限系统里维护,则允许访问
3. 当前用户所拥有的权限如何不包含当前的访问地址,则跳转到/unauthorized,否则就允许访问
1 package com.how2java.filter; 2 3 import java.util.Set; 4 5 import javax.servlet.ServletRequest; 6 import javax.servlet.ServletResponse; 7 8 import org.apache.shiro.SecurityUtils; 9 import org.apache.shiro.authz.UnauthorizedException; 10 import org.apache.shiro.subject.Subject; 11 import org.apache.shiro.web.filter.PathMatchingFilter; 12 import org.apache.shiro.web.util.WebUtils; 13 import org.springframework.beans.factory.annotation.Autowired; 14 15 import com.how2java.service.PermissionService; 16 17 public class URLPathMatchingFilter extends PathMatchingFilter { 18 @Autowired 19 PermissionService permissionService; 20 21 @Override 22 protected boolean onPreHandle(ServletRequest request, ServletResponse response, Object mappedValue) 23 throws Exception { 24 String requestURI = getPathWithinApplication(request); 25 26 System.out.println("requestURI:" + requestURI); 27 28 Subject subject = SecurityUtils.getSubject(); 29 // 如果没有登录,就跳转到登录页面 30 if (!subject.isAuthenticated()) { 31 WebUtils.issueRedirect(request, response, "/login"); 32 return false; 33 } 34 35 // 看看这个路径权限里有没有维护,如果没有维护,一律放行(也可以改为一律不放行) 36 boolean needInterceptor = permissionService.needInterceptor(requestURI); 37 if (!needInterceptor) { 38 return true; 39 } else { 40 boolean hasPermission = false; 41 String userName = subject.getPrincipal().toString(); 42 Set<String> permissionUrls = permissionService.listPermissionURLs(userName); 43 for (String url : permissionUrls) { 44 // 这就表示当前用户有这个权限 45 if (url.equals(requestURI)) { 46 hasPermission = true; 47 break; 48 } 49 } 50 51 if (hasPermission) 52 return true; 53 else { 54 UnauthorizedException ex = new UnauthorizedException("当前用户没有访问路径 " + requestURI + " 的权限"); 55 56 subject.getSession().setAttribute("ex", ex); 57 58 WebUtils.issueRedirect(request, response, "/unauthorized"); 59 return false; 60 } 61 62 } 63 64 } 65 }
applicationContext-shiro.xml
首先声明URLPathMatchingFilter 过滤器
<bean id="urlPathMatchingFilter" class="com.how2java.filter.URLPathMatchingFilter"/>
在shiro中使用这个过滤器
<entry key="url" value-ref="urlPathMatchingFilter" />
过滤规则是所有访问
/** = url
1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xmlns="http://www.springframework.org/schema/beans" xmlns:util="http://www.springframework.org/schema/util" 4 xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p" 5 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:mvc="http://www.springframework.org/schema/mvc" 6 xmlns:aop="http://www.springframework.org/schema/aop" 7 xsi:schemaLocation="http://www.springframework.org/schema/beans 8 http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/tx 9 http://www.springframework.org/schema/tx/spring-tx-4.0.xsd http://www.springframework.org/schema/context 10 http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/mvc 11 http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/aop 12 http://www.springframework.org/schema/aop/spring-aop-4.0.xsd http://www.springframework.org/schema/util 13 http://www.springframework.org/schema/util/spring-util.xsd"> 14 15 <!-- url过滤器 --> 16 <bean id="urlPathMatchingFilter" class="com.how2java.filter.URLPathMatchingFilter"/> 17 18 <!-- 配置shiro的过滤器工厂类,id- shiroFilter要和我们在web.xml中配置的过滤器一致 --> 19 <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> 20 <!-- 调用我们配置的权限管理器 --> 21 <property name="securityManager" ref="securityManager" /> 22 <!-- 配置我们的登录请求地址 --> 23 <property name="loginUrl" value="/login" /> 24 <!-- 如果您请求的资源不再您的权限范围,则跳转到/403请求地址 --> 25 <property name="unauthorizedUrl" value="/unauthorized" /> 26 <!-- 退出 --> 27 <property name="filters"> 28 <util:map> 29 <entry key="logout" value-ref="logoutFilter" /> 30 <entry key="url" value-ref="urlPathMatchingFilter" /> 31 </util:map> 32 </property> 33 <!-- 权限配置 --> 34 <property name="filterChainDefinitions"> 35 <value> 36 <!-- anon表示此地址不需要任何权限即可访问 --> 37 /login=anon 38 /index=anon 39 /static/**=anon 40 <!-- 只对业务功能进行权限管理,权限配置本身不需要没有做权限要求,这样做是为了不让初学者混淆 --> 41 /config/**=anon 42 /doLogout=logout 43 <!--所有的请求(除去配置的静态资源请求或请求地址为anon的请求)都要通过过滤器url --> 44 /** = url 45 </value> 46 </property> 47 </bean> 48 <!-- 退出过滤器 --> 49 <bean id="logoutFilter" class="org.apache.shiro.web.filter.authc.LogoutFilter"> 50 <property name="redirectUrl" value="/index" /> 51 </bean> 52 53 <!-- 会话ID生成器 --> 54 <bean id="sessionIdGenerator" 55 class="org.apache.shiro.session.mgt.eis.JavaUuidSessionIdGenerator" /> 56 <!-- 会话Cookie模板 关闭浏览器立即失效 --> 57 <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie"> 58 <constructor-arg value="sid" /> 59 <property name="httpOnly" value="true" /> 60 <property name="maxAge" value="-1" /> 61 </bean> 62 <!-- 会话DAO --> 63 <bean id="sessionDAO" 64 class="org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO"> 65 <property name="sessionIdGenerator" ref="sessionIdGenerator" /> 66 </bean> 67 <!-- 会话验证调度器,每30分钟执行一次验证 ,设定会话超时及保存 --> 68 <bean name="sessionValidationScheduler" 69 class="org.apache.shiro.session.mgt.ExecutorServiceSessionValidationScheduler"> 70 <property name="interval" value="1800000" /> 71 <property name="sessionManager" ref="sessionManager" /> 72 </bean> 73 <!-- 会话管理器 --> 74 <bean id="sessionManager" 75 class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> 76 <!-- 全局会话超时时间(单位毫秒),默认30分钟 --> 77 <property name="globalSessionTimeout" value="1800000" /> 78 <property name="deleteInvalidSessions" value="true" /> 79 <property name="sessionValidationSchedulerEnabled" value="true" /> 80 <property name="sessionValidationScheduler" ref="sessionValidationScheduler" /> 81 <property name="sessionDAO" ref="sessionDAO" /> 82 <property name="sessionIdCookieEnabled" value="true" /> 83 <property name="sessionIdCookie" ref="sessionIdCookie" /> 84 </bean> 85 86 <!-- 安全管理器 --> 87 <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> 88 <property name="realm" ref="databaseRealm" /> 89 <property name="sessionManager" ref="sessionManager" /> 90 </bean> 91 <!-- 相当于调用SecurityUtils.setSecurityManager(securityManager) --> 92 <bean 93 class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> 94 <property name="staticMethod" 95 value="org.apache.shiro.SecurityUtils.setSecurityManager" /> 96 <property name="arguments" ref="securityManager" /> 97 </bean> 98 99 <!-- 密码匹配器 --> 100 <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher"> 101 <property name="hashAlgorithmName" value="md5"/> 102 <property name="hashIterations" value="2"/> 103 <property name="storedCredentialsHexEncoded" value="true"/> 104 </bean> 105 106 <bean id="databaseRealm" class="com.how2java.realm.DatabaseRealm"> 107 <property name="credentialsMatcher" ref="credentialsMatcher"/> 108 </bean> 109 110 <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> 111 <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor" /> 112 </beans>
jsp 们
jsp前端页面这里就不贴了。先要的到下面下载。
关于角色
在本知识点中,权限通过url进行灵活配置了。 但是角色还没有和url对应起来。 为什么不把角色也对应起来呢?
从代码开发的角度讲是可以做的,无非就是在 role表上增加一个url 字段。 但是从权限管理本身的角度看,当一个url 既对应权限表的数据,又对应角色表的数据,反而容易产生混淆。
反倒是现在这种,url地址,仅仅和权限表关联起来,在逻辑上明晰简单,也更容易维护。 所以就放弃了角色表也进行url维护的做法了。
结束
大家可以跑起来试一下。
代码下载地址:https://gitee.com/fengyuduke/my_open_resources/blob/master/shiro-url.zip
另外:项目所需jar包下载地址 https://gitee.com/fengyuduke/my_open_resources/tree/jarPackage/