概述:
1、权限控制的两种方式:粗粒度基于URL级别权限控制、细粒度基于方法级别权限控制
2、基于Apache Shiro实现登录认证和权限控制,重点shiro权限控制流程、自定义Realm对象控制系统认证和授权
3、Apache Shiro实现细粒度方法级别权限控制
4、动态系统菜单显示功能
5、对认证和授权数据进行缓存优化;
权限控制方式_粗粒度URL级别权限控制
可以基于Filter实现
在数据库中存放用户、权限、访问URL对应关系,当前用户访问一个URL地址,查询数据库判断用户当前具有权限,是否包含这个URL,如果包含允许访问,如果不包含则权限不足
权限控制方式_细粒度方法级别权限控制
可以代理、自定义注解实现,访问目标对象方法,在方法上添加权限注解信息,对目标对象创建代理对象,访问真实对象先访问代理对象,在代理对象查询数据库判断是否具有注解上描述需要权限,具有权限允许访问,不具有权限,拦截访问,提示权限不足。
权限相关数据表分析和创建(能实现)
1、准备工作
1)用户(User):系统登录用户
2)权限(Permission):描述权限信息(粗粒度权限控制,在权限表描述访问资源URL信息)
3)角色(Role):方便用户进行授权,角色就是权限的集合
用户 *---* 角色 *---* 权限(5张表)
4)Menu菜单,为了方便进行动态菜单管理,为不同用户定制不同系统菜单
不同用户系统菜单,可以根据用户角色进行管理 角色*---*菜单(2张表)
- 具体操作
- 在bos_domain建立cn.itcast.bos.domain.system包
- 将实体类复制到包中,启动项目自动建表
-
对bos_domain进行maven install
ApacheShiro简介和项目导入
Apache Shiro(更轻量,使用更简单,并不完全依赖spring,可以独立使用)
Apache Shiro体系结构:
- Authentication认证:用户登录,身份识别 who are you?
- Authorization 授权:用户具有哪些权限、角色 what can you do?
- Cryptography安全数据加密
- Session Management 会话管理
- Web Integration web系统集成
- Integrations 集成其他应用,spring、缓存框架
项目中pom引入Shiro依赖:
ApacheShiro运行流程和权限控制方式分析
1、核心介绍
1)Application Code用户编写代码
2)Subject就是shiro管理的用户
3)SecurityManager安全管理器,就是shiro权限控制核心对象,在编程时,只需要操作Subject方法,底层调用SecurityManager方法,无需直接编程操作SecurityManager
4)Realm应用程序和安全数据之间连接器,应用程序进行权限控制读取安全数据(数据表、文件、网络…)通过Realm对象完成
2、Shiro执行流程
应用程序--->Subject--->SecurityManager--->Realm--->安全数据
3、Shiro进行权限控制的四种主要方式
1)在程序中通过Subject编程方式进行权限控制
2)配置Filter实现URL级别粗粒度权限控制
3)配置代理,基于注解实现细粒度权限控制
4)在页面中使用shiro自定义标签实现,页面显示权限控制
配置ShiroFilter实现URL级别权限控制
- 配置web.xml
<!-- shiro的Filter -->
<filter>
<!-- 去spring配置文件中寻找同名bean -->
<filter-name>shiroFilter</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>shiroFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
2、配置applicationContext-shiro.xml
applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- 扫描 @Server @Controller @Repository -->
<context:component-scan base-package="cn.itcast"/>
<!-- 加载properties文件 -->
<context:property-placeholder location="classpath:config.properties" />
<!-- 引入外部数据文件 -->
<import resource="applicationContext-dataSource.xml"/>
<!-- 引入WebService配置 -->
<import resource="applicationContext-webService.xml"/>
<!-- 引入quartz配置 -->
<import resource="applicationContext-quartz.xml"/>
<!-- 引入 mq配置 -->
<import resource="applicationContext-mq.xml"/>
<!-- 引入 elasticsearch 配置 -->
<import resource="applicationContext-elasticsearch.xml"/>
<!-- 引入 shiro权限控制配置 -->
<import resource="applicationContext-shiro.xml"/>
<!-- 引入cache 配置 -->
<import resource="applicationContext-cache.xml"/>
</beans>
applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:jpa="http://www.springframework.org/schema/data/jpa" xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<!-- 配置Shiro核心Filter -->
<bean id="shiroFilter"
class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
<!-- 安全管理器 -->
<property name="securityManager" ref="securityManager" />
<!-- 未认证,跳转到哪个页面 -->
<property name="loginUrl" value="/login.html" />
<!-- 登录页面页面 -->
<property name="successUrl" value="/index.html" />
<!-- 认证后,没有权限跳转页面 -->
<property name="unauthorizedUrl" value="/unauthorized.html" />
<!-- shiro URL控制过滤器规则 -->
<property name="filterChainDefinitions">
<value>
/login.html* = anon
/user_login.action* = anon
/validatecode.jsp* = anon
/css/** = anon
/js/** = anon
/images/** = anon
/services/** = anon
/pages/base/courier.html* = perms[courier:list]
/pages/base/area.html* = roles[base]
/** = authc
</value>
</property>
</bean>
<!-- 安全管理器 -->
<bean id="securityManager"
class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
<property name="realm" ref="bosRealm" />
<property name="cacheManager" ref="shiroCacheManager" />
</bean>
<!-- 配置Realm -->
<bean id="bosRealm" class="cn.itcast.bos.realm.BosRealm">
<!-- 缓存区的名字 就是 ehcache.xml 自定义 cache的name -->
<property name="authorizationCacheName" value="bos" />
</bean>
//生命周期处理器
<bean id="lifecycleBeanPostProcessor"
class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
<!-- 开启shiro注解模式 -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" >
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
</beans>
/login.html后面加上*是因为会有如下情况:
http://localhost:9001/bos_management/login.html;jsessionid=68A360BFE7413C7CEAE81E5069F1EE81
/**=authc放在最后
ehcache.xml(额外)
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../config/ehcache.xsd">
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</defaultCache>
<cache name="bos"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</cache>
<cache name="standard"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
maxElementsOnDisk="10000000"
diskExpiryThreadIntervalSeconds="120"
memoryStoreEvictionPolicy="LRU">
<persistence strategy="localTempSwap"/>
</cache>
</ehcache>
Shiro过滤器配置参考:
anon未认证可以访问
authc认证后可以访问
perms需要特定权限才能访问
roles需要特定角色才能访问
user需要特定用户才能访问
port需要特定端口才能访问(不常用)
rest根据指定HTTP请求才能访问(不常用)
自定义Realm实现用户登录功能
1.提供login登录方法:
public String login() {
// 用户名和密码 都保存在model中
// 基于shiro实现登录
Subject subject = SecurityUtils.getSubject();
// 用户名和密码信息
AuthenticationToken token = new UsernamePasswordToken(
model.getUsername(), model.getPassword());
try {
subject.login(token);
// 登录成功
// 将用户信息 保存到 Session
return SUCCESS;//跳转成功页面
} catch (AuthenticationException e) {
// 登录失败
e.printStackTrace();
return LOGIN;//重定向到登录页面
}
}
public String logout() {
// 基于shiro完成退出
Subject subject = SecurityUtils.getSubject();
subject.logout();
return SUCCESS;
}
Shiro执行流程:应用程序--->Subject--->SecurityManager--->Realm--->安全数据
2. 自定义Realm对象,实现认证方法(实际开发中,只需要继承AuthorizingRealm)
package cn.itcast.bos.realm;
import java.util.List;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import cn.itcast.bos.domain.system.Permission;
import cn.itcast.bos.domain.system.Role;
import cn.itcast.bos.domain.system.User;
import cn.itcast.bos.service.system.PermissionService;
import cn.itcast.bos.service.system.RoleService;
import cn.itcast.bos.service.system.UserService;
// 自定义Realm ,实现安全数据 连接
// @Service("bosRealm")
public class BosRealm extends AuthorizingRealm {
@Autowired
private UserService userService;
@Autowired
private RoleService roleService;
@Autowired
private PermissionService permissionService;
@Override
// 授权...
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
System.out.println("shiro 授权管理...");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 根据当前登录用户 查询对应角色和权限
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
// 调用业务层,查询角色
List<Role> roles = roleService.findByUser(user);
for (Role role : roles) {
authorizationInfo.addRole(role.getKeyword());
}
// 调用业务层,查询权限
List<Permission> permissions = permissionService.findByUser(user);
for (Permission permission : permissions) {
authorizationInfo.addStringPermission(permission.getKeyword());
}
return authorizationInfo;
}
@Override
// 认证...
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
System.out.println("shiro 认证管理... ");
// 转换token
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
// 根据用户名 查询 用户信息
User user = userService.findByUsername(usernamePasswordToken
.getUsername());
if (user == null) {
// 用户名不存在
// 参数一: 期望登录后,保存在Subject中信息
// 参数二: 如果返回为null 说明用户不存在,报用户名
// 参数三 :realm名称
return null;
} else {
// 用户名存在
// 当返回用户密码时,securityManager安全管理器,自动比较返回密码和用户输入密码是否一致
// 如果密码一致 登录成功, 如果密码不一致 报密码错误异常
return new SimpleAuthenticationInfo(user, user.getPassword(),
getName());
}
}
}
3.在shiroFilter配置中将user_login.action放行
6、注意问题
1)当用户名不存在时,抛出异常
2)当密码错误时,抛出异常
对页面访问添加授权控制(重要)
- 用户授权功能主要是解决用户能操作哪些功能的问题
2.参考
3.修改applicationContext-shiro.xml配置shiroFilter权限过滤程序
4.访问courier.html时,因为需要特别权限,调用自定义Realm的doGetAuthorizationInfo进行授权,如果没有权限,跳转
自定义Realm实现用户授权功能
1、在数据表中导入预定义角色、权限数据,导入t_user_role_permission.sql
1)t_permission权限数据
2)t_role角色数据
3)t_role_permission角色权限关系表
4)t_user用户表
5)t_user_role用户角色关系表
2、实现Realm的授权方法
@Override
// 授权...
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) {
System.out.println("shiro 授权管理...");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
// 根据当前登录用户 查询对应角色和权限
Subject subject = SecurityUtils.getSubject();
User user = (User) subject.getPrincipal();
// 调用业务层,查询角色
List<Role> roles = roleService.findByUser(user);
for (Role role : roles) {
authorizationInfo.addRole(role.getKeyword());
}
// 调用业务层,查询权限
List<Permission> permissions = permissionService.findByUser(user);
for (Permission permission : permissions) {
authorizationInfo.addStringPermission(permission.getKeyword());
}
return authorizationInfo;
}
配置注解实现业务层方法级别权限控制
细粒度(方法)权限控制原因:
自定义注解(加在方法上,在注解中描述需要权限信息),对目标业务对象创建代理对象,在代理方法中使用反射技术读取注解信息,获取需要权限,查询当前登录用户具有权限是否满足。
- 配置applicationContext-shiro.xml激活注解
<!-- 开启shiro注解模式 -->
<bean
class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
depends-on="lifecycleBeanPostProcessor" >
<property name="proxyTargetClass" value="true" />
</bean>
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
<property name="securityManager" ref="securityManager"/>
</bean>
- 配置快递员添加方法,业务层使用shiro注解
3、注意问题:
1)针对CourierServiceImpl对象进行代理,而不是针对接口进行代理
解决:
applicationContext-dataSource.xml
applicationContext-shiro.xml
2)使用方法注解进行权限控制,当权限不足时,代理对象抛出一个异常
21:53:00,549 ERROR DefaultDispatcherErrorHandler:42 - Exception occurred during processing request: Subject does not have permission [courier:add]
org.apache.shiro.authz.UnauthorizedException: Subject does not have permission [courier:add]
Caused by: org.apache.shiro.authz.AuthorizationException: Not authorized to invoke method: public void cn.itcast.bos.service.base.impl.CourierServiceImpl.save(cn.itcast.bos.domain.base.Courier)
at org.apache.shiro.authz.aop.AuthorizingAnnotationMethodInterceptor.assertAuthorized(AuthorizingAnnotationMethodInterceptor.java:90)
... 122 more
通过shiro自定义标签控制页面元素显示
这些标签用于JSP页面,控制一些元素是否可见
对这些按钮是否显示进行控制,没有权限的用户无法看到对应按钮的
- 在页面引入shiro标签库
通过shiro自带标签控制按钮是否显示
三、shiro几种权限控制方式小结
第一种:URL级别粗粒度权限控制(重点)
配置web.xml的shiroFilter拦截 /*
在spring的applicationContext*.xml配置文件中配置同名的bean,配置filterChainDefinitions拦截控制规则
xxx.html*=anon(未登录可以访问)
xxx.html*=authc(必须登录才能访问)
xxx.html*=perms[权限](需要特定权限才能访问 )
xxx.html*=roles[角色](需要特定角色才能访问)
第二种:方法级别细粒度权限控制
在spring的applicationContext*.xml配置spring aop对spring管理bean对象开启shiro注解支持
@RequiresPermissions(权限) 需要特定权限才能访问
@RequiresRoles(角色) 需要特定角色才能访问
@RequiresAuthentication 需要认证才能访问
第三种:通过shiro自定义标签,实现页面元素显示控制
<shiro:authenticated> 登录后才能访问
<shiro:hasPermission name=”abc”> 需要特定权限才能访问
<shior:hasRole name=”abc”> 需要特定角色才能访问
第四种:在程序中通过代码,判断用户是否具有指定权限(不太常用,有代码侵入)
补充:权限表达式“:”代表子权限
权限courier包含courier:add、courier:list、courier:*,如果用户具有父权限,操作所有子权限功能
比如:权限courier:list 包含courier:list:10