• Shiro框架-----用来实现认证和授权的操作


                                          Shiro框架

    1.概念:shiro : 是一个权限管理控制框架。主要提供了认证与授权的操作。

        认证:用户得登录成功后才能访问页面。   (这时是可以访问所有页面)

        授权:授权访问资源,是指用户认证成功的情况下,再判断模块(权限)来访问 可访问的页面。    (这时是只能访问又权限的页面)


    2.shiro  和 spring security的比较

     ①shiro比spring security简单

     ②shiro需要和spring整合,而spring security不需要

     ③spring security功能更加的强大


    3.shiro的核心实现是---过滤器(拦截或过虑请求),用于用户访问页面(URL)时进行权限控制

    4.shiro的主要功能模块有:

      Authentication:身份认证/登录,------核心功能

      Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限 --------核心功能

      Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中

      Cryptography:加密,保护数据的安全性,

      Web Support:Web支持

      Caching:缓存

      Concurrency:shiro支持多线程应用的并发验证

    记住一点,Shiro不会去维护用户和维护权限;这些需要我们自己去设计/提供;然后通过相应的接口注入给Shiro即可


    5.shiro的三大组件

      Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,

          保存用户信息,负责认证和授权方法调用,只要调了方法就会到SecurityManager

      SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;它管理着所有Subject;可以看出它是Shiro的核心,负责认证和授权的管理    

      Realm:域,连接数据库获取认证和授权信息的桥梁,Shiro从Realm获取安全数据(如用户、角色、权限);

    也就是说对于我们而言,最简单的一个Shiro应用:
    1、应用代码通过Subject来进行认证和授权,而Subject又委托给SecurityManager;
    2、我们需要给Shiro的SecurityManager注入Realm,从而让SecurityManager能得到合法的用户及其权限进行判断。
    从以上也可以看出,Shiro不提供维护用户/权限,而是通过Realm让开发人员自己注入。


    6.shiro的环境搭建

      项目添加shiro依赖 (父项目已经添加) (shiro通常是一个项目的一部分) 

        <!--shiro-->
        <!--shiro和spring整合-->
        <dependency>
          <groupId>org.apache.shiro</groupId>
          <artifactId>shiro-spring</artifactId>
          <version>1.3.2</version>
          </dependency>
        <!--shiro核心包-->
        <dependency>
          <groupId>org.apache.shiro</groupId>
          <artifactId>shiro-core</artifactId>
          <version>1.3.2</version>
        </dependency>

       配置web.xml

    <!--Spring整合Shiro的过滤器
    作用: 接收所有需要交给Shiro过滤的请求

    过滤器的名称: filter-name就是Spring的bean的id
    -->
    <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>

      配置applicationContext-shiro.xml, Spring整合shiro

    <?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">

    <!--1.Shiro处理认证授权逻辑的对象-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
    <!--安全管理器-->
      <property name="securityManager" ref="securityManager"/>
    </bean>

    <!--2.配置安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
    <!--配置Realm-->
      <property name="realm" ref="myRealm"/>
    </bean>

    <!--3.配置Realm-->
    <bean id="myRealm" class="cn.itcast.web.shiro.AuthRealm"/>

    </beans>

      创建自定义realm类:AuthRealm,继承AuthorizingRealm    

    public class AuthRealm extends AuthorizingRealm{
      //授权
      @Override
      protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
      }

      //认证
      @Override
      protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        return null;
      }
    }


     7.shiro的使用

    前引:

    一:与认证(登录)相关的过滤器的使用
    过滤器:(先拦截,在判断)
    anon:匿名过滤器,代表不认证(登录)也可以访问该请求,通过对静态资源进行放行(指css,js,图片都没有了)
    authc:认证过滤器,代表必须认证成功才可以访问该资源,如果认证不成功或没有认证,会自动跳转到login.jsp页面(自带的),但是可以修改默认登录页面,用<property name="loginUrl" value="/login.jsp"/>
    用法:
    /make/**=anon
    /** = authc
    anon一定要放在authc前,/** = authc表示拦截所有的请求,而在其上面的/make/**=anon表示这个放行不拦截
    该拦截的资源用authc,该放行的资源用anon
    又由于静态资源也会被拦截,所以anon主要用来给静态资源放行(css,js,图片等)
    /login.do因为是登录请求,必须使用anon过滤器放行!!!否则无法登录啦!

    二与授权相关的过滤器

    第一部分:Shiro登陆认证(一)使用认证过滤器

      第一步:applicationContext-shiro.xml添加认证过滤器

    <!--1.Shiro处理认证授权逻辑的对象-->
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <!--安全管理器-->
            <property name="securityManager" ref="securityManager"/>
    
    
            <!--设置Shiro的过滤器-->
    
            <!--
              过滤的路径问题:
                /index.jsp :  准确拦截/index.jsp页面
                /user/*  : 模糊匹配,拦截user根目录下的所有资源(代表一级目录)
                /user/**  : 模糊匹配,拦截user目录下的所有资源(代表任意级目录)
    
              1. 认证相关的过滤器
                 anon: 匿名过滤器,不用认证也可以访问该资源(必须放在authc的前面)
                 authc:认证过滤器,必须认证成功才可以访问该资源,如果认证不成功,会自动跳转到login.jsp页面(可以通过loginUrl属性更新登录页面)
            -->
            <property name="filterChainDefinitions">
                <value>
                    /css/**=anon
                    /img/**=anon
                    /make/**=anon
                    /plugins/**=anon
                    /login.do=anon
                    /**=authc
                </value>
            </property>
    
            <!--修改shrio默认登录页面-->
            <property name="loginUrl" value="/login.jsp"/>
    
        </bean>
    

      第二步:修改LoginController,通过shiro实现登陆认证

    /**
         * 登录方法
         *  1)URL: http://localhost:8080/login.do
         *  2)参数:email=eric&password=123
         *  3)返回:/WEB-INF/pages/home/main.jsp
         */
        @RequestMapping("/login")
        public String login(String email,String password){
            //1.判断是否为空
            if(StringUtils.isEmpty(email) || StringUtils.isEmpty(password)){
                request.setAttribute("error","邮箱和密码不能为空");
                //手动转发到login.jsp页面
                return "forward:/login.jsp";
            }
      //-----------------------------------------------------一以下是正常做法--------------------------------------------------------
            /*//2.判断email是否存在
            User loginUser = userService.findByEmail(email);
    
            //3 email不存在,提示"用户名不存在"
            if(loginUser==null){
                request.setAttribute("error","用户名不存在");
                //手动转发到login.jsp页面
                return "forward:/login.jsp";
            }
    
            //4 email存在,继续判断password是否正确
            if(loginUser.getPassword().equals(password)){
                //5. password正确,登录成功,保存用户数据到session域中,跳转到主页
                session.setAttribute("loginUser",loginUser);
    
                //查询当前用户拥有的菜单(模块)
                List<Module> menuList = moduleService.findModuleByUser(loginUser);
                //去除重复元素
                removeDuplicate(menuList);
                //菜单必须存入session域
                session.setAttribute("menus",menuList);
    
    
                // 路径: /WEB-INF/pages/home/main.jsp
                return "home/main";
            }else{
                //6.password不正确,提示"密码错误"
                request.setAttribute("error","密码错误");
                //手动转发到login.jsp页面
                return "forward:/login.jsp";
            }*/
    
    //---------------------------------------------------以上是正常做法---------------------------------------------------
            //使用Shiro的登录认证逻辑
    
            //1.获取Subject对象
            Subject subject = SecurityUtils.getSubject();
    
            //2.UsernamePasswordToken 封装需要认证的信息(用户名和密码)
            UsernamePasswordToken token = new UsernamePasswordToken(email,password);
    
            //3.调用Subject的login方法进行认证
            //4.判断login方法有无异常,有异常代表认证失败,无异常代表认证成功
            try {
                subject.login(token);
    
                //无异常代表认证成功
    
                //5.从Shiro的Subject中获取登录用户对象:  subject.getPrincipal()
                User loginUser = (User)subject.getPrincipal();
    
                //把登录用户对象存入session域 (注意:这里存入的loginUser对象,不是给Shiro使用的,是给我们自己业务使用的)
                session.setAttribute("loginUser",loginUser);
    
                //查询当前用户拥有的菜单(模块)
                List<Module> menuList = moduleService.findModuleByUser(loginUser);
                //去除重复元素
                //若是已经在sql语句那里加了distinct去重,就不需要这个了
                removeDuplicate(menuList);
                //菜单必须存入session域
                session.setAttribute("menus",menuList);
    
                //跳转到主页
                return "home/main";
            } catch (UnknownAccountException e) {  //UnknownAccountException: 该异常代表用户名不存在
                request.setAttribute("error","Shrio:用户名不存在");
                return "forward:/login.jsp";
            } catch (IncorrectCredentialsException e) {  //IncorrectCredentialsException: 该异常代表密码错误
                request.setAttribute("error","Shrio:密码输入有误");
                return "forward:/login.jsp";
            } catch (Exception e) {  //其他异常
                request.setAttribute("error","Shrio:服务器错误");
                return "forward:/login.jsp";
            }
    
        }
    

      第三步:编写AuthRealm,实现登陆认证

    /**
     * 自定义Realm
     */
    public class AuthRealm extends AuthorizingRealm{
        @Autowired
        private UserService userService;
    
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行授权方法...");
            return null;
        }
    
        //认证 (subject.login(token)方法固定执行doGetAuthenticationInfo方法)
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            //System.out.println("执行认证方法...");
    
            //1.判断用户名是否存在
            //token传给了AuthenticationToken
            UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
            String email = token.getUsername();
    
            User loginUser = userService.findByEmail(email);
            if(loginUser==null){
                //用户名不存在
                //我们只需要return null即可,Shiro底层判断为null则抛出UnKnowAccountException异常
                return null;
            }
    
            //2.返回数据库保存密码给Shiro,让Shiro判断密码是否正确
            /**
             * Shiro底层获取SimpleAuthenticationInfo的参数,例如获取password判断和用户输入的密码是否一致
             *    1)如果密码不一致,抛出IncorrectCredentialsException异常
             *    2)如果密码一致, 把principal存入session域
              */    
    
            //SimpleAuthenticationInfo: 封装认证数据对象
            /**
             * 参数一: principal  登录用户对象,用于Subject.getPrincipal()方法获取
             * 参数二: password   数据库的密码
             * 参数三: realm的别名, 在多个Realm的情况下使用,用于区分用户表的。只有一张用户表不需要别名
             */
            return new SimpleAuthenticationInfo(loginUser,loginUser.getPassword(),"");
        }
    }
    

    总结:

    Shiro登录认证的流程是怎样的?

      LoginController的login方法调用Subject.login(),从而就到SecurityManager

      AuthRealm的认证方法,编写判断用户名和返回数据库密码的逻辑,从数据库获得数据


    8.加密

    Shiro登陆认证(三)凭证匹配器-普通加密(如md5)

    第一步:在applicationContext-shiro.xml,添加加密认证配置--shiro自带的凭证匹配器

     <!--创建Shiro自带的凭证匹配器-->
        <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
            <!--指定需要加密的算法名称-->
            <property name="hashAlgorithmName" value="md5"/>
        </bean>

    第二步:对数据库密码进行md5加密(select md5(123);),再直接修改数据库的用户密码

    请简单说出Shiro加密判断的执行流程

    Subject.login(token) (未加密数据) -> AuthRealm的认证方法->返回数据库密码->先把token密码使用算法加密,再和数据库的密码匹配

     

    Shiro登陆认证(四)凭证匹配器-加盐加密

    使用md5算法加密1次 + 盐(变量,每个用户不同的)= 2次使用md5加密;  一般是使用邮箱作为盐来第二次md5加密

    步骤

    1)编写代码对密码加盐加密,查看结果,把数据库里面的用户密码替换掉

    public class Demo {
    
        public static void main(String[] args) {
            //1.原密码
            String password = "123";
    
            //2.盐
            String salt = "lw@export.com";
    
            //3.加盐加密
            /**
             * 参数一:原密码
             * 参数二:盐
             * 参数三(可选):加密次数,默认1次
             */
            Md5Hash md5Hash = new Md5Hash(password,salt);
    
            System.out.println(md5Hash.toString());
        }
    
    }
    

      

    2)编写自定义凭证匹配器

    public class CustomCredentialsMatcher extends SimpleCredentialsMatcher{
    
        /**
         * 完成密码判断的逻辑
         * @param token  包含了用户输入的密码
         * @param info   包含了数据库的密码
         * @return 密码判断结果
         *       true: 代表数据库的密码和用户输入的密码一致的
         *       false:代表数据库的密码和用户输入的密码不一致的
         */
        
        //打个do就能出来这个方法
        @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
    
            //1.获取用户输入的密码
            UsernamePasswordToken userToken = (UsernamePasswordToken)token;
            
            //UsernamePasswordToken 是用来封装用户名和密码,用户名用字符串封装,密码用字符数组封装
            //userToken.getPassword()['1','2','3']
            String userPassword = new String(userToken.getPassword()); // ['1','2','3']
    
            //获取用户邮箱
            String email = userToken.getUsername();
    
            //2.对用户输入的密码进行加盐加密
            Md5Hash md5Hash = new Md5Hash(userPassword,email);
            String encodePwd = md5Hash.toString();
    
            //3.获取数据库的密码
            String dbPwd = (String)info.getCredentials();
    
            //4.判断数据库的密 码和第二步产生的密码是否一致,一致返回true,不一致返回false
            return dbPwd.equals(encodePwd);
        }
    }
    

      

    3)在applicationContext-shiro.xml,添加自定义凭证匹配器

     <!--3.配置Realm-->
        <bean id="myRealm" class="cn.itcast.web.shiro.AuthRealm">
            <!--配置凭证匹配器-->
            <property name="credentialsMatcher" ref="credentialsMatcher"/>
        </bean>
        
        <!--创建自定义凭证匹配器-->
        <bean id="credentialsMatcher" class="cn.itcast.web.shiro.CustomCredentialsMatcher"/>
    

      

    4)测试

     

    拓展注意:

    1.添加用户的时候要让用户的密码加密,故再service实现类里面写添加用户的方法是的业务逻辑是这样的

     @Override
        public void save(User user) {
            //生成主键
            user.setId(UUID.randomUUID().toString());
            
            //对密码加盐加密
            Md5Hash md5Hash = new Md5Hash(user.getPassword(),user.getEmail());
            user.setPassword(md5Hash.toString());
            
            userDao.save(user);
        }
    

    2.Shiro的登录注销代码实现

      @RequestMapping("/logout")
        public String logout(){
            //删除session的登录数
            session.removeAttribute("loginUser");
            session.removeAttribute("menus");
    
            //Shiro登录注销(底层:删除之前Shiro存入的session的key)
            //防止浏览器记住登录认证
            Subject subject = SecurityUtils.getSubject();
            subject.logout();
    
            return "redirect:/login.jsp";
        }
    

      

     

     

    第二部分:Shiro授权(一)使用认证过滤器

    如何实现授权?分为以下两个步骤

    1. 登陆认证成功后,获取用户的权限 (获取权限),就相当于买票

    2. 访问资源时候,进行授权校验:用访问资源需要的权限去用户权限(模块)列表查找,如果存在,则有权限访问资源。(权限拦截)。就相当于售票

    .获取权限

    实现自定义realm的doGetAuthorizationInfo()方法,返回用户已经具有的权限。

     //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            //System.out.println("执行授权方法...");
    
            //1、获取当前登录用户拥有的权限
            //从Subject获取当前登录用户
            Subject subject = SecurityUtils.getSubject();
            User loginUser = (User)subject.getPrincipal();
    
            List<Module> moduleList = moduleService.findModuleByUser(loginUser);
    
            //2、把当前登录用户的权限告诉Shiro框架
            //2.1 创建授权对象
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
            //2.2 把模块存入授权对象
            if(moduleList!=null && moduleList.size()>0){
                for(Module module:moduleList){
                    if(!StringUtils.isEmpty(module.getName())) {
                        //往授权对象存入授权信息(授权信息存入什么字符串? 字符串必须唯一的)
                        info.addStringPermission(module.getName());
                    }
                }
    
            }
            return info;
        }
    

      

    .授权校验

    shiro其实提供了四种方式实现了权限校验:硬编码,过滤器,注解方法,jsp标签。主要掌握注解方法,jsp标签

    1)注解方法

    1. 在applicationContext-shiro.xml中开启shiro注解支持

      <!--====开启Shiro的注解====-->
        <!--
            Shiro注解借助了Spring的AOP来实现授权校验
        -->
        <bean id="lifecycleBeanPostProcessor"
              class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
        <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
              depends-on="lifecycleBeanPostProcessor"/>
        <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
            <property name="securityManager" ref="securityManager"/>
        </bean>
    

      2.开启Aop自动代理(已经完成)

    <!--6. 开启Aop自动代理-->
    <aop:aspectj-autoproxy/>
    

      3.在controller中使用@RequiresPermissions(“”)注解

    2)再页面上使用jsp标签

    <%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>测试Shiro的jsp标签</title>
    </head>
    <body>
    
    <shiro:hasPermission name="企业管理">
    <a href="">企业管理</a>
    </shiro:hasPermission>
    
    <hr/>
    
    <shiro:hasPermission name="用户管理">
    <a href="">用户管理</a>
    </shiro:hasPermission>
    
    </body>
    </html>
    

      



    最后,总结一下

    1.shiro的关键代码:

      认证: subject.login(token);

      授权: subject.checkPermission("");

    2.认证和授权的流程

     流程图

    Shiro vs Spri第二ng Security

    Shiro vs Spring Security

    Shiro vs Spring Security

    一个Java开发的菜鸟
  • 相关阅读:
    设计模式之备忘录模式
    特殊传参方式
    页面响应效率测试
    composer安装的包git无法提交的解决办法是因为安装的时候生成了.git隐藏文件
    数据结构和算法深入浅出理解
    中缀表达式转换为后缀表达式
    p2p技术
    【自动化测试】WebDriver使用
    pt-query-digest简介使用
    mac编译openJDK8
  • 原文地址:https://www.cnblogs.com/lanto/p/13235813.html
Copyright © 2020-2023  润新知