apache shiro学习笔记

1、权限概述

1.1 认证与受权

  认证:系统提供的用于识别用户身份的功能,一般登陆功能就是认证功能-----让系统知道你是谁??css

  受权:系统授予用户能够访问哪些功能的许可(证书)----让系统知道你能作什么??html

1.2 常见的权限控制方式

  【URL拦截权限控制】——底层基于拦截器或者过滤器实现前端

  【方法注解权限控制】——底层基于代理技术实现,为Action建立代理对象,由代理对象进行权限校验java

2、apache shiro框架简介

2.1 shiro简介

  Apache Shiro(发音为“shee-roh”,日语“堡垒(Castle)”的意思)是一个强大易用的Java安全框架(官方网站:shiro.apache.org),提供了认证、受权、加密和会话管理功能,可为任何应用提供安全保障 - 从命令行应用、移动应用到大型网络及企业应用。mysql

  如下是你能够用 Apache Shiro所作的事情:web

  • 验证用户
  • 对用户执行访问控制。如: 判断用户是否拥有角色admin;判断用户是否拥有访问的权限
  • 在任何环境下使用 Session API。例如CS程序。
  • 可使用多个用户数据源。例如一个是oracle用户库,另一个是mysql用户库。
  • 单点登陆(SSO)功能。 
  • “Remember Me”服务 ,相似购物车的功能,shiro官方建议开启。

2.2 体系结构

  

  Shiro的4大部分——身份验证,受权,会话管理和加密:spring

  • Authentication:身份认证/登陆,验证用户的身份信息;
  • Authorization:受权,哪一个用户拥有什么样的权限给其进行受权。
  • Session Management:会话管理,用户登陆后未退出时其信息都存在会话里。
  • Cryptography:加密,对用户密码进行加密,防止明文密码出现。

  除了以上功能,shiro还提供不少扩展:sql

  • Web Support:Web支持,能够很是容易的集成到Web环境;
  • Caching:缓存可使应用程序运行更有效率。 
  • Concurrency:多线程相关功能。
  • Testing:帮助咱们进行测试相关功能 
  •  "Run As":一个容许用户假设为另外一个用户身份(若是容许)的功能,有时候在管理脚本颇有用。 
  • Remember Me:记住用户身份,这个功能开启后下次登陆就不能从新登陆了,相似购物车功能。

2.3 shiro的工做流程

  工做流程是这样的:前台将用户名/密码经过Subject与shiro的核心管理器(shiro securitymanager)进行交互来获取权限和认证,核心管理器从Realm中获取用户的权限信息。数据库

  • Subject:主体,表明了当前“用户”,这个用户能够是人也能够是某个机器。全部Subject 实例都必须绑定到一个SecurityManager上。
  • SecurityManager:安全管理器;这是shiro的核心,它管理着全部Subject,初始化时协调各个模块运行。然而,一旦 SecurityManager协调完毕,SecurityManager 会被单独留下,且咱们只须要去操做Subject便可,无需操做SecurityManager 。 可是咱们得知道,当咱们正与一个 Subject 进行交互时,实质上是 SecurityManager在处理 Subject 安全操做。
  • Realm:Shiro从Realm获取安全数据(如用户、角色、权限),能够把Realm当作DataSource,即安全数据源。他获取安全数据来判断subject是否可以登陆,subject拥有什么权限。他有点相似DAO。在配置realms时,须要至少一个realm。并且Shiro提供了一些经常使用的 Realms来链接数据源,如LJDBC数据源的JdbcRealm,properties文件数据源的PropertiesRealm,等等。咱们也能够插入本身的 Realm实现来表明自定义的数据源。 像其余组件同样,Realms也是由SecurityManager控制

  

3、使用shiro实现登陆安全认证

  shiro的优点,不须要在代码里面判断是否登陆,是否有执行的权限,实现了从前端页面到后台代码的权限的控制很是的灵活方便。apache

  传统的登陆认证方式是,从前端页面获取到用户输入的帐号和密码以后,直接去数据库查询帐号和密码是否匹配和存在,若是匹配和存在就登陆成功,没有就提示错误。

  而shiro的认证方式则是,从前端页面获取到用户输入的帐号和密码以后,传入给一个UsernamePasswordToken对象也就是令牌,而后再把令牌传给subject,subject会调用自定义的 realm,realm作的事情就是用前端用户输入的用户名,去数据库查询出一条记录(只用用户名去查,查询拿到返回用户名和密码),而后再把两个密码进行对比,不一致就抛出异常。也就是说若是subject.login(token);没有抛出异常,就表示用户名和密码是匹配的,表示登陆成功

  【实现步骤】:

  第一步:在父工程的pom.xml中引入shiro框架相关的jar

    <!-- 引入shiro框架的依赖 -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-all</artifactId>
            <version>1.2.2</version>
        </dependency>

  第二步:在web.xml中配置spring框架提供的用于整合shiro框架的过滤器

<!--配置过滤器,用于整合shiro-->
<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>/*</url-pattern>
</filter-mapping>

   第三步:在spring配置文件中配置bean,id为shiroFilter

<!--配置shiro框架的过滤器工厂bean-->
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <!--shiro的核心安全接口-->
    <property name="securityManager" ref="securityManager"/>
    <!--没有登陆的用户请求须要登陆的页面时自动跳转到登陆页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。-->
    <property name="loginUrl" value="/login.jsp"/>
    <!--登陆成功默认跳转页面,不配置则跳转至"/"。-->
    <property name="successUrl" value="/index.jsp"/>
    <!--未受权时跳转的页面-->
    <property name="unauthorizedUrl" value="unauthorized.jsp"/>
    <!--指定URL级别拦截策略-->
    <property name="filterChainDefinitions">
        <value>
            /css/** = anon
            /js/** = anon
            /images/** = anon
            /login.jsp* = anon
            /validatecode.jsp* = anon
            /userAction_login = anon
        <!-- 拦截page_base_staff.action这个方法必须有staff-list权限才能使用 -->
            /page_base_staff.action = perms["staff-list"]
            /* = authc
        </value>
    </property>
</bean>

   shiro框架提供的过滤器:

  

  anon:例子/admins/**=anon 没有参数,表示能够匿名使用,无需校验权限。

  authc:例如/admins/user/**=authc表示须要认证(登陆)才能使用,没有参数。

  perms:例如/page_base_staff.action=perms["staff-list"]表示须要有"staff-list"这个权限才能查看;参数能够写多个,多个时必须加上引号,而且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每一个参数都经过才经过,想当于isPermitedAll()方法。

  roles:例子/admins/user/**=roles[admin],当前用户是否有这个角色权限。 

  第四步:配置安全管理器

<!--配置安全管理器-->
<bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"/>

  第五步:编写登陆方法

  • 传统的登陆方法:
    /**
         * 用户登陆
         */
        public String login(){
            //从Session中获取生成的验证码
            String validatecode = (String) ServletActionContext.getRequest().getSession().getAttribute("key");
            //校验验证码是否输入正确
            if(StringUtils.isNotBlank(checkcode) && checkcode.equals(validatecode)){
                //输入的验证码正确
                User user = userService.login(model);
                if(user != null){
                    //登陆成功,将user对象放入session,跳转到首页
                    ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
                    return HOME;
                }else{
                    //登陆失败,,设置提示信息,跳转到登陆页面
                    //输入的验证码错误,设置提示信息,跳转到登陆页面
                    this.addActionError("用户名或者密码输入错误!");
                    return LOGIN;
                }
            }else{
                //输入的验证码错误,设置提示信息,跳转到登陆页面
                this.addActionError("输入的验证码错误!");
                return LOGIN;
            }
        }
  • shiro的登陆认证方法

    /**
         * 用户登陆,使用shiro框架提供的方式进行认证操做
         */
        public String login() {
            // 从session中获取生成的验证码
            String validateCode = (String) ServletActionContext.getRequest().getSession().getAttribute("key");
            // 验证验证码是否正确
            if (StringUtils.isNotBlank(checkcode) && checkcode.equals(validateCode)) {
                // 输入的验证码正确
                // 使用shiro框架提供的方式进行认证
                Subject subject = SecurityUtils.getSubject(); //得到当前登陆用户对象,如今状态为"未认证"
                // 用生成令牌(传入用户输入的帐号和密码)
                AuthenticationToken token = new UsernamePasswordToken(model.getUsername(), MD5Utils.md5(model.getPassword()));
                // 认证登陆
                try {
                    // 这里会加载自定义的realm
                    subject.login(token); //把令牌放到login里面进行查询,若是查询帐号和密码时候匹配,若是匹配就把user对象获取出来,失败就抛异常
                    //获取登陆成功的用户对象(之前是直接去service里面查)
                    User user = (User) subject.getPrincipal();
                    ServletActionContext.getRequest().getSession().setAttribute("loginUser", user);
                    return "home";
                } catch (Exception e) {
                    //认证登陆失败抛出异常
                    this.addActionError("用户名和密码错误");
                    return LOGIN;
                }
            } else {
                // 输入的验证码错误,设置提示信息,跳转到登陆页面
                this.addActionError("输入的验证码错误");
                return LOGIN;
            }
        }

  第六步:自定义realm

public class BOSRealm extends AuthorizingRealm {
    @Autowired
    private IUserDao userDao;

    // 受权方法
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        // TODO Auto-generated method stub
        return null;
    }

    //认证方法
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取令牌
        UsernamePasswordToken mytoken = (UsernamePasswordToken) token;
        // 获得帐号和密码
        String username = mytoken.getUsername();
        // 根据用户名查询数据库是否存在用户,若是存在返回对象(帐号和密码都有的对象)
        User user = userDao.findUserByUsername(username);
        if (user == null) {
            // 用户名不存在
            return null;
        }
        // 若是能查询到,再由框架对比数据库中查询到的密码与页面提交的密码是否一致
        // 参数1:用户认证的对象(subject.getPrincipal();返回的对象)
        // 参数2.从数据库根据用户名查询到的用户的密码
        // 参数3.把当前自定义的realm对象传给SimpleAuthenticationInfo,在配置文件须要注入
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
        return info;
    }
}

   第七步:在安全管理器里面注入自定义的realm

<!--配置安全管理器-->
<bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!--注入realm到安全管理器进行密码匹配-->
    <property name="realm" ref="bosRealm"/>
</bean>

<!--注册自定义realm-->
<bean id = "bosRealm" class="cn.itcast.bos.realm.BOSRealm"/>

4、使用shiro为用户受权

4.1 添加权限的四种方式

  • URL拦截权限控制——基于过滤器实现
    <!-- 配置URL拦截规则 -->
    <property name="filterChainDefinitions">
       <value>
           /css/** = anon
           /js/** = anon
           /images/** = anon
           /validatecode.jsp* = anon
           /login.jsp* = anon
           /User_login.action= anon
          <!-- 拦截page_base_staff.action这个方法必须有staff权限才能使用 -->
           /page_base_staff.action = perms["staff"]  /** = authc 
    </value>
    </property>
  • 方法注解权限控制——基于代理技术实现

   第一步:在spring配置文件中开启shiro注解支持

<!--开启shiro框架注解支持-->
<bean id="defaultAdvisorAutoProxyCreator" class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator">
    <!--必须使用cglib方式为Action对象建立代理对象-->
    <property name="proxyTargetClass" value="true"/>
</bean>

<!--配置shiro框架提供的切面类,用于建立代理对象-->
<bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"></bean>

   第二步:在Action的方法上使用shiro注解

/**
 * 取派员批量删除
 * @return
 */ @RequiresPermissions("staff-delete") //执行这个方法,须要当前用户具备staff-delete权限
public String deleteBatch() {
    staffService.deleteBatch(ids);
    return "list";
}

  第三步:在struts.xml中配置全局异常捕获,当shiro框架抛出权限不足异常时,跳转到权限不足提示页面

<!--全局结果集定义-->
<global-results>
    <result name="login">/login.jsp</result>
    <result name="unauthorized">/unauthorized.jsp</result>
</global-results>

<global-exception-mappings>
    <exception-mapping exception="org.apache.shiro.authz.UnauthorizedException" result="unauthorized"></exception-mapping>
</global-exception-mappings>
  •  页面标签权限控制——标签技术实现

  第一步:在jsp页面中引入shiro的标签库   

<%@ taglib uri="http://shiro.apache.org/tags" prefix="shiro"%>

  第二步:使用shiro的标签控制页面元素展现  

<!--有staff-delete权限才能显示此an按钮-->
<shiro:hasPermission name="staff-delete">
{
    id : 'button-delete',
    text : '删除',
    iconCls : 'icon-cancel',
    handler : doDelete
},
</shiro:hasPermission>
  • 代码级别权限控制(几乎不用)——基于代理技术实现

  在要设置权限的代码中添加一下两行代码就能够了

    //修改
    public String edit()
    {
        Subject subject = SecurityUtils.getSubject();
        subject.checkPermission("staff.edit");//要运行此方法下面的代码,必需要拥有staff.edit的权限
        //更新model
        staffService.update(model);
        return "staff";
    }

4.2 受权    

  • 手动受权和认证

  由于要受权的权限太多,因此须要一张权限表

public class BOSRealm extends AuthorizingRealm {
    @Autowired
    private IUserDao userDao;

    // 受权方法
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 为用户受权
        info.addStringPermission("staff");//为page_base_staff.action请求受权staff权限
        info.addStringPermission("staff.delete");//为page_base_staff.action请求受权staff权限
        info.addStringPermission("staff.edit");
        return info;
    }

    //认证方法
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 获取令牌
        UsernamePasswordToken mytoken = (UsernamePasswordToken) token;
        // 获得帐号和密码
        String username = mytoken.getUsername();
        // 根据用户名查询数据库是否存在用户,若是存在返回对象(帐号和密码都有的对象)
        User user = userDao.findUserByUsername(username);
        if (user == null) {
            // 用户名不存在
            return null;
        }
        // 若是能查询到,再由框架对比数据库中查询到的密码与页面提交的密码是否一致
        // 参数1:用户认证的对象(subject.getPrincipal();返回的对象)
        // 参数2.从数据库根据用户名查询到的用户的密码
        // 参数3.把当前自定义的realm对象传给SimpleAuthenticationInfo,在配置文件须要注入
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), this.getName());
        return info;
    }
}
  • 遍历数据库受权

  根据当前登陆用户查询数据库,获取实际对应的权限

// 受权方法
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

    // 获取当前登陆用户对象
    User user = (User) SecurityUtils.getSubject().getPrincipal();
    // 根据当前登陆用户查询数据库,获取实际对应的权限
    List<Function> list = null;
    if (user.getUsername().equals("admin")) {
        DetachedCriteria detachedCriteria = DetachedCriteria.forClass(Function.class);
        // 超级管理员内置用户,查询全部权限
        list = functionDao.findByCriteria(detachedCriteria);
    } else {
        list = functionDao.findFunctionByUserId(user.getId());
    }
    for (Function function : list) {
        info.addStringPermission(function.getCode());
    }

    return info;
}

5、使用ehcache缓存权限数据

  ehcache是专门缓存插件,能够缓存Java对象,提升系统性能。

5.1 配置ehcache缓存

  第一步:在pom.xml文件中引入ehcache的依赖

<!-- 引入ehcache的依赖 -->
<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.6</version>
</dependency>

   第二步:在项目中提供ehcache的配置文件(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"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
</ehcache>

  第三步:在spring配置文件中配置缓存管理器对象,并注入给安全管理器对象

<!--配置安全管理器-->
<bean id = "securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!--注入realm到安全管理器进行密码匹配-->
    <property name="realm" ref="bosRealm"/>
    <!--注入缓存管理器-->
    <property name="cacheManager" ref="cacheManager"/>
</bean>

<!--注册缓存管理器-->
<bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <!--注入ehcache的配置文件-->
    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
</bean>

5.2 测试缓存的做用

  上面咱们已经配置好了缓存,那么咱们怎么证实缓存是否起做用了呢?咱们能够经过给Realm打断点的方式来测试:

  

  没配置ehcache缓存前,每次点击查询页面,都会执行这个方法。

  而配置了缓存后,只有当咱们第一次访问这个查询页面的时候,才会执行一次这个方法,即只用查询一次数据库,之后就不用查(权限数据)了。

  那么何时会再次查数据库呢?咱们能够经过ehcache.xml配置它的有效时间,默认是(从不操做开始)2分钟后再次查询,咱们能够修改相关配置来肯定它的有效时间,(好比设置6秒后再次查询数据库):

<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="6"
            timeToLiveSeconds="6"
            overflowToDisk="true"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
            />
</ehcache>

  若是,咱们从新登陆用户,有效时间也会从新生效。每次咱们从新登陆后,原先缓存的数据就没了,哪怕你设置的时间还没到。

 

 

 

 

 

 

 

 

 

 

 参考:https://blog.csdn.net/liaomin416100569/article/details/78838900

  https://baijiahao.baidu.com/s?id=1591438032280398708&wfr=spider&for=pc

  https://www.cnblogs.com/AngeLeyes/p/7196956.html

相关文章
相关标签/搜索