Shiro安全框架入门

Posted anyueemo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
                
                /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>
</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中与配置授权控制规则相关的内容

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

 



以上是关于Shiro安全框架入门的主要内容,如果未能解决你的问题,请参考以下文章

Shiro安全框架入门

Shiro安全框架入门篇

Shiro安全框架入门篇(登录验证实例详解与源码)

Shiro安全框架入门使用方法

shiro安全框架的入门

Shiro安全框架「快速入门」就这一篇