BOS物流管理系统-第八天

BOS物流管理系统-第八天-权限系统设计—Shiro

回顾:

两大块业务:基础设置模块,业务派单模块。

两天:权限控制。

拦截器大的范围的、纯登录级别(认证级别)大颗粒的权限控制的一种技术。

Shiro:多种权限方式控制,可以细粒度控制,可以配置的。

 

主要内容:

  1. 权限控制的方式(URL级别-粗粒度,方法级别-细粒度,页面级别-自定义标签、数据级别)
  2. 权限系统的数据表设计(有原则)
  3. Shiro的系统集成(Spring整合)
  4. Apache Shiro框架的运行原理
  5. 自定义Shiro Realm,实现认证功能(基于数据库)
  6. 自定义Shiro Realm,实现授权功能(代码中伪实现,不连接数据库)

 

学习目标:

  • 权限控制的方式(会选择不同的方式)
  • 表的设计
  • Shiro的认证功能(登录)
  • Shiro的授权功能(url和方法)

 

  1. 权限管理系统分析设计

    1. 权限控制的方式

 

从类别上分,有两大类:

  • 认证:你是谁?--识别用户身份。
  • 授权:你能做什么?--限制用户使用的功能。

 

从控制级别(模型)上分:

  • URL级别-粗粒度
  • 方法级别-细粒度
  • 页面级别-自定义标签
  • 数据级别-最细化的

 

  1. 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实现

 

  1. 方法级别的权限控制-细粒度

以前:spring的aop的切面编程。

场景:url是粗粒度的,比如保存一个员工url操作,里面可以有多个方法,先查询员工存在不存在,。。。。。。。。保存。。。。很多方法。可能某个方法,当前户没有操作权限。此时url就过滤就做不到了。

方案:aop面向切面的编程,在方法执行之前,进行权限判断,如果没有权限,抛出异常,终止方法的继续运行。

如何进行权限判断?

 

如果你方法权限控制要在action层控制,可以直接在数据库中配置aciton的名字和用户关系。

但,如果,你配置service配置,配置具体的方法名和用户关系。

 

技术分享

 

自定义注解 在需要权限控制方法上, 添加需要的权限信息

    代理 (Spring AOP ),在目标方法运行时 进行增强 ,通过反射技术获取目标方法上注解中权限 , 查询数据库获取当前登陆用户具有权限,进行比较

相比URL级别权限控制, 可以控制到服务器端执行的每个方法,一次请求中可以控制多次

 

 

  1. 页面(显示)级别的权限控制-自定义标签

场景:

Rose进来,不允许她保存派送员的操作。如何在页面限制她呢?

方案:将按钮"隐藏"起来,不显示。
<s:if>权限判断

    如果有权限

<input type="button" value="保存"/>

</s:if>

 

提示:代码是服务端代码,客户端根本就没有按钮元素。

 

技术分享

页面显示的权限控制,通常是通过 自定义标签来实现

 

  1. 数据级别的权限控制

场景:Rose不能查询一张表中的A类数据,而Jack也不能查询B类的数据。情况:不同的人对一张表的数据有不同的权限。

方案:在每条数据上增加一个字段,该字段记录了权限的值。数据和权限绑定。

代码,你在查询数据的时候,需要去权限和用户对应表中,通过当前登录用户的条件,查询出你的数据权限。然后再将数据权限作为一个条件,放到业务表中进行查询。从而限制了数据的访问。

技术分享

 

  1. 权限管理数据表的设计

  • 资源:用户要访问的目标,通常是服务中的程序或文件
  • 权限:用户具有访问某资源的能力
  • 角色:权限的集合,为了方便给用户授权。
  • 用户:访问系统的‘人‘。

 

 

技术分享

用户、权限、角色之间的关系。

 

思考:需要几张表:

表对象实体:

  • 用户(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级)

 

技术分享

 

  1. 数据库设计和实体类生成

    1. 数据库建模设计和数据表的生成

 

技术分享

技术分享

执行生成的脚本,生成数据库对象:

技术分享

 

t_auth_function:权限表

t_auth_role:角色表

t_auth_role_function:角色权限关系表

t_auth_user_role:用户角色关系表

t_user:用户表

 

扩展:

脚本执行时控制台的提示信息:

技术分享

 

 

  1. 反转生成实体类(hibernate3-maven-plugin插件)

5张表3个实体。

 

修改src/main/resources/reveng.xml

 

    <schema-selection match-table="T_AUTH_.*" match-schema="MYBOS"/>

 

<table name="T_AUTH_FUNCTION" schema="MYBOS"

        class="cn.itcast.bos.domain.auth.Function">

        <primary-key>

            <generator class="uuid"></generator>

        </primary-key>

    </table>

    <table name="T_AUTH_ROLE" schema="MYBOS"

        class="cn.itcast.bos.domain.auth.Role">

        <primary-key>

            <generator class="uuid"></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中。

技术分享

  1. Apache Shiro权限控制

 

  1. Shiro简介和环境搭建

在企业中一般使用什么技术方案进行权限控制呢?

一般两种方式:

  • 自定义一个权限框架

实现方式:URL级别 Filter实现,方法级别 自定义注解 Spring AOP 反射实现, 页面显示 自定义标签实现

  • 使用已有的权限控制系统

 

  • Spring Security 安全框架

技术分享

技术分享

    缺点:使用复杂、不够灵活(必须依赖于spring), Spring 官方项目 使用Apache Shiro 进行权限控制

 

  • Apache Shiro 比较新 ,很多企业了解到shiro 因为 spring side 项目

技术分享

 

什么是Apache Shiro ?

技术分享

技术分享

    Apache Shiro 可以不依赖任何技术使用, 可以直接和web整合,通常在企业中和Spring 结合使用。

 

官网:

http://shiro.apache.org/

 

技术分享

技术分享

 

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。

 

  1. Shiro基本原理分析

可参考:

技术分享

技术分享

 

Shiro的框架的体系结构:

技术分享

 

Shiro权限控制流程的原理:

技术分享

 

应用代码 ---- 调用Subject (shiro的Subject 就代表当前登陆用户) 控制权限 ---- Subject 在shiro框架内部 调用 Shiro SecurityManager 安全管理器 ----- 安全管理器调用 Realm (程序和安全数据连接器 )

技术分享

官方说法:

技术分享

技术分享

Subject要进行任何操作,都必须要调用安全管理器(对我们来说是自动的)。

而安全管理器会调用指定的Realms对象,来连接安全数据。

技术分享

Realms用来编写安全代码逻辑和访问安全数据,是连接程序和安全数据的桥梁。

 

小结:如何开发Shiro?

通过以上分析,程序员使用shiro只需要

1、 应用程序代码中调用Subject(用户)的API ;

2、 定义编写Realm连接安全数据(获取权限数据)

 

  1. URL级别的权限控制—Shiro与Spring整合

Url的级别控制,有两种方式:

认证和授权。

User_login.action---->:

  1. 登录:认证,可以拥有所有的action的访问权----shiro的配置+编码
  2. 授权:授权,更具体化了。谁可以访问哪个action。

 

  1. 配置

 

配置过滤器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 id="shiroSecurityFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">

        <!-- shiro 的核心安全接口 -->

        <property name="securityManager" ref="securityManager" />

        <!-- 要求登录时的链接 -->

        <property name="loginUrl" value="/login.jsp" />

        <!-- 登陆成功后要跳转的连接 -->

        <property name="successUrl" value="/index.jsp" />

        <!-- 未授权时要跳转的连接,权限不足的跳转路径 -->

        <property name="unauthorizedUrl" value="/unauthorized.jsp" />

        <!-- shiro 连接约束配置URL级别的权限控制),即URLfilter的关系,URL控制规则:路径=规则名 -->

        <property name="filterChainDefinitions">

            <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 id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

        <!-- 注入 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的认证。

 

关于通配符:

技术分享

技术分享

技术分享

技术分享

 

重新登录演示。

 

  1. 用户认证(登录)—自定义Realm

 

  • 传统登录逻辑:

    用户输入用户名和密码 ---- 传递数据库查询 ---- 返回user ---- 判断如果user不为null, 登录成功, 将user加入session ----- 如果 user为null ,调回登录页面

 

  • Shiro实现登录逻辑

用户输入用户名和密码 ---- 应用程序调用Subject的login方法 ---- Subject 调用SecurityManager的方法 ---- SecurityManager 调用Realm的认证方法 ---- 认证方法根据登录用户名查询密码 ,返回用户的密码 ---- SecurityManager 比较用户输入的密码和真实密码是否一致

 

技术分享

技术分享

 

  1. 编写Shiro的认证登录逻辑

UserAction的login登录方法:

//shiro:登录逻辑

        //获取认证对象(用户)包装对象

        Subject subject = SecurityUtils.getSubject();

        

        //获取一个认证的令牌:

        //直接获取页面的用户名和密码进行校验

        AuthenticationToken authenticationToken = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5( model.getPassword()));

        //认证过程...("自己认证")

        //用户登录

        try {

            // 如果成功,就不抛出异常,会自动将用户放入session的一个属性。

            subject.login(authenticationToken);

            // 成功,首页

            return SUCCESS;

        } catch (AuthenticationException e) {

            // 认证失败

            e.printStackTrace();

            // 登录页面

super.addActionError(this.getText("UserAction.loginfail"));

            return LOGIN;

        }

        

 

重启服务测试:

控制台异常:

技术分享

认证错误的异常。可以用来判断登录是否成功,进行页面调整逻辑。

 

技术分享

说明了没有realm。

 

 

  1. 编写Realm,给SecurityManager提供。

 

技术分享

JdbcRealm和jndiLdapRealm,直接连接jdbc或jndi或ldap。

相当于dao和reaml整合了,能直接读取数据库,逻辑代码都帮你实现好了。

 

我们的,dao:spring data jpa:

自定义realm:

 

 

技术分享

BosRealm:

/**

* 实现认证和授权功能

*自定义的realm,作用从数据库查询数据,并返回数据库认证的信息

 

* @author BoBo

*

*/

@Component("bosRealm")

public class BosRealm extends AuthorizingRealm{

    //注入用户Service

    @Autowired

    private UserService userService;

 

    @Override

    //授权方法:获取用户的权限信息

    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        System.out.println("授权中。。。");

        return null;

    }

 

    @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 null;//会自动抛出异常

        }else{

            //用户存在

            //参数1:用户对象,将来要放入session,数据库查询出来的用户

            //参数2:凭证(密码):密码校验:校验的动作交给shiro

            //参数3:当前使用的RealmSpring容器中的名字(bean的名字,自动在spring容器中寻找)

            SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(user, user.getPassword(), super.getName());

            return authenticationInfo;//密码校验失败,会自动抛出IncorrectCredentialsException

        }

        

    }

 

}

 

 

ApplicatonContext.xml:

<!-- service需要spring扫描 -->

    <context:component-scan base-package="cn.itcast.bos.service,cn.itcast.bos.web,cn.itcast.bos.auth" />

 

<!-- shiro安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">

        <!-- 注入 Realm连接安全数据-->

        <property name="realm" ref="bosRealm"></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 SUCCESS;

        } catch (UnknownAccountException e) {

            //e.printStackTrace();

            //提示用户名不存在

            super.addActionError(this.getText("UserAction.usernamenotfound "));

            // 登录页面

            return LOGIN;

        } catch (IncorrectCredentialsException e) {

            //e.printStackTrace();

            //提示密码不正确

            super.addActionError(this.getText("UserAction.passwordinvalid "));

            // 登录页面

            return LOGIN;

        } catch (AuthenticationException e) {

            // 认证失败

            e.printStackTrace();

            // 登录页面

super.addActionError(this.getText("UserAction.loginfail"));

            return LOGIN;

        }

 

        

 

国际化文件:Messages.properties

略。。。。

 

 

  1. 用户认证(退出)

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 NONE;

    }

 

提示:修改密码的逻辑也需要发生变化,得从shiro中获取当前登录用户了。

技术分享

 

 

  1. 用户授权(权限)—自定义Ream

 

测试:

技术分享

技术分享

 

 

  1. 准备测试数据

<property name="filterChainDefinitions">

            <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;

 

 

  1. 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的授权底层:就是两个集合,分别存放功能权限字符串和角色权限字符串,二者没有必然关系,各自对比各自的

        

        //====获取当前用户的名字

        //两种方式:

        //方式1:工具类来获取(首长-)

//        User user=(User)SecurityUtils.getSubject().getPrincipal();

        //方式2:通过参数获取首长(推荐)

        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 authorizationInfo;//将授权信息交给安全管理器接口。

//        return null;//当前用户没有授权的权限

    }

 

cn.itcast.bos.dao.auth.RoleDAO:

//角色的dao

public interface RoleDAO extends JpaRepository<Role, String>{

    /**

     * 根据用户编号查询角色列表

     * @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 interface FunctionDAO extends JpaRepository<Function, String> {

 

}

 

 

 

  1. Shrio的运行原理

 

底层

先有认证,再授权

技术分享

 

  1. 方法级别的权限控制

 

实现原理

  1. 定义注解(shiro已经提供了)
  2. Spring AOP动态代理配置(开启注解等功能,aop)
  3. 反射机制获取注解信息(shiro帮你做了)

 

我们要做的是什么?

  1. 配置开启shiro注解功能
  2. 在方法上加注解

 

开发步骤:

1.启用Shiro注解:需要 Shiro 的 Spring AOP 集成来扫描合适的注解类以及执行必要的安全逻辑。

ApplicationContext.xml

 

<!-- 开启权限控制的注解功能并且配置aop -->

    <!-- 后处理器:通过动态代理在某bean实例化的前增强。:自己去找权限注解 -->

    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 切面自动代理:相当于以前的AOP标签配置

    advisor:切面 advice:通知

    -->

    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

    depends-on="lifecycleBeanPostProcessor">

        

    </bean>

    

    <!-- Advisor切面配置:授权属性的切面 -->

    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">

        <!-- 注入安全管理器 -->

        <property name="securityManager" ref="securityManager"/>

    </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 class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"

    depends-on="lifecycleBeanPostProcessor" >

        <!-- 设置aop的代理使用CGLIB代理 -->

        <property name="proxyTargetClass" value="true"/>

    </bean>

 

【扩展—终极方案】

技术分享

 

 

错误信息2:

技术分享

技术分享

递归向上寻找泛型的类型。

//递归向上 查找

        Class actionClass =this.getClass();

        //向父类递归寻找泛型

        while(true){

            //得到带有泛型的类型,如BaseAction<Userinfo>

            Type type = actionClass.getGenericSuperclass();

            

            if(type instanceof ParameterizedType){

                //转换为参数化类型

                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 void setSubareaService(SubareaService subareaService) {

        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级别权限控制:页面自动跳转到:

方法级别权限控制(注解):直接抛出异常。

 

 

  1. 重点和作业

 

  1. 权限控制的几种方式 ? 实现原理?

URL 、 方法注解、 页面显示标签控制,数据级别

  1. 画出 权限控制数据表结构
  2. Shiro权限控制好处, 为什么要使用Apache Shiro ?

使用简单,灵活 数据可以采用自定义Realm 连接安全数据 (没有数据来源要求 , 来自文件、 数据库、网络 … )

     4、 Shiro 运行原理画图 整理 shiro 认证和授权,调用过程

应用代码 ---- Subject --- SecurityManager ----- Realm

    5.Url级别的权限控制:认证和授权(通过自定义realm提供权限数据)

    补充作业: JdbcRealm 实现认证和授权

     6、 方法级别的控制(注解5个(认证3,授权2))

      

 

代码部分 :

    Shiro 会配置,会修改 (不需要去记忆 )

  • Subject 代码调用
  • 自定义Realm
  • 修改 URL访问规则
  • 在目标方法使用shiro注解

 

 

Eclise工具校验的问题:

技术分享

 

 

 

文章来自:http://www.cnblogs.com/beyondcj/p/6271260.html
© 2021 jiaocheng.bubufx.com  联系我们
ICP备案:鲁ICP备09046678号-3