BOS物流管理系统-第八天-权限系统设计—Shiro
回顾:
两大块业务:基础设置模块,业务派单模块。
两天:权限控制。
拦截器大的范围的、纯登录级别(认证级别)大颗粒的权限控制的一种技术。
Shiro:多种权限方式控制,可以细粒度控制,可以配置的。
主要内容:
- 权限控制的方式(URL级别-粗粒度,方法级别-细粒度,页面级别-自定义标签、数据级别)
- 权限系统的数据表设计(有原则)
- Shiro的系统集成(Spring整合)
- Apache Shiro框架的运行原理
- 自定义Shiro Realm,实现认证功能(基于数据库)
- 自定义Shiro Realm,实现授权功能(代码中伪实现,不连接数据库)
学习目标:
- 权限控制的方式(会选择不同的方式)
- 表的设计
- Shiro的认证功能(登录)
- Shiro的授权功能(url和方法)
-
权限管理系统分析设计
权限控制的方式
从类别上分,有两大类:
- 认证:你是谁?--识别用户身份。
- 授权:你能做什么?--限制用户使用的功能。
从控制级别(模型)上分:
- URL级别-粗粒度
- 方法级别-细粒度
- 页面级别-自定义标签
- 数据级别-最细化的
URL级别的权限控制-粗粒度
(url:统一资源定位符)
场景:要访问一个链接》路径:
http://localhost/mavenbos25/user_login.action
http://localhost/mavenbos25/staff_save.action
问题:如何控制用户是否有权限访问该路径?
方案:
在web.xml中配置一个过滤器filter,在过滤器中,对请求的地址进行解析,字符串截取:
url.substring()…把上下文前面的路径都截取掉,剩下user_login.action。
过滤器代码:
以通过查询数据库,来判断,当前登录用户,是否可以访问user_login.action。
设计表:
用户表:
Id name
101 rose
权限用户对应关系表:
Id 权限名称 权限路径 用户id外键
1 用户登录 user_login.action 101
2 员工保存 staff_save.action 101
判断逻辑:拿当前session中的用户id,到数据库权限数据,判断,不为空,通过,有权限;
原理:
为什么说url级别控制是粗粒度的?
因为 url级别控制,每次请求过程中只控制一次 ,相比方法级别权限控制 是粗粒度的 !
URL级别权限控制,基于Filter实现
方法级别的权限控制-细粒度
以前:spring的aop的切面编程。
场景:url是粗粒度的,比如保存一个员工url操作,里面可以有多个方法,先查询员工存在不存在,。。。。。。。。保存。。。。很多方法。可能某个方法,当前户没有操作权限。此时url就过滤就做不到了。
方案:aop面向切面的编程,在方法执行之前,进行权限判断,如果没有权限,抛出异常,终止方法的继续运行。
如何进行权限判断?
如果你方法权限控制要在action层控制,可以直接在数据库中配置aciton的名字和用户关系。
但,如果,你配置service配置,配置具体的方法名和用户关系。
自定义注解 在需要权限控制方法上, 添加需要的权限信息
代理 (Spring AOP ),在目标方法运行时 进行增强 ,通过反射技术获取目标方法上注解中权限 , 查询数据库获取当前登陆用户具有权限,进行比较
相比URL级别权限控制, 可以控制到服务器端执行的每个方法,一次请求中可以控制多次
页面(显示)级别的权限控制-自定义标签
场景:
Rose进来,不允许她保存派送员的操作。如何在页面限制她呢?
方案:将按钮"隐藏"起来,不显示。
<s:if>权限判断
如果有权限
<input type="button" value="保存"/>
</s:if>
提示:代码是服务端代码,客户端根本就没有按钮元素。
页面显示的权限控制,通常是通过 自定义标签来实现
数据级别的权限控制
场景:Rose不能查询一张表中的A类数据,而Jack也不能查询B类的数据。情况:不同的人对一张表的数据有不同的权限。
方案:在每条数据上增加一个字段,该字段记录了权限的值。数据和权限绑定。
代码,你在查询数据的时候,需要去权限和用户对应表中,通过当前登录用户的条件,查询出你的数据权限。然后再将数据权限作为一个条件,放到业务表中进行查询。从而限制了数据的访问。
权限管理数据表的设计
- 资源:用户要访问的目标,通常是服务中的程序或文件
- 权限:用户具有访问某资源的能力
- 角色:权限的集合,为了方便给用户授权。
- 用户:访问系统的'人'。
用户、权限、角色之间的关系。
思考:需要几张表:
表对象实体:
- 用户(User)表:访问系统的用户,比如用户登录要用
- 权限(Function)表:系统某个功能允许访问而对应的权限
- 角色(Role)表:角色是权限的集合(权限组),方便用户授权。
如果没有角色,如何授权?
甲员工,客服人员,授权:记录通知单、快速录单、调度。就在权限表中一个一个设置。3条对应。
乙员工,客服人员,还需要设置3个权限。
解决:设置一个角色,角色中拥有这3个权限。再来新员工丙,只需要将这个一个角色给他就可以了。因为角色中拥有这三个权限。
为了简化权限设计,通常我们要求,用户要有权限,必须通过角色来配置,间接的获得权限。
表对象之间的关系:
- 用户和角色关系表:一个用户对应N个角色,一个角色可以授予N个用户—》多对多关系
- 角色和权限关系表:一个角色包含N个权限,一个权限可以属于N个角色—》多对多关系
完整的权限相关表:
URL级别权限控制包含:资源表、权限表、角色表、用户表,以及相关关系(都是多对多),共7张表。
方法级别的权限控制包含:权限、角色、用户,以及相关关系(都是多对多),共5张表。
但Apache Shiro框架支持的URL级别权限控制,是将资源和资源权限对应关系配置到了配置文件中,不需要表的支撑,只需要5张表了。因此,我们的表设计如下:
课前资料:(回去自己画一下)
角色和权限表都具有的共有字段:
- Id:编号
- Name:名称,中文,用于显示识别,如管理员
- Code:编码名称,英文,编程时使用,如admin
- Description:描述,用户描述该角色或权限的用途
权限表的设计:
(下面几个字段主要用来生成动态菜单)
- 路径:每一个url(struts2:xxx.action)都设置一个权限(路径的字段)。
- 是否生成菜单
xxx.action是否都是菜单?
不是,大部分的action,是功能性aciton,比如staff_save.action。
菜单的action:page_staff_.action
所以,是否生成菜单,来识别url是否要在菜单上展示。
因为下一次课程中的菜单要生成动态菜单。现在的菜单json写死。
到时候,我们直接在数据库查询路径作为菜单。查询的时候,加一个条件,是否生成菜单:1。,在页面展示。
- 优先级:排序,用来控制菜单顺序。Order by 优先级字段。
-
父权限编号:树形结构设计,该菜单在哪个父菜单下面。
第一级,父节点0,(如基础数据)
第二级:子节点:第一级别id(如取派员设置)
两级的菜单。(扩展出来N级)
-
数据库设计和实体类生成
数据库建模设计和数据表的生成
执行生成的脚本,生成数据库对象:
t_auth_function:权限表
t_auth_role:角色表
t_auth_role_function:角色权限关系表
t_auth_user_role:用户角色关系表
t_user:用户表
扩展:
脚本执行时控制台的提示信息:
反转生成实体类(hibernate3-maven-plugin插件)
5张表3个实体。
修改src/main/resources/reveng.xml
<schema-selection <table class="cn.itcast.bos.domain.auth.Function"> <primary-key> <generator </primary-key> </table> <table class="cn.itcast.bos.domain.auth.Role"> <primary-key> <generator </primary-key> </table> |
执行:
【补充】
如果是Oracle11g的数据库,反转可能会报错,
解决方案1:需要在hibernate.properties中配置Oracle方言。
hibernate.dialect=org.hibernate.dialect.Oracle10Dialect hibernate.connection.driver_class=oracle.jdbc.driver.OracleDriver hibernate.connection.url=jdbc:oracle:thin:@localhost:1521:xe hibernate.connection.username=mybos hibernate.connection.password=mybos |
如果还不行,则需修改反转配置文件reveng.xml的内容:
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE hibernate-reverse-engineering SYSTEM "http://hibernate.sourceforge.net/hibernate-reverse-engineering-3.0.dtd" > <hibernate-reverse-engineering> <table-filter match-schema="AMES" match-name="ATTACH"/> <table-filter match-schema="AMES" match-name="ATTACH_GROUP"/> ...... </hibernate-reverse-engineering> 注意:match-schema就是用户名,match-name就是表名,注意大小写的区分 |
查看生成的实体内容。
将三个对象都复制到工程中:
注意User覆盖前要将原来user中的东东复制到新的User中。
Apache Shiro权限控制
Shiro简介和环境搭建
在企业中一般使用什么技术方案进行权限控制呢?
一般两种方式:
- 自定义一个权限框架
实现方式:URL级别 Filter实现,方法级别 自定义注解 Spring AOP 反射实现, 页面显示 自定义标签实现
- 使用已有的权限控制系统
- Spring Security 安全框架
缺点:使用复杂、不够灵活(必须依赖于spring), Spring 官方项目 使用Apache Shiro 进行权限控制
- Apache Shiro 比较新 ,很多企业了解到shiro 因为 spring side 项目
什么是Apache Shiro ?
Apache Shiro 可以不依赖任何技术使用, 可以直接和web整合,通常在企业中和Spring 结合使用。
官网:
Authentication: 认证 --- 用户登录
Authorization : 授权 ---- 功能权限管理
区别:
皇宫后宫三千佳丽。
认证:
要得到佳丽,认证。登录到后宫(进入后宫),用户认证,皇帝输入用户名和密码进入后宫。随意找佳丽。
问题:如果有后宫令牌(可以登录进去),里面的佳丽没法限制。
授权:
不管有没有认证身份,不管谁进入到后宫,每个佳丽的身上都有一把锁,你必须有钥匙才能才行。
通过引入Maven坐标导入shiro:
<shiro.version>1.2.4</shiro.version> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-all</artifactId> <version>${shiro.version}</version> </dependency> |
官方建议:不推荐直接引入shiro-all,依赖比较多,原因怕有jar冲突。
官方推荐根据需要单独导入jar。
Shiro基本原理分析
可参考:
Shiro的框架的体系结构:
Shiro权限控制流程的原理:
应用代码 ---- 调用Subject (shiro的Subject 就代表当前登陆用户) 控制权限 ---- Subject 在shiro框架内部 调用 Shiro SecurityManager 安全管理器 ----- 安全管理器调用 Realm (程序和安全数据连接器 )
官方说法:
Subject要进行任何操作,都必须要调用安全管理器(对我们来说是自动的)。
而安全管理器会调用指定的Realms对象,来连接安全数据。
Realms用来编写安全代码逻辑和访问安全数据,是连接程序和安全数据的桥梁。
小结:如何开发Shiro?
通过以上分析,程序员使用shiro只需要
1、 应用程序代码中调用Subject(用户)的API ;
2、 定义编写Realm连接安全数据(获取权限数据)
URL级别的权限控制—Shiro与Spring整合
Url的级别控制,有两种方式:
认证和授权。
User_login.action---->:
- 登录:认证,可以拥有所有的action的访问权----shiro的配置+编码
- 授权:授权,更具体化了。谁可以访问哪个action。
配置
配置过滤器web.xml:
<!-- shiro权限过滤器 --> <filter> <!-- 这里的 filter-name 要和 spring 的 applicationContext-shiro.xml 里的 org.apache.shiro.spring.web.ShiroFilterFactoryBean 的 bean name 相同 --> <filter-name>shiroSecurityFilter</filter-name> <!-- spring的代理过滤器类:以前的过滤器 --> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroSecurityFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> |
注意:要放在struts的前端控制器之前配置!但放在openEntitymanage之后。
以前:
web的过滤器谁初始化的?
Servlet容器初始化的。
现在:
org.springframework.web.filter.DelegatingFilterProxy提供了让过滤器的具体对象等交给spring初始化,它本身并不是具体的干活的filter。
filter-name
必须和一会要配置spring bean的名字一致,才能自动找到。
这个Filter 是 spring提供 ,DelegationFilterProxy 是代理Filter
(会自动找 和<filter-name> 同名的 <bean> 对象 )
配置ApplicationContext.xml:(shiro权限控制过滤器+ shiro安全管理器):
<!-- shiro权限控制过滤器bean --> <bean <!-- shiro <property <!-- 要求登录时的链接 --> <property <!-- 登陆成功后要跳转的连接 --> <property <!-- 未授权时要跳转的连接,权限不足的跳转路径 --> <property <!-- shiro <property <value> /login.jsp = anon /validatecode.jsp = anon /js/** = anon /css/** = anon /images/** = anon /user_login.action* = anon /page_base_staff.action = anon /page_base_region.action = perms["user"] /page_base_subarea.action = roles["operator"] /** = authc </value> </property> </bean> <!-- shiro安全管理器 --> <bean <!-- 注入 Realm连接安全数据--> </bean> |
配置shiroFilter后,可以应用 10种过滤规则
配置shiroFilter 其实是一个过滤器链,含有10个Filter(的校验功能)。
详解见:
常用:
认证
- anon不用认证(登录)就能访问(单词注意大小写)
- authc: 需要认证(登录)才能使用,例如/admins/user/**=authc,没有参数
授权:
- perms:需要拥有某权限才能使用,如具体允许的权限:/page_base_region.action =perms["user"],如果要访问该action,当前登录用户必须拥有user名字的权限。
- roles:需要拥有某角色才能使用,如具体允许的角色:/page_base_subarea.action = roles["operator"]如果要访问该action,当前用户必须拥有operator权限。
认证和授权是两码事.
Anon可以不登陆访问;authc必须登录才能访问。
假如登录上了,没有配置具体权限的url,可以访问。
但如果配置了具体权限的url(门有锁),就必须遵循这个权限了。
测试:
小结:通过配置就可以URL级别的权限控制。
经过测试,发现无法达到首页。
原因是:你现在的认证是你自己的认证。
你要想让配置规则有效,认证能进去,必须走shiro的认证。
操作:去掉strut2的认证。
关于通配符:
重新登录演示。
用户认证(登录)—自定义Realm
- 传统登录逻辑:
用户输入用户名和密码 ---- 传递数据库查询 ---- 返回user ---- 判断如果user不为null, 登录成功, 将user加入session ----- 如果 user为null ,调回登录页面
- Shiro实现登录逻辑
用户输入用户名和密码 ---- 应用程序调用Subject的login方法 ---- Subject 调用SecurityManager的方法 ---- SecurityManager 调用Realm的认证方法 ---- 认证方法根据登录用户名查询密码 ,返回用户的密码 ---- SecurityManager 比较用户输入的密码和真实密码是否一致
- 编写Shiro的认证登录逻辑
UserAction的login登录方法:
//shiro:登录逻辑 //获取认证对象(用户)包装对象 Subject subject = SecurityUtils.getSubject(); //获取一个认证的令牌: //直接获取页面的用户名和密码进行校验 AuthenticationToken authenticationToken = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5( model.getPassword())); //认证过程...("自己认证") //用户登录 try { // 如果成功,就不抛出异常,会自动将用户放入session的一个属性。 subject.login(authenticationToken); // 成功,首页 return } catch (AuthenticationException e) { // 认证失败 e.printStackTrace(); // 登录页面 super.addActionError(this.getText("UserAction.loginfail")); return } |
重启服务测试:
控制台异常:
认证错误的异常。可以用来判断登录是否成功,进行页面调整逻辑。
说明了没有realm。
- 编写Realm,给SecurityManager提供。
JdbcRealm和jndiLdapRealm,直接连接jdbc或jndi或ldap。
相当于dao和reaml整合了,能直接读取数据库,逻辑代码都帮你实现好了。
我们的,dao:spring data jpa:
自定义realm:
BosRealm:
/** * 实现认证和授权功能 *自定义的realm,作用从数据库查询数据,并返回数据库认证的信息 * @author BoBo * */ @Component("bosRealm") public //注入用户Service @Autowired private UserService userService; @Override //授权方法:获取用户的权限信息 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("授权中。。。"); return } @Override //认证:回调,认证管理器会将认证令牌放到这里(action层的令牌AuthenticationToken) //发现如果返回null,抛出用户不存在的异常UnknownAccountException protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("开始认证了。。。。。"); //用户名密码令牌(action传过来) UsernamePasswordToken upToken = (UsernamePasswordToken)token; //调用业务层来查询(根据用户名来查询用户,无需密码) User user = userService.findUserByUsername(upToken.getUsername()); //判断用户是否存在 if(null==user){ //用户名不存在 return }else{ //用户存在 :用户对象,将来要放入session,数据库查询出来的用户 :凭证(密码):密码校验:校验的动作交给shiro //参数3:当前使用的Realm在Spring容器中的名字(bean的名字,自动在spring容器中寻找) SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), super.getName()); return } } } |
ApplicatonContext.xml:
<!-- service需要spring扫描 --> <context:component-scan <!-- shiro安全管理器 --> <bean <!-- 注入 Realm连接安全数据--> <property </bean> |
启动测试:
控制台:
用户不存在的异常:
密码错误的异常:
因此,action层可以根据异常的不同,来更加细化给用户的提示信息:
UserAction:
//shiro:登录逻辑 //获取认证对象(用户)包装对象 Subject subject = SecurityUtils.getSubject(); //获取一个认证的令牌: //直接获取页面的用户名和密码进行校验 AuthenticationToken authenticationToken = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5( model.getPassword())); //认证过程...("自己认证") try { // 如果成功,就不抛出异常,会自动将用户放入session的一个属性。 subject.login(authenticationToken); // 成功,首页 return } catch (UnknownAccountException e) { //e.printStackTrace(); //提示用户名不存在 super.addActionError(this.getText("UserAction.usernamenotfound ")); // 登录页面 return } catch (IncorrectCredentialsException e) { //e.printStackTrace(); //提示密码不正确 super.addActionError(this.getText("UserAction.passwordinvalid ")); // 登录页面 return } // 认证失败 e.printStackTrace(); // 登录页面 super.addActionError(this.getText("UserAction.loginfail")); return } |
国际化文件:Messages.properties
略。。。。 |
用户认证(退出)
UserAction:
//登出 @Action(value="user_logout",results={@Result(name="success",type="redirect",location="/login.jsp")}) public String loginout(){ //传统的退出:销毁session的对象 // ServletActionContext.getRequest().getSession().invalidate(); //shiro退出:调用自己的退出 Subject subject = SecurityUtils.getSubject(); subject.logout(); //跳转到登录页面 return } |
提示:修改密码的逻辑也需要发生变化,得从shiro中获取当前登录用户了。
用户授权(权限)—自定义Ream
测试:
准备测试数据
<property <value> /login.jsp = anon /validatecode.jsp = anon /js/** = anon /css/** = anon /images/** = anon /user_login.action = anon /page_base_staff.action = anon /page_base_region.action = perms["region"] /page_base_subarea.action = roles["weihu"] /page_qupai_noticebill_add.action = perms["noticebill"] /page_qupai_quickworkorder.action = roles["kefu"] /** = authc </value> </property> |
在t_auth_user、t_auth_role、t_auth_function、t_auth_role_function、t_auth_user_role 五张表插入一些测试数据:
直接导入课前资料中的脚本:
T_AUTH_FUNCTION:
T_AUTH_ROLE
T_AUTH_ROLE_FUNCTION
T_USER
新增的用户的密码都为1。
提示:密码可以通过自定义md5 函数生成
T_AUTH_USER_ROLE
select t.*, t.rowid from T_AUTH_FUNCTION t; select t.*, t.rowid from T_AUTH_ROLE t; select t.*, t.rowid from T_AUTH_ROLE_FUNCTION t; select t.*, t.rowid from T_AUTH_USER_ROLE t; select t.*, t.rowid from T_USER t; |
Realm的授权代码的实现
cn.itcast.bos.auth.realm.BosRealm:
//注入角色dao @Autowired private RoleDAO roleDAO; //注入功能的dao @Autowired private FunctionDAO functionDAO; @Override //授权方法:获取用户的权限信息 //授权:回调方法 //如果返回null,说明没有权限,shiro会自动跳到<property name="unauthorizedUrl" value="/unauthorized.jsp" /> //如果不返回null,根据配置/page_base_subarea.action = roles["weihu"],去自动匹配 //给授权提供数据的 protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { //每一次授权,都一定会调用这里的方法,来获取授权的信息 System.out.println("开始授权了。。。。。。。。。。。。。。。。。。。。。。。。。。。"); //给当前用户授权的权限(功能权限、角色) //强行给当前用户权限(写死) SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo(); //1)功能权限 // authorizationInfo.addStringPermission("user"); //2)角色权限 // authorizationInfo.addRole("operator"); //对于shiro的授权底层:就是两个集合,分别存放功能权限字符串和角色权限字符串,二者没有必然关系,各自对比各自的 //====获取当前用户的名字 //两种方式: :工具类来获取(首长-) // User user=(User)SecurityUtils.getSubject().getPrincipal(); :通过参数获取首长(推荐) User user=(User)principals.getPrimaryPrincipal(); //实际:需要根据当前用户的角色和功能权限来构建一个授权信息对象,交给安全管理器 //得从数据库查询当前用户的授权情况 //分析得知:在系统中有两类用户:超管-普通用户 if(user.getUsername().equals("admin")){ //超管:不管有没有配置角色或权限,它必须都有才行 //查询出所有的角色,给认证信息对象 List<Role> roleList = roleDAO.findAll(); for (Role role : roleList) { authorizationInfo.addRole(role.getCode()); } //查询出所有的功能权限,给认证对象 List<Function> functionList = functionDAO.findAll(); for (Function function : functionList) { authorizationInfo.addStringPermission(function.getCode()); } }else{ //普通用户:需要根据数据库数据来看有哪些角色和权限 //获取当前用户的拥有的角色 List<Role> roleList = roleDAO.findByUsers(user); for (Role role : roleList) { authorizationInfo.addRole(role.getCode()); //导航查询,获取某角色的拥有的功能权限 Set<Function> functionList = role.getFunctions(); for (Function function : functionList) { authorizationInfo.addStringPermission(function.getCode()); } } } return // return null;//当前用户没有授权的权限 } |
cn.itcast.bos.dao.auth.RoleDAO:
//角色的dao public /** * 根据用户编号查询角色列表 * @param userId * @return */ @Query("from Role r inner join fetch r.users u where u.id = ?") public List<Role> findByUserId(String userId); /** * 根据用户查询角色列表 * @param user * @return */ public List<Role> findByUsers(User user); } |
cn.itcast.bos.dao.auth. FunctionDAO
//功能dao public } |
Shrio的运行原理
底层
先有认证,再授权
方法级别的权限控制
实现原理
- 定义注解(shiro已经提供了)
- Spring AOP动态代理配置(开启注解等功能,aop)
- 反射机制获取注解信息(shiro帮你做了)
我们要做的是什么?
- 配置开启shiro注解功能
- 在方法上加注解
开发步骤:
1.启用Shiro注解:需要 Shiro 的 Spring AOP 集成来扫描合适的注解类以及执行必要的安全逻辑。
ApplicationContext.xml
<!-- 开启权限控制的注解功能并且配置aop --> <!-- 后处理器:通过动态代理在某bean实例化的前增强。:自己去找权限注解 --> <bean <!-- 切面自动代理:相当于以前的AOP标签配置 advisor:切面 advice:通知 --> <bean depends-on="lifecycleBeanPostProcessor"> </bean> <!-- Advisor切面配置:授权属性的切面 --> <bean <!-- 注入安全管理器 --> <property </bean> |
在需要权限控制的目标方法上面使用shiro的注解:
@RequiresAuthentication 需要用户登录 subject.isAuthenticated() 必须返回true @ RequiresUser subject.isAuthenticated() 返回true 或者subject.isRemembered() 返回true "Remember Me"服务: 认证机制 基于 session 被记忆机制 基于 cookie (subject.isAuthenticated() 返回 false ) 面试题: 认证和记忆的区别 @ RequiresGuest 与 @RequiresUser 相反,不能认证也不能被记忆。 @ RequiresRoles 需要角色 @RequiresPermissions 需要权限 |
可参考:
Action层的方法权限控制(在需要权限控制的方法上面加上述两个注解之一即可):
分区保存的action方法上 |
错误信息1:
原因分析:Spring的动态代理优先对接口进行代理,默认的接口Action中只有execute方法,没有listpage()方法。
解决方案:
配置ApplicationContext.xml,设置代理为cglib代理(对目标类代理)
<!-- 切面自动代理:相当于以前的AOP标签配置 --> <bean depends-on="lifecycleBeanPostProcessor" <!-- 设置aop的代理使用CGLIB代理 --> <property </bean> |
【扩展—终极方案】
错误信息2:
递归向上寻找泛型的类型。
//递归向上查找 Class //向父类递归寻找泛型 while(true){ //得到带有泛型的类型,如BaseAction<Userinfo> Type type = actionClass.getGenericSuperclass(); if(type //转换为参数化类型 ParameterizedType parameterizedType = (ParameterizedType) type; //获取泛型的第一个参数的类型类,如Userinfo Class<T> modelClass = (Class<T>) parameterizedType.getActualTypeArguments()[0]; //实例化模型对象 try { model=modelClass.newInstance(); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } break; } //寻找父类 actionClass=actionClass.getSuperclass(); } |
错误信息3:
原因分析:Service没有注入进来,注入的时候根据类型注入失败了。
因为基于SubareanAction生成的为SubareanAction$$$xxx,再初始化SubareanAction$$$xxx的时候,无法将父类的私有属性继承过来,因此,也无法根据属性的autowaired的注解来生成对应的setter方法。因此也就无法注入service。
解决方案1:使用public 的Setter方法上的注解直接注入Service。
SubareaAction:
//注入service private SubareaService subareaService; @Autowired public this.subareaService = subareaService; } |
解决方案2:
@Autowire还放到私有声明上,
在struts.xml中覆盖常量(开启自动装配策略):
值默认是false,struts2默认注入采用的是构造器注入(从spring中寻找的bean)
改成true,struts2会采用setter方法注入
扩展:如果要对Action使用AOP,除了上面的配置外,还要将代理的方式改为cglib代理:
<aop:aspectj-autoproxy proxy-target-class="true"/> |
错误信息4(正常了)
web.xml:
小结:
Shiro的Url级别权限控制和方法级别权限控制(注解)的无权限的处理结果不同:
Url级别权限控制:页面自动跳转到:
方法级别权限控制(注解):直接抛出异常。
重点和作业
- 权限控制的几种方式 ? 实现原理?
URL 、 方法注解、 页面显示标签控制,数据级别
- 画出 权限控制数据表结构
- Shiro权限控制好处, 为什么要使用Apache Shiro ?
使用简单,灵活 数据可以采用自定义Realm 连接安全数据 (没有数据来源要求 , 来自文件、 数据库、网络 … )
4、 Shiro 运行原理 :画图 整理 shiro 认证和授权,调用过程
应用代码 ---- Subject --- SecurityManager ----- Realm
5.Url级别的权限控制:认证和授权(通过自定义realm提供权限数据)
补充作业: JdbcRealm 实现认证和授权
6、 方法级别的控制(注解5个(认证3,授权2))
代码部分 :
Shiro 会配置,会修改 (不需要去记忆 )
- Subject 代码调用
- 自定义Realm
- 修改 URL访问规则
- 在目标方法使用shiro注解
Eclise工具校验的问题: