Shiro

作者: 忒无聊了叭 | 来源:发表于2020-10-21 19:01 被阅读0次

    1.权限的管理

    1.1什么是权限管理?

    基本上涉及到用户参与的系统都要有权限管理,权限管理属于系统的安全范畴,权限管理实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可以访问而且只能访问自己授权的资源。

    权限管理包括身份认证授权两部分,简称认证授权。对于需要访问。对于需要访问控制资源的用户首先经过身份认证,认证通过后用户具有该资源的访问权限方可访问。

    1.2什么是身份认证

    身份认证,就是判断一个用户是否为合法用户的处理过程。最常用的简单身份认证方式是系统通过核对对用户输入的用户名和口令,看其是否与系统中存储的该用户的用户名和口令一致,来判断用户身份是否正确。

    1.3什么是授权

    授权,就是访问控制,控制谁能访问那些资源。主体进行身份认证后需要分配权限方可访问系统的资源,对于某些资源没有权限是无法访问的。

    2.什么是shiro

    官网:http://shiro.apache.org/

    介绍:

    Apache Shiro™ is a powerful and easy-to-use Java security framework that performs authentication, authorization, cryptography, and session management. With Shiro’s easy-to-understand API, you can quickly and easily secure any application – from the smallest mobile applications to the largest web and enterprise applications.

    翻译过来:

    阿帕奇 shiro™ 是一个功能强大且易于使用的Java安全框架,它执行身份验证、授权、加密和会话管理。使用Shiro易于理解的API,您可以快速、轻松地保护任何应用程序—从最小的移动应用程序到最大的web和企业应用程序。

    总结:

    shiro是阿帕奇旗下的一个开源框架,它将软件系统的安全认证相关的功能抽取出来,实现用户身份认证,权限管理、加密、会话管理等功能,组成一个通用的安全认证框架。

    3.shiro的核心架构

    3.1架构图说明
    架构图

    Security Manager:安全管理器,最重要的部分,最核心的API之一,日后去权限管理时,应该先找到安全管理器。

    Authenticator和Authorizer是认证和授权,最主要的两个部分。

    Session Manager和Session Dao是和会话相关的。

    Cache Manager是进行缓存的。

    最右边绿色的是进行密码加密处理的,MD5的加密方式是经常使用的。

    3.2各部分详细说明

    Subject:Subject即主体,外部应用与subject进行交互,subject记录了当前操作用户,将用户的概念理解为当前操作的主体,可能是一个通过浏览器请求的用户,也可能是一个运行的程序。Subject在shiro中是一个接口,接口中定义了很多认证授相关的方法,外部程序通过subject进行认证授,而subject是通过SecurityManager安全管理器进行认证授权。

    Security Manager:SecurityWanager即安全管理器,对全部的subject进行安全管理,它是shiro的核心,负责对所有的subject进行安全管理。通过SecurityManager可以完成subject的认证、授权等,实质上SecurityManager是通过Authenticator进行认证,通过Authorizer进行授权.通过SessionManager进行会话管理等。

    • Security Manager是一个接口,继承了Authenticator,Authorizer,SessionManager这三个接口。

    Authenticator:Authenticator即认证器,对用户身份进行认证,Authenticator是一个接口,shiro提供ModularRealmAuthenticator实现类,通过ModularRealmAuthenticator基本上可以满足大多数需求,也可以自定义认证器。

    Authorizer:即授权器,用户通过认证器认证通过,在访问功能时需要通过授权器判断用户是否有此功能的操作权限。

    Realm:即领域,相当于dataSource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库中获取用户身份信息。

    • 不要吧realm理解成只从数据源中取数据,在realm中还有认证授权校验的相关代码。

    SessionManager:即会话管理,shiro框架定义了一套会话管理,它不依赖web容器的seesion,所以shiro可以使用在非web应用上,也可以将分布式应用的会话,此特性可以实现单点登录。

    SessionDao:即会话Dao,是对Session会话操作的一套接口,比如要将seesion存储到数据库,可以通过jdbc将会话存储到数据库。

    CacheManager:即缓存管理,将用户权限数据存储到缓存,这样可以提高性能。

    Cryptography:即密码管理,shiro提供了一套加密、解密额组件,方便开发。比如提供常用的散列、加/解密等功能。

    4.shiro中的认证

    4.1认证

    身份认证。就可以判断一个用户是否为合法用户的处理过程。最常用的的简单身份认证方式是系统通过核对用户名和口令,看其是否与系统中存储的该用户和口令一致,来判断用户身份是否正确。

    4.2shiro中认证的关键对象
    • Subject :主体

    访问系统的用户,主体可以是用户,程序等,进行认证的都称为主体。

    • Principal:身份信息

    是主体进行身份认证的标识,标识必须具有唯一性,如用户名,手机号,邮箱地址等,一个主体可以有多个身份,但是必须有一个主身份。

    • credential:身份凭证

    是只有主题自己知道的安全信息,如密码、证书。

    4.3认证流程
    认证流程

    注意:在我们没有集成spring框架时,shiro配置文件使用ini结尾。用来学习shiro书写我们系统中相关权限数据。我们先不进行连接数据库,将所用的数据写死到ini文件中。

    4.4认证的开发

    1、创建项目并且导入依赖

    <!-- https://mvnrepository.com/artifact/org.apache.shiro/shiro-core -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.5.3</version>
    </dependency>
    

    2、在Resource中的ini配置本地数据

    shiro.ini

    [users]
    szw=123
    sq=123456
    

    3、走完一个最简单的认证

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/11
     */
    public class TestAuthenticator {
        public static void main(String[] args) {
    
            // 创建安全管理器
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
    
            // 由于要从ini中获取数据,所以这里要建立起来realm,给安全管理器设置realm
            securityManager.setRealm(new IniRealm("classpath:shiro.ini"));
    
            // SecurityUtils 全局安全工具类,给全局的安全工具类设置安全管理器
            SecurityUtils.setSecurityManager(securityManager);
    
            // 创建subject主体
            Subject subject = SecurityUtils.getSubject();
    
            // 用户认证,创建令牌
            UsernamePasswordToken token = new UsernamePasswordToken("szw","123");
    
            try{
                System.out.println("认证之前:"+subject.isAuthenticated());
                subject.login(token);// 用户状态
                System.out.println("认证之后:"+subject.isAuthenticated());
            }catch (UnknownAccountException e){// 用户名未知错误异常
                e.printStackTrace();
                System.out.println("认证失败,用户名不存在...");
            }catch (IncorrectCredentialsException e){// 凭证错误异常
                e.printStackTrace();
                System.out.println("认证失败,凭证不正确...");
            }
        }
    }
    
    

    4.5自定义Realm

    先来理解下realm的作用,注意底下的一句话。

    Realm:即领域,相当于dataSource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据,比如:如果用户身份数据在数据库那么realm就需要从数据库中获取用户身份信息。

    • 不要吧realm理解成只从数据源中取数据,在realm中还有认证授权校验的相关代码。

    我们在日后的编码中,在realm中去查询数据库即可。

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/12
     */
    public class CustomerRealm extends AuthorizingRealm {
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 从token中获取用户名
            String principal = (String) authenticationToken.getPrincipal();
            // 根据身份信息查询数据库,这里模拟数据库
            if ("szw".equals(principal)){
                // 三个参数,1、身份信息 2、密码 3、当前realm的名字,通过方法调用即可
                SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(principal,"123",this.getName());
                return simpleAuthenticationInfo;
            }
            return null;
        }
    }
    // SimpleAuthenticationInfo 实现了AuthenticationInfo
    

    测试:

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/12
     */
    public class TestCustomerRealm {
        public static void main(String[] args) {
            // 创建安全管理器
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
            // 设置自定义的Realm
            securityManager.setRealm(new CustomerRealm());
            // 将安全工具类设置安全工具类
            SecurityUtils.setSecurityManager(securityManager);
            // 通过安全工具类获取主体
            Subject subject = SecurityUtils.getSubject();
            // 创建token
            UsernamePasswordToken token = new UsernamePasswordToken("szw","1234");
            try {
                subject.login(token);// 用户状态
            }catch (UnknownAccountException e){// 用户名未知错误
                e.printStackTrace();
                System.out.println("认证失败,用户名不存在...");
            }catch (IncorrectCredentialsException e){
                e.printStackTrace();
                System.out.println("认证失败,凭证不正确...");
            }
        }
    }
    

    4.6MD5加密介绍

    我们可以发现在上述的自定义realm中,我们的密码是明文,这样如果盗取了我们的数据库,使用明文存储密码的话,安全性很低,所以我们这里可以使用MD5加密。

    MD5作用:一般用来加密或者签名,校验。

    特点:MD5是不可逆的,如果内容相同,无论执行多少次,md5生成的结果都是一致的。无论内容的大小是多少GB还是T,生成的结果都是16进制的32位长度字符串。

    比较两个文件的内容时,也可以使用MD5进行比较。

    MD5+Salt

    应用到我们的系统的时候,由于可能用户的密码很简单,黑客可能可以通过穷举算法破解md5的密码,所以我们可以在我们的业务层中对用户的密码加上一些复杂的字符串,相当于一层盐,让这个密码更“咸”,就是更混乱,这个就相当于Salt!但是我们要在我们的数据库中增加一栏,就是这个复杂的字符串,用户原来的密码加上复杂的字符串再通过md5,会很难被破解。

    4.7MD5加密实际应用测试

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/13
     */
    public class TestShiroMd5 {
        public static void main(String[] args) {
    
            /*
                不要使用set方法进行加密,没有用
             */
    
            // 创建一个md5算法,同时加上了哈希散列,更加安全,注意要将明文放在构造方法中,才可以进行加密
            Md5Hash md5Hash = new Md5Hash("123");
            System.out.println(md5Hash.toHex());
    
            // 创建md5算法,并且将进行salt加密处理
            Md5Hash md5Hash1 = new Md5Hash("123","Q!W@!#*2");
            System.out.println(md5Hash1.toHex());
    
            // 创建md5算法,并且将进行salt加密处理,再加上Hash散列
            Md5Hash md5Hash2 =  new Md5Hash("123","Q!W@!#*2",1024);
            System.out.println(md5Hash2.toHex());
    
        }
    }
    

    如果我们注册时将密码进行了MD5的加密方式,在认证时就需要修改realm默认的验证方式,将原来简单的equals修改为md5的形式,是在初始化realm中设置。

    密码的验证方式改为md5

            // 注入realm,并且我们需要设置凭证的对比方式,使用md5
            CustomerRealm realm = new CustomerRealm();
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            credentialsMatcher.setHashAlgorithmName("md5");
            // 设置凭证到realm
            realm.setCredentialsMatcher(credentialsMatcher);
            // 将realm设置到安全管理器中
            securityManager.setRealm(realm);
    

    ok,我们已经把realm的验证方式改成了md5,那么如果我们注册时再加上盐呢?

    我们就需要去realm的认证方法中修改返回时多加上一条参数

     // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 从token中获取用户名
            String principal = (String) authenticationToken.getPrincipal();
            // 根据身份信息查询数据库,这里模拟数据库
            if ("从数据库取出的账户名".equals(principal)){
    // 四个参数,1、身份信息 2、密码 3、从数据库中取出初试时添加的随机盐  4、当前realm的名字,通过方法调用即可
    
                return  new SimpleAuthenticationInfo(principal,
                        "数据库的加密密码",
                        ByteSource.Util.bytes("从数据库取出的随机盐"),
                        this.getName());
            }
            return null;
        }
    

    当你觉得这就结束了还是不够的!

    当我们使用了第三种加上散列后,怎么处理呢?

    告诉credentialsMatcher我们散列的次数就可以啦!

    // 散列的次数与自己当时加密时的次数要一致    
    credentialsMatcher.setHashIterations(1024);
    

    5.Shiro中授权

    5.1授权的概念

    授权,即访问控制,控制谁能访问那些资源。主体进行身份认证后需要分配权限方可访问系统资源,对于某些资源是没有权限访问的。

    5.2关键对象

    授权可以简单的理解为who对what进行How操作

    • who即主体,主体需要访问系统中的资源
    • what即资源,如菜单,页面,按钮,类方法。系统商品信息。资源包括资源类型和资源实例,比如商品信息为资源类型,类型001的商品为资源实例。
    • How,权限/许可。规定了主体对资源的操作许可,权限离开资源没有意义,通过权限可以知道用户对那些资源有许可。

    5.3授权流程

    授权流程

    5.4授权方式

    • 基于角色的访问控制

      • RBAC基于角色的访问控制,是以角色为中心进行访问控制
      if(subject.hasRole("admin")){
        // 操作什么权限
      }
      
    • 基于资源的访问控制

      • RBDC基于资源的访问控制,是以资源为中心进行访问控制
      // 某一个资源实例
      if(subject.isPermission("user:update:01")){
        // 对01用户进行修改
      }
      // 整个资源类型都可以修改
      if(subject.isPermission("user:update:*")){
        // 对全部用户进行修改
      }
      
      

    5.5权限字符串

    我们在shiro中用的是上述授权方式的第二种。

    权限字符串的规则是:资源标识符:操作:资源实例标识符,意思是对哪个资源的哪个实例具有什么操作,“:”是资源/操作/实例的分割符。权限字符串也可以使用*通配符。

    例如:

    • 用户创建权限:user:create 或 user:create:*
    • 用户修改实例001额权限 user:update:001
    • 用户实例001的所有权限: user:*:001

    我们在设计系统时。也应该按权限字符串的标识进行设置。

    5.6shiro中授权编程的实现方式

    • 编程式
    Subject subject = SecturityUtils.getSubject();
    if(subject.hasRole("admin")){
        // 有权限
    }else{
        // 无权限
    }
    
    • 注解式
    @RequiresRoles("admin")
    public void hello(){
        // 有权限
    }
    
    • 标签式
    在JSP页面上,通过标签的形式完成
    <shiro:hasRole name = "admin">
        <!- 有权限->
    </shiro:hasRole>
    

    测试:

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/12
     */
    public class TestCustomerRealm {
        public static void main(String[] args) {
            // 创建安全管理器
            DefaultSecurityManager securityManager = new DefaultSecurityManager();
    
           // 注入realm,并且我们需要设置凭证的对比方式,使用md5
            CustomerRealm realm = new CustomerRealm();
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            credentialsMatcher.setHashAlgorithmName("md5");
            // 设置散列的次数
            credentialsMatcher.setHashIterations(1024);
            // 设置凭证到realm
            realm.setCredentialsMatcher(credentialsMatcher);
            // 将realm设置到安全管理器中
            securityManager.setRealm(realm);
    
            // 将安全工具类设置安全工具类
            SecurityUtils.setSecurityManager(securityManager);
            // 通过安全工具类获取主体
            Subject subject = SecurityUtils.getSubject();
            // 创建token
            UsernamePasswordToken token = new UsernamePasswordToken("szw","123");
            try {
                subject.login(token);// 用户状态
            }catch (UnknownAccountException e){// 用户名未知错误
                e.printStackTrace();
                System.out.println("认证失败,用户名不存在...");
            }catch (IncorrectCredentialsException e){
                e.printStackTrace();
                System.out.println("认证失败,凭证不正确...");
            }
    
            // 如果用户已经认证
            if(subject.isAuthenticated()){
                // 基于角色的权限控制, 判断当前主体有没有admin角色的权限
                boolean flag = subject.hasRole("admin");
                if (flag) System.out.println("含有该权利");
    
                // 判断该主体同时有没有这两个权限
                boolean b = subject.hasAllRoles(Arrays.asList("admin", "user"));
                if (b) System.out.println("含有admin和user两个权限");
    
                /**
                 * 我们可以发现,每当我们调用一次判断权限的方法,就会调用到Realm的授权中一次
                 */
    
                // 基于权限字符串的访问控制,  资源标识符:操作:资源类型,中间的操作都拥有,add、update、delete、create
                // 通过测试发现,只要在realm的授权中的操作设置*,这里的判断权限写任何字符串都含有权限,1、sw....
                System.out.println("权限" + subject.isPermitted("user:*:01"));
                System.out.println(subject.isPermittedAll("user:*:01", "product:create:02  "));
    
            }
        }
    }
    
    

    Realm中:

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/12
     */
    public class CustomerRealm extends AuthorizingRealm {
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("==========打印授权方法============");
            String primaryPrincipal = (String)principalCollection.getPrimaryPrincipal();
            System.out.println("身份信息:" + primaryPrincipal);
    
            // 根据身份信息 用户名 获取当前用户的角色信息,以及角色信息 szw admin user
            SimpleAuthorizationInfo s = new SimpleAuthorizationInfo();
    
            // 将数据库中查询角色信息赋值到s中,假数据
            s.addRole("admin");
            s.addRole("user");
            
            //将数据库中查询的权限信息赋值给权限对象
            s.addStringPermission("user:*:01");
            s.addStringPermission("product:create:02");
            
            return s;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 从token中获取用户名
            String principal = (String) authenticationToken.getPrincipal();
            // 根据身份信息查询数据库,这里模拟数据库
            if ("szw".equals(principal)){
                // SimpleAuthenticationInfo 实现了AuthenticationInfo
                // 三个参数,1、身份信息 2、密码 3、从数据库中取出初试时添加的随机盐  4、当前realm的名字,通过方法调用即可
    
                return  new SimpleAuthenticationInfo(principal,
                        "323296d9b262097975da7ae56cb9c1b5",
                        ByteSource.Util.bytes("Q!W@!#*2"),
                        this.getName());
            }
            return null;
        }
    }
    
    

    6.整合SpringBoot

    6.1整合思路:

    思路

    6.2springBoot整合jsp、shiro实战

    • 项目目录结构
    目录结构
    • springBoot配置
    server.port=9999
    server.servlet.context-path=/shiro
    spring.application.name=shiro
    
    spring.mvc.view.prefix=/
    spring.mvc.view.suffix=.jsp
    
    • 所需要的依赖,shiro整合springboot的、springbootweb启动器、jsp解析、jstl
            <!-- 引入解析jsp的解析依赖 -->
            <dependency>
                <groupId>org.apache.tomcat.embed</groupId>
                <artifactId>tomcat-embed-jasper</artifactId>
            </dependency>
    
            <!-- jstl -->
            <dependency>
                <groupId>jstl</groupId>
                <artifactId>jstl</artifactId>
                <version>1.2</version>
            </dependency>
    
            <!-- springBoot整合shiro -->
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring-boot-starter</artifactId>
                <version>1.5.3</version>
            </dependency>
    
    • 设置login.jsp和index.jsp,我们只有在login页面登录后,才有权访问index.jsp页面

    index.jsp

    <!-- 中文乱码解决 -->
    <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <h1>系统首页</h1>
        <ul>
            <li><a href="">订单管理</a> </li>
            <li><a href="">人事管理</a> </li>
            <li><a href="">公告管理</a> </li>
            <li><a href="">工作流管理</a> </li>
        </ul>
            <br><a href="${pageContext.request.contextPath}/user/logout">退出登录</a>
    </body>
    </html>
    

    login.jsp

    <%@page contentType="text/html; UTF-8" pageEncoding="UTF-8" isELIgnored="false" %>
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport"
              content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <h1>登录页面</h1>
        <FORM action="${pageContext.request.contextPath}/user/login" method="post">
            用户名:<input type="text" name="username"><br>
            密  码:<input type="password" name="password"><br>
            <input type="submit" value="登陆">
        </FORM>
    </body>
    </html>
    
    • 设置shiro的配置类
      • 创建shiroFilter
      • 设置安全管理器
      • 创建Realm
    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/18
     * 用来整合shiro框架的相关配置类
     */
    @Configuration
    public class shiroConfig {
    
        // 1、创建shiroFilter,负责拦截所有请求
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    
            // 给filter设置安全管理器
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    
            // 配置受限资源
            Map<String,String> map = new HashMap<String, String>();
            map.put("/user/login","anon"); // 设置公共资源
            map.put("/**","authc");// authc代表请求这个资源需要认证,没有认证则默认跳转login.jsp
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            // 配置认证界面的路径,这里默认就是login.jsp
            shiroFilterFactoryBean.setLoginUrl("/login.jsp");
            // 配置公共资源
    
            return shiroFilterFactoryBean;
        }
    
        // 2、创建安全管理器
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm r){
            DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
            // 给安全管理器设置Realm
            defaultWebSecurityManager.setRealm(r);
    
    
            return defaultWebSecurityManager;
        }
    
        // 3、创建Realm对象
        @Bean
        public Realm getRealm(){
            return new CustomerRealm();
        }
    }
    
    
    • 创建CustomerRealm进行认证和授权
    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/18
     * 自定义Realm
     */
    public class CustomerRealm extends AuthorizingRealm {
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            String user = (String)authenticationToken.getPrincipal();
            if ("szw".equals(user)){
                return new SimpleAuthenticationInfo(user,"123",this.getName());
            }
            return null;
        }
    }
    
    
    • controller
    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/18
     */
    @Controller
    @RequestMapping("user")
    public class UserController {
    
        @RequestMapping("login")
        public String login(String username, String password){
    
            // 获取主体对象
            Subject subject = SecurityUtils.getSubject();
            try{
                subject.login(new UsernamePasswordToken(username,password));
                return "redirect:/index.jsp";
            }catch (UnknownAccountException e){
                e.printStackTrace();
                System.out.println("用户名错误!");
            }catch (IncorrectCredentialsException e){
                e.printStackTrace();
                System.out.println("密码错误!");
            }
            return "redirect:/login.jsp";
        }
    
        @RequestMapping("logout")
        public String logout(){
            SecurityUtils.getSubject().logout(); // 获取对象后,调用logout方法
            return "redirect:/login.jsp";
        }
    }
    
    

    6.3系统中的过滤器

    过滤器
     // 配置受限资源
            Map<String,String> map = new HashMap<String, String>();
            map.put("/index.jsp","authc");// authc代表请求这个资源需要认证,没有认证则默认跳转login.jsp
    // 这里如果使用anon,就不用进行授权和认证
    

    6.4整合mysql,登录与注册

    这里主要贴重要的代码,整个案例放在gitee中

    https://gitee.com/shao_zhao_wei/shiro_web
    

    shiro配置类:

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/18
     * 用来整合shiro框架的相关配置类
     */
    @Configuration
    public class shiroConfig {
    
        // 1、创建shiroFilter,负责拦截所有请求
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
    
            // 给filter设置安全管理器
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
    
            // 配置受限资源
            Map<String,String> map = new HashMap<String, String>();
            // 将注册的页面和登录的页面设置为公共资源
            map.put("/user/login","anon");
            map.put("/user/register","anon");
            map.put("/register.jsp","anon");
            map.put("/**","authc");// authc代表请求这个资源需要认证,没有认证则默认跳转login.jsp
            shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
            // 配置认证界面的路径,这里默认就是login.jsp
            shiroFilterFactoryBean.setLoginUrl("/login.jsp");
            // 配置公共资源
    
            return shiroFilterFactoryBean;
        }
    
        // 2、创建安全管理器
        @Bean
        public DefaultWebSecurityManager getDefaultWebSecurityManager(Realm r){
            DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
            // 给安全管理器设置Realm
            defaultWebSecurityManager.setRealm(r);
    
    
            return defaultWebSecurityManager;
        }
    
        // 3、创建Realm对象
        @Bean
        public Realm getRealm(){
            CustomerRealm realm = new CustomerRealm();
            // 哈希凭证匹配器,我们将加密方式设置到该匹配器汇总
            HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
            credentialsMatcher.setHashAlgorithmName("md5");
            // 设置散列的次数
            credentialsMatcher.setHashIterations(1024);
            // 设置凭证到realm
            realm.setCredentialsMatcher(credentialsMatcher);
            return realm;
        }
    }
    

    Realm

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/18
     * 自定义Realm
     */
    public class CustomerRealm extends AuthorizingRealm {
    
        @Autowired
        private UserDao userDao;
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            return null;
        }
    
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            // 获取用户名
            String principal = (String)authenticationToken.getPrincipal();
            // 根据用户名在数据库中查询用户信息
            User dbUser = userDao.selectUserByName(principal);
            if (null != dbUser)
                return new SimpleAuthenticationInfo(dbUser.getUsername(),
                        dbUser.getPassword(),
                        ByteSource.Util.bytes(dbUser.getSalt()),
                        this.getName());
            return null;
        }
    }
    

    注册的业务层:

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/19
     */
    @Service
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserDao userDao;
    
        @Override
        public void register(User user) {
            // 获取自定义随机盐,这里有一个自定义获取随机字符串的工具类
            String salt = SaltUtils.getSalt(6);
            user.setSalt(salt);
            // 铭文密码需要处理成md5+salt+hash散列,这里配置后一定要在realm中进行配置
            Md5Hash md5Hash = new Md5Hash(user.getPassword(),salt,1024);
            // set处理后的密码
            user.setPassword(md5Hash.toHex());
    
            // 持久化到数据库中
            userDao.saveUser(user);
        }
    }
    

    6.5授权的实现

    由于视频讲解的该部分是jsp的标签形式的实现,所以这里只截图。

    按角色授权Realm中:

    授权realm

    jsp页面(需要加shiro的头标签)

    <%@taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>  
    
    jsp标签形式

    按资源形式授权的Realm:

    [图片上传失败...(image-d47804-1603278030113)]

    jsp页面(需要加shiro的头标签)

    jsp

    基于注解形式的授权认证

    @RequiresRoles("admin") // 用来判断角色
    @RequestMapper("save")
    public String save(){
        return;
    }
    
    @RequiresRoles(values = {"admin","user"}) // 同时具有admin和user两个角色
    @RequestMapper("save")
    public String save(){
        return;
    }
    
    @RequiresPermissions("user:update:01") // 用来判断权限字符串
    @RequestMapper("save")
    public String save(){
        return;
    }
    

    6.6shiro授权集成mysql

    数据库设计

    一个用户可以绑定多个角色,一个角色有多个用户,多对多。

    一个角色可以有多个权限,一个权限也可以被多个角色拥有,多对多。

    权限和资源是一一对应的关系。


    db

    7.缓存

    7.1缓存的作用

    • Cache缓存,计算机内存中一段数据,好比计算机的内存条

    • 作用:减轻DB的访问压力,从而提高系统的查询效率

      就我们目前没有缓存的系统而言,我们可以开启mybatis的日志,我们可以发现,每当我们刷新一次页面,就进行访问db,这样数据库的压力会很大。

      [图片上传失败...(image-69a1e9-1603278030113)]

    7.2在shiro中集成EhCache缓存

    在shiro中提供了一个CacheManager接口去完成自定义缓存.

    缓存概念:就是内存的上的数据。有什么特点?不同与数据库,我们之前的db都是存放在硬盘上的,之前都是程序与数据库简历一个连接,然后通过连接去获取db'的数据,这里是通过io的形式加载到了应用程式的内存中,然后再把相应给用户。

    然而现在,我们可以直接利用缓存,放到内存中,这样就不用建立连接,提高了查询效率。但是我们系统中的内存,都是有限的,所以说我们日后需要区别哪写资源放到内存中,应该是查询多,增删改少的资源,放到缓存中。

    shiro集成EhCache步骤:

    • 1、项目中引入EhCache的依赖
    <!-- 这里的ehcache的版本号应与shiro保持一致 -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-ehcache</artifactId>
        <version>1.4.0</version>
    </dependency>
    
    
    • 2、我们知道realm是做数据的调配和认证授权,我们想开启缓存,所以就需要告知realm中设置eache的实现,所以在shiro.config文件中设置缓存开启。
    • 3、我们可以查看当前被继承的CustomerRealm,最上面继承了CacheRealm,这个就是做缓存处理的customer.setCacheManager(new CacheRealm),然后设置开启缓存,再分别开启授权和认证的缓存。
            // 开启缓存管理
            realm.setCacheManager(new EhCacheManager());
            // 开启全局缓存
            realm.setCachingEnabled(true);
            // 开启认证缓存
            realm.setAuthenticationCachingEnabled(true);
            // 开启授权缓存
            realm.setAuthorizationCachingEnabled(true);
    

    这里开个题外话,ehcache和redis两种缓存有何区别?

    • ehcache直接在jvm虚拟机中缓存,速度快,效率高;但是缓存共享麻烦,集群分布式应用不方便。

    • redis是通过socket访问到缓存服务,效率比ecache低,比数据库要快很多,处理集群和分布式缓存方便,有成熟的方案。如果是单个应用或者对缓存访问要求很高的应用,用ehcache。如果是大型系统,存在缓存共享、分布式部署、缓存内容很大的,建议用redis。

    7.3集成Redis缓存

    1、首先需要我们配置相关的redis的配置(ip地址、端口号),依赖,开启redis的服务端。

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
        <version>2.3.4.RELEASE</version>
    </dependency>
    

    2、我们想要集成Redis,那么在开启缓存管理的时候,就需要将RedisCacheManager对象new一个放进去,并且这个类是需要我们自己实现的。

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/21
     * 自定义缓存管理器
     */
    public class RedisCacheManager implements CacheManager {
        @Override
        public <K, V> Cache<K, V> getCache(String cacheName) throws CacheException {
            return new RedisCache<K,V>();
        }
    }
    

    3、我们发现,自定义管理器中的getCache方法范湖了Cache类型的数据,我们需要去实现这个接口;

    这里主要实现了两个get和put方法。

    /**
     * @author szw<szw0814 @ 1 6 3 . com> 2020/10/21
     * 自定义redis的缓存
     */
    public class RedisCache<k,v> implements Cache<k,v> {
    
        @Autowired
        RedisTemplate redisTemplate;
    
        private String cacheName;
    
        public RedisCache() {
        }
    
        public RedisCache(String cacheName) {
            this.cacheName = cacheName;
        }
    
        @Override
        public v get(k k) throws CacheException {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());// 哈希模型可以让我们以对象的形式查看数据
            return (v) redisTemplate.opsForHash().get(this.cacheName,k.toString());
        }
    
        @Override
        public v put(k k, v v) throws CacheException {
            redisTemplate.setKeySerializer(new StringRedisSerializer());
            redisTemplate.setHashKeySerializer(new StringRedisSerializer());
            redisTemplate.opsForHash().put(this.cacheName,k.toString(),v);
            return null;
        }
    
        @Override
        public v remove(k k) throws CacheException {
            return null;
        }
    
        @Override
        public void clear() throws CacheException {
    
        }
    
        @Override
        public int size() {
            return 0;
        }
    
        @Override
        public Set<k> keys() {
            return null;
        }
    
        @Override
        public Collection<v> values() {
            return null;
        }
    }
    

    相关文章

      网友评论

          本文标题:Shiro

          本文链接:https://www.haomeiwen.com/subject/wgvemktx.html