Shiro安全框架入门

完整学习网址:https://www.w3cschool.cn/shiro

一、Shiro简介

1、Shiro的概念

Apache Shiro是一个强大易用的Java安全框架,提供了认证、授权、加密和会话管理等功能。

认证(Authentication):用户身份识别,常被称为用户“登录”,判断用户是否登陆,如果未登陆,则拦截其请求。优点:可以实现单点登录,多个子系统登录一个其他子系统也自动登录。(如登录了淘宝,天猫也自动登录了)

授权(Authorization):访问控制。当用户登陆后,判断其身份是否有权限访问相应的资源,如果没有权限则拦截

密码加密(Cryptography):保护或隐藏数据防止被偷窃。将MD5进行二次封装,让其更加容易使用。注意MD5不可逆运算

会话管理(Session Management)

Shiro的三大核心组件

Subject:正与系统进行交互的人,或某一个第三方服务。所有Subject实例都被绑定到(且这是必须的)一个SecurityManager上。

SecurityManager:Shiro架构的心脏,典型的Facade模式。用来协调内部各安全组件,管理内部组件实例,并通过它来提供安全管理的各种服务。当Shiro与一个Subject进行交互时,实质上是幕后的SecurityManager处理所有繁重的Subject安全操作。

Realms:本质上是一个特定安全的DAO。当配置Shiro时,必须指定至少一个Realm用来进行身份验证和/或授权。Shiro提供了多种可用的Realms来获取安全相关的数据。如关系数据库(JDBC),INI及属性文件等。可以定义自己Realm实现来代表自定义的数据源。

Shiro 完整架构图

2、Shiro内置过滤器

Shiro的内置过滤器分为两组:

认证过滤器:anon(不认证也可以访问),authcBasic, authc(必须认证后才可访问)

授权过滤器:perms(指定资源需要哪些权限才可以访问),Roles, ssl, rest, port

过滤器名称

过滤器类

描述

anon

org.apache.shiro.web.filter.authc.AnonymousFilter

匿名过滤器

authc

org.apache.shiro.web.filter.authc.FormAuthenticationFilter

如果继续操作,需要做对应的表单验证否则不能通过

authcBasic

org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter

基本http验证过滤,如果不通过,跳转登录页

perms

org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter

权限过滤器

port

org.apache.shiro.web.filter.authz.PortFilter

端口过滤器,可以设置是否是指定端口如果不是跳转到登录页面

rest

org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter

http方法过滤器,可以指定如post不能进行访问等

roles

org.apache.shiro.web.filter.authz.RolesAuthorizationFilter

角色过滤器,判断当前用户是否指定角色

ssl

org.apache.shiro.web.filter.authz.SslFilter

请求需要通过ssl,如果不是跳转登录页

user

org.apache.shiro.web.filter.authc.UserFilter

如果访问一个已知用户,比如记住我功能,走这个过滤器


 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

二、基本使用

配合Demo说明基本使用方法

项目环境:Eclipse+SSH

1、ERP整合Shiro

1)添加依赖

在erp_parent父工程的pom.xml添加依赖

<properties>
    <shiro.ver>1.2.3</shiro.ver>
</properties>

<dependencies>
    <!-- shiro -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>${shiro.ver}</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-web</artifactId>
        <version>${shiro.ver}</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>${shiro.ver}</version>
    </dependency>
    
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-aspectj</artifactId>
        <version>${shiro.ver}</version>
    </dependency>
</dependencies>

2)配置web.xml, 添加过滤器代理DelegatingFilterProxy,要放在struts2的核心过滤器之前

    <!-- 配置shiro的过滤器代理DelegatingFilterProxy -->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    </filter>
    
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>*.action</url-pattern>
        <url-pattern>*.html</url-pattern>
        <url-pattern>*</url-pattern>
    </filter-mapping>

Spring提供的一个简便的过滤器处理方案,它将具体的操作交给内部的Filter对象delegate去处理,而这个delegate对象通过Spring的IOC容器获取,这里采用的是Spring的FactorBean的方式获取这个对象。虽然配置了这一个filter,但是它并没做任何实际的工作,而是把这个工作交由Spring容器中一个bean的id为shiroFilter的类,即ShiroFilterFactoryBean。

3)添加shiro核心控制器的spring配置文件applicationContext_shiro.xml在erp_web的资源目录下(中间省略了部分业务相关的配置)

注意过滤链的顺序,匿名-->授权-->认证

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd
        "> 
         
    <!-- shiro的过滤工厂,相当默认的加载了9个过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 安全管理器,shiro核心组件(大脑) Facade模式 -->
        <property name="securityManager" ref="securityManager" />
        <property name="filters">
            <map>
                <entry key="perms" value-ref="erpAuthorizationFilter"></entry>
            </map>
        </property>
        <!-- 认证相关配置:用户如果没有登陆,当他在访问资源的时候,就会自动跳转到登陆的页面 -->
        <property name="loginUrl" value="/login.html"></property>
        <!-- 授权相关配置:当用户没有访问某项资源权限的时候,跳转到该页面 -->
        <property name="unauthorizedUrl" value="/error.html"></property>
        <!-- 过滤链的定义:定义URL访问的时候对应的认证或授权时处理的过滤器 -->
        <property name="filterChainDefinitions">
            <value>
                /error.html = anon
                /login_*.action = anon
                /login_* = anon
                
          /*_list=perms[]
           /emp_*=perms["用户角色设置","重置密码"] /goodstype.html=perms["商品类型"] /goodstype_*=perms["商品类型"] /goods.html=perms["商品"] /goods_*=perms["商品"] /orders.html=perms["采购订单查询","采购订单申请","采购订单审核","采购订单确认","采购订单入库","我的采购订单","销售订单查询","销售订单录入","销售订单出库"] /orders_*=perms["采购订单查询","采购订单申请","采购订单审核","采购订单确认","采购订单入库","我的采购订单","销售订单查询","销售订单录入","销售订单出库"] /report_*.html=perms["销售统计报表","销售趋势报表"] /report_*=perms["销售统计报表","销售趋势报表"] /*.html = authc /*.action = authc /* = authc
</value> </property> </bean> <!-- 安全管理器 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="erpRealm"></property> </bean> <!-- 自定义的realm --> <bean id="erpRealm" class="cn.itcast.erp.realm.ErpRealm"> <property name="empBiz" ref="empBiz"></property> </bean> <!-- 自定义的过滤器 --> <bean id="erpAuthorizationFilter" class="cn.itcast.erp.filter.ErpAuthorizationFilter"></bean>

    <!-- 启动shiro注解 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor" >
      <!-- 默认使用JDK代理 ,如被代理类没有实现接口,必须使用下列配置开启 cglib代理 -->
      <property name="proxyTargetClass" value="true" />
    </bean>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
      <property name="securityManager" ref="securityManager" />
    </bean>

    <!-- Shiro生命周期处理器对安全管理器 增强代码,spring 后处理器 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"></bean>

</beans>

4)创建error.html

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>提示信息</title>
</head>
<body>
尊敬的用户,您没有访问权限!
</body>
</html>

2、认证功能

1)LoginAction的checkUser方法中使用Subject自带的login方法

说明:UsernamePasswordToken是AuthenticationToken的子接口的实现类,它是对用户名和密码的封装。

Subject是对当前用户执行操作的封装,此处用它来执行登陆的操作。如果用户名和密码错误,login方法会抛出AuthenticationException异常

public void checkUser() {
    try {
        //1. 创建令牌,身份证明
        UsernamePasswordToken upt = new UsernamePasswordToken(username,pwd);
        //2. 获取主题 subject: 封装当前用户的一些操作
        Subject subject = SecurityUtils.getSubject();
        //3. 执行login
        subject.login(upt);
    } catch (Exception e) {
        e.printStackTrace();
    }
 }

2)自定义Realm

Realm的概念:Realm是Shiro与应用安全数据间的“桥梁”或者“连接器”。实质上是一个安全相关的DAO,它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。可以配置多个Realm。

作业流程:使用subject.login方法后,并不会调用登陆的业务层进行登陆的验证查询,即不会从数据库查找登陆的用户名和密码是否正确,而是将这项工作交给shiro去完成。

shiro通过Realm找到我们提供的登陆验证业务,验证登陆的用户名和密码是否正确。因此真正实现登陆验证的是Realm,而shiro只是去调Realm。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。 

a)在erp_web子工程下创建包cn.itcast.erp.realm

b)创建ErpRealm类继承自AuthorizingRealm 

public class ErpRealm extends AuthorizingRealm {
    
    private IEmpBiz empBiz;
    
    public void setEmpBiz(IEmpBiz empBiz) {
        this.empBiz = empBiz;
    }
    
    /**
     * 认证
     * @return null:认证失败, AuthenticationInfo实现类,认证成功
     */
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //通过令牌得到用户名和密码
        UsernamePasswordToken upt = (UsernamePasswordToken) token;
        //得到密码
        String pwd = new String(upt.getPassword());
        //调用登录查询
        Emp emp = empBiz.findByUsernameAndPwd(upt.getUsername(), pwd);
        if(null != emp) {
            //构造参数1: 主角=登陆用户
            //参数2:授权码:密码
            //参数3:realm的名称
            SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(emp,pwd,getName());
            return info;
        }
        return null;
    }

}
empBiz.findByUsernameAndPwd方法
/**
* 用户登陆
* @param username
* @param pwd
* @return
*/
public Emp findByUsernameAndPwd(String username, String pwd) {
  //查询前先加密
  pwd = encrypt(pwd, username);
  return empDao.findByUsernameAndPwd(username, pwd);
}

empDao.findByUsernameAndPwd方法

public Emp findByUsernameAndPwd(String username, String pwd){
  String hql = "from Emp where username=? and pwd=?";        
  List<Emp> list = (List<Emp>) this.getHibernateTemplate().find(hql, username, pwd);
  //能够匹配上,则返回第一个元素
  if(list.size() > 0){
    return list.get(0);
  }
  //如果登陆名或密码不正确
  return null;
}

c)applicationContext_shiro.xml中与自定义ErpRealm相关的配置

<!-- 安全管理器 -->
<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
  <property name="realm" ref="erpRealm"></property>
</bean>
    
<!-- 自定义的realm -->
<bean id="erpRealm" class="cn.itcast.erp.realm.ErpRealm">
  <property name="empBiz" ref="empBiz"></property>
</bean>

注意:登录的action要配置为anon,不然由于配置了/*=authc,会强行跳转到登陆页面。

/login_*.action = anon
/login_* = anon

d)LoginAction中的loginOut方法也可以通过subject的getPrincipal方法提取主题对象。

Shiro提供了会话管理机制,实际上自定义的realm认证方法返回值对象中的主角对象就是登陆的用户,可以通过subject的getPrincipal方法将其提取出来。

/**
* 退出登陆
*/
public void loginOut() {
    //ActionContext.getContext().getSession().remove("loginUser");
    SecurityUtils.getSubject().logout();
}

3、授权功能(粗颗粒)

授权就是通过设置规则,指定哪些URL需要哪些权限才可以访问。

在ErpRealm中新增授权方法作用:告诉shiro当前用户有什么权限

在applicationContext_shiro.xml增加配置信息作用:告诉shiro什么资源有什么权限才可以访问

1)授权方法与配置

a) 在ErpRealm中新增授权方法

/**
* 授权
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    //获取当前用户
    Emp emp = (Emp) principals.getPrimaryPrincipal();
    //获取当前登陆用户的菜单权限
    List<Menu> menuList = empBiz.getMenusByEmpuuid(emp.getUuid());
    //加入授权
    for(Menu m : menuList) {
    //这里使用menuname来做授权里的key值,那么在配置授权访问的url=perms[菜单名称]
    info.addStringPermission(m.getMenuname());
    }
    return info;
}
empBiz.getMenusByEmpuuid方法
/**
* 根据员工编号获取菜单
* @param uuid
*/
public List<Menu> getMenusByEmpuuid(Long uuid) {
  return empDao.getMenusByEmpuuid(uuid);
}
empDao.getMenusByEmpuuid方法
public List<Menu> getMenusByEmpuuid(Long uuid) {
  String hql = "select m from Emp e join e.roles r join r.menus m where e.uuid=?";
  return (List<Menu>) this.getHibernateTemplate().find(hql, uuid);
}

b) applicationContext_shiro.xml中与配置授权控制规则相关的内容

 

注意:通用方法需要单独提取出来配置

/*_list=perms[]

/*_list=perms[]

/goodstype.html=perms["商品类型"] /goodstype_*=perms["商品类型"] /goods.html=perms["商品"] /goods_*=perms["商品"] /store.html=perms["仓库"] /store_*=perms["仓库"] /orders.html=perms["采购订单查询","采购订单申请","采购订单审核","采购订单确认","采购订单入库","我的采购订单","销售订单查询","销售订单录入","销售订单出库"] /orders_*=perms["采购订单查询","采购订单申请","采购订单审核","采购订单确认","采购订单入库","我的采购订单","销售订单查询","销售订单录入","销售订单出库"] /report_*.html=perms["销售统计报表","销售趋势报表"] /report_*=perms["销售统计报表","销售趋势报表"]

2)自定义授权过滤器

当一个URL有多个权限需要访问的时候,如果按下面的方法来配置,系统默认使用的是and关系,即同时具备这两个权限才可以访问此URL

/orders.html=perms[“采购订单查询”,“采购订单审核”]

如果想要转换成or关系,即只要有具备一种就可以访问此URL,就需要自定义授权过滤器。

a)在erp_web子工程下创建包cn.itcast.erp.filter

b)创建自定义过滤器,继承自AuthorizationFilter

/**
 * 自定义授权过滤器
 */
public class ErpAuthorizationFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        //获取主题
        Subject subject = getSubject(request,response);
        //orders.html=perms["采购订单的查询","采购订单的审核","采购订单的确认","采购订单的入库"]
        //mappedValue="采购订单的查询","采购订单的审核","采购订单的确认","采购订单的入库"
        String[] perms = (String[]) mappedValue;
        
        boolean isPermitted = true;
        if(null == perms || perms.length == 0){
            return isPermitted;
        }
        if(null != perms || perms.length > 0){
            for(String perm : perms) {
                //只要有一个权限,就返回true
                if(subject.isPermitted(perm)) {
                    return true;
                }
            }
        }
        return false;
    }

}

c)applicationContext_shiro.xml中配置过滤器相关内容

<property name="filters">
  <map>
    <entry key="perms" value-ref="erpAuthorizationFilter"></entry>
  </map>
</property>

<!-- 自定义的过滤器 -->
<bean id="erpAuthorizationFilter" class="cn.itcast.erp.filter.ErpAuthorizationFilter"></bean>

4、授权功能(细颗粒)

对URL的访问权限控制,称之为粗颗粒的访问控制。

shiro的细颗粒授权控制包括:方法级别 与代码级别

1)方法级别

对某个方法加访问控制,用户必须拥有某项权限才可以访问该方法,没有权限则抛出异常,无法访问。一般在业务层添加注解,方便Action层进行捕获。

a)开启注解,applicationContext_shiro.xml中相关配置

<!-- 启动shiro注解 -->
    <bean
        class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
            depends-on="lifecycleBeanPostProcessor" >
        <!-- 默认使用JDK代理 ,如被代理类没有实现接口,必须使用下列配置开启 cglib代理  -->
        <property name="proxyTargetClass" value="true" />
    </bean>    
    <bean
        class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager" />
    </bean>

b)修改OrdersBiz,在其方法添加注解

/**
* 审核
* @param uuid 订单编号
* @param empUuid 审核员
*/
@RequiresPermissions("采购订单审核")
public void doCheck(Long uuid,Long empUuid) {
  ...    
}

/**
* 确认
* @param uuid 订单编号
* @param empUuid 采购员
*/
@RequiresPermissions("采购订单确认")
public void doStart(Long uuid,Long empUuid){
  ...  
}

c)完善OrdersAction的doCheck方法,向前端输出报错信息。

        /**
     * 采购订单审核
     */
    public void doCheck() {
        //获取登录用户
        Emp loginUser = getLoginUser();
        //用户没有登陆,session已失效
        if(loginUser == null) {
            ajaxReturn(false, "亲!您还没有登陆");
            return;
        }
        try {
            ordersBiz.doCheck(getId(), loginUser.getUuid());
            ajaxReturn(true, "审核成功");
        } catch (UnauthorizedException u) {
            ajaxReturn(false, "权限不足");
        } catch (ErpException e) {
            ajaxReturn(false,e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
            ajaxReturn(false, "审核失败");
        }
    }    

2)代码级别控制

代码级别控制:指的是在代码中加入权限控制,可以把控制粒度放在更细的层面上,也就是代码级别访问控制。

比如采购订单申请和销售订单录入,都会调用OrdersBiz的add方法。这样只要用户具有其中一个权限,就可以执行另一个功能了。

修改OrdersBiz的add方法,加入权限控制的判断

    /**
     * 添加订单
     */
    public void add(Orders orders) {
        //获取主题
        Subject subject = SecurityUtils.getSubject();
        if(Orders.TYPE_IN.equals(orders.getType())) {
            //代码级别的权限控制
            //判断当前登陆的用户是否有 采购订单申请 的权限
            if(subject.isPermitted("采购订单申请")) {
                throw new ErpException("权限不足");
            }
        }else if(Orders.TYPE_OUT.equals(orders.getType())){
       //判断当前登录的用户是否有 销售订单录入 的权限 
if(!subject.isPermitted("销售订单录入")){ throw new ErpException("权限不足"); } }else{ throw new ErpException("非法参数"); } ... }
posted @ 2018-07-10 19:01  anyueemo  阅读(563)  评论(0)    收藏  举报