安全框架Shiro的简单学习

文章目录

Shiro

1、什么是 Shiro

  • Apache Shiro 是一个 java 的安全(权限)框架

  • Shiro 可以非常容易的开发出足够好的应用,其不仅可以用在 JavaSE 环境,也可以用在 JavaEE 环境

  • Shiro 可以完成,认证,授权,加密,会话管理,Web 集成,缓存等等

  • Shiro 官方地址:http://shiro.apache.org/

  • 10 Minute Tutorial on Apache Shiro:http://shiro.apache.org/10-minute-tutorial.html

2、功能介绍

在这里插入图片描述

  • Authentication:身份认证、登录,验证用户是不是拥有相应的身份
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限,即判断用户能否进行什么操作,如:验证某个用户是否拥有某个角色,或者细粒度的验证某个用户对某个资源是否具有某个权限
  • Session Manager:会话管理,即用户登录后就是第一次会话,在没有退出之前,它的所有信息都会存放在会话中,会话可以是普通的 JavaSE 环境,也可以是 Web 环境
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库中,而不是明文存储
  • Web Support:Web 支持,可以非常容易的集成到 Web 环境
  • Caching:缓存,比如用户登录后,其用户信息,拥有的角色、权限不必每次去查,这样可以提高效率
  • Concurrency:Shiro 支持多线程应用的并发验证,即:如在一个线程开启另一个线程,能把权限自动的传播过去
  • Testing:提供测试支持
  • Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问
  • Remember Me:记住我功能,这是一个非常常见的功能,一次登录后,只要不注销,下次打开的话就不用登录

3、Shiro 架构

3.1 Shiro 外部架构

从外部来看 Shiro,即从应用程序角度来观察如何使用 食肉 完成工作:

在这里插入图片描述

  • subject:应用代码直接交互的对象是 Subject,也就是说 Shiro 的对外 API 核心就是 Subject,Subject 代表了当前的用户,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是 Subject,如网络爬虫,机器人等等,与 Subject 的所有交互都会委托给 SecurityManager;Subject 其实就是一个门面,SecurityManager 才是实际的执行者
  • SecurityManager:安全管理器,即所有与安全有关的操作都会与 SecurityManager 加护,并且它管理者所有的 Subject,可以看出它是 Shiro 的核心,它负责与 Shiro 的其他组件进行交互,它相当于 SpringMVC 的 DispatcherServlet 的角色
  • Realm:Shiro 从 Realm 获取安全数据(如用户,角色,权限),就是说 SecurityManager 要验证用户身份,那么他需要从 Realm 获取相应的用户进行比较,来确定用户的身份是否合法;也需要从 Realm 得到用户相应的角色、权限,进行验证用户的操作是否能够进行,可以吧 Realm 看成 DataSource

3.2 Shiro 内部架构

在这里插入图片描述

  • Subject:任何可以与用户交互的 ‘用户‘
  • Security Manager:相当于 SpringMVC 中的 DispatcherServlet;是 Shiro 的心脏,所有具体的交互都通过 SecurityManager 进行控制,他管理着所有的 Subject,且负责进行认证,授权,会话,以及缓存的管理
  • Authentication:负责 Subject 认证,是一个扩展点,可以自定义实现,可以使用认证策略(AuthenticationStrategy),即什么情况下算用户认证通过了
  • Authorizer:授权器,即访问控制器,用来决定主体是否有权进行相应的操作;即控制着用户访问应用中的哪些功能
  • Realm:可以有一个或者多个的 Realm,可以认为是安全实体数据源,即用户获取安全实体,可以用 JDBC 实现,也可以提取内存实现等等;由用户提供,所以一般在应用中都需要实现自己的 Realm
  • SessionManager:管理 Session 生命周期的组件,而 Shiro 并不仅仅可以用在 Web 环境,也可以用在普通的 JavaSE 环境中
  • CacheManager:缓存控制器,来管理如用户,角色,权限等缓存,因为这些数据基本很少改变,放到缓存中后可以提高访问的性能
  • Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于密码加密,以及解密等等

4、Hello,Shiro

4.1 快速实践

官方文档:http://shiro.apache.org/tutorial.html

官方 quickstart:https://github.com/apache/shiro/tree/main/samples/quickstart

快速搭建步骤:

  1. 创建一个 Maven 父工程,用于学习 Shiro,删掉 src

  2. 创建一个普通的 Maven 项目

    在这里插入图片描述

  3. 根据官方文档,导入 shiro 依赖

    • 如果没有导入 log4j,那么就会默认使用 commons-logging

      org.apache.shiro shiro-core 1.8.0 org.slf4j jcl-over-slf4j 2.0.0-alpha5 org.slf4j slf4j-log4j12 2.0.0-alpha5 test log4j log4j 1.2.17
  4. 在 resource 目录下,创建并将官网的内容编写入 log4j 的配置文件

    log4j.rootLogger=INFO, stdout
    
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n
    
    # General Apache libraries
    log4j.logger.org.apache=WARN
    
    # Spring
    log4j.logger.org.springframework=WARN
    
    # Default Shiro logging
    log4j.logger.org.apache.shiro=INFO
    
    # Disable verbose logging
    log4j.logger.org.apache.shiro.util.ThreadContext=WARN
    log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
    
  5. 在 resource 目录下,创建并将官网的内容编写入 shiro.Ini 文件

    • 如果使用 IDEA 需要下载并安转 Ini 插件,否则看着不爽

      [users]

      user ‘root’ with password ‘secret’ and the ‘admin’ role

      root = secret, admin

      user ‘guest’ with the password ‘guest’ and the ‘guest’ role

      guest = guest, guest

      user ‘presidentskroob’ with password ‘12345’ ("That’s the same combination on

      my luggage!!!" 😉), and role ‘president’

      presidentskroob = 12345, president

      user ‘darkhelmet’ with password ‘ludicrousspeed’ and roles ‘darklord’ and ‘schwartz’

      darkhelmet = ludicrousspeed, darklord, schwartz

      user ‘lonestarr’ with password ‘vespa’ and roles ‘goodguy’ and ‘schwartz’

      lonestarr = vespa, goodguy, schwartz

      -----------------------------------------------------------------------------

      Roles with assigned permissions

      Each line conforms to the format defined in the

      org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc

      -----------------------------------------------------------------------------

      [roles]

      ‘admin’ role has all permissions, indicated by the wildcard ‘*’

      admin = *

      The ‘schwartz’ role can do anything (*) with any lightsaber:

      schwartz = lightsaber:*

      The ‘goodguy’ role is allowed to ‘drive’ (action) the winnebago (type) with

      license plate ‘eagle5’ (instance specific id)

      goodguy = winnebago:drive:eagle5

  6. 在 java 文件夹下,将官网的快速搭建粘入

    • 注意:新版中官方已经有更好的方法替代了之前的方法

      // 旧
      //import org.apache.shiro.ini.IniSecurityManagerFactory;
      //import org.apache.shiro.lang.util.Factory;
      
      // 新
      import org.apache.shiro.mgt.DefaultSecurityManager; // new
      import org.apache.shiro.realm.text.IniRealm; // new
      
      
      // 旧方法
      //Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
      //SecurityManager securityManager = factory.getInstance();
      
      // 新方法
      DefaultSecurityManager securityManager = new DefaultSecurityManager();
      IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
      securityManager.setRealm(iniRealm);
      

      /*

      • Licensed to the Apache Software Foundation (ASF) under one
      • or more contributor license agreements. See the NOTICE file
      • distributed with this work for additional information
      • regarding copyright ownership. The ASF licenses this file
      • to you under the Apache License, Version 2.0 (the
      • “License”); you may not use this file except in compliance
      • with the License. You may obtain a copy of the License at
      • http://www.apache.org/licenses/LICENSE-2.0
        
      • Unless required by applicable law or agreed to in writing,
      • software distributed under the License is distributed on an
      • “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
      • KIND, either express or implied. See the License for the
      • specific language governing permissions and limitations
      • under the License.
        */

      import org.apache.shiro.SecurityUtils;
      import org.apache.shiro.authc.*;
      //import org.apache.shiro.ini.IniSecurityManagerFactory;
      import org.apache.shiro.mgt.DefaultSecurityManager; // new
      import org.apache.shiro.realm.text.IniRealm; // new
      import org.apache.shiro.mgt.SecurityManager;

      import org.apache.shiro.session.Session;
      import org.apache.shiro.subject.Subject;
      //import org.apache.shiro.lang.util.Factory;
      import org.slf4j.Logger;
      import org.slf4j.LoggerFactory;

      /**

      • Simple Quickstart application showing how to use Shiro’s API.

      • @since 0.9 RC2
        */
        public class Quickstart {

        private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);

        public static void main(String[] args) {

         // The easiest way to create a Shiro SecurityManager with configured
         // realms, users, roles and permissions is to use the simple INI config.
         // We'll do that by using a factory that can ingest a .ini file and
         // return a SecurityManager instance:
        
         // Use the shiro.ini file at the root of the classpath
         // (file: and url: prefixes load from files and urls respectively):
        
         // 旧方法
         //Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
         //SecurityManager securityManager = factory.getInstance();
         // 新方法
         DefaultSecurityManager securityManager = new DefaultSecurityManager();
         IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
         securityManager.setRealm(iniRealm);
        
         // for this simple example quickstart, make the SecurityManager
         // accessible as a JVM singleton.  Most applications wouldn't do this
         // and instead rely on their container configuration or web.xml for
         // webapps.  That is outside the scope of this simple quickstart, so
         // we'll just do the bare minimum so you can continue to get a feel
         // for things.
         SecurityUtils.setSecurityManager(securityManager);
        
         // Now that a simple Shiro environment is set up, let's see what you can do:
        
         // get the currently executing user:
         Subject currentUser = SecurityUtils.getSubject();
        
         // Do some stuff with a Session (no need for a web or EJB container!!!)
         Session session = currentUser.getSession();
         session.setAttribute("someKey", "aValue");
         String value = (String) session.getAttribute("someKey");
         if (value.equals("aValue")) {
             log.info("Retrieved the correct value! [" + value + "]");
         }
        
         // let's login the current user so we can check against roles and permissions:
         if (!currentUser.isAuthenticated()) {
             UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
             token.setRememberMe(true);
             try {
                 currentUser.login(token);
             } catch (UnknownAccountException uae) {
                 log.info("There is no user with username of " + token.getPrincipal());
             } catch (IncorrectCredentialsException ice) {
                 log.info("Password for account " + token.getPrincipal() + " was incorrect!");
             } catch (LockedAccountException lae) {
                 log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                         "Please contact your administrator to unlock it.");
             }
             // ... catch more exceptions here (maybe custom ones specific to your application?
             catch (AuthenticationException ae) {
                 //unexpected condition?  error?
             }
         }
        
         //say who they are:
         //print their identifying principal (in this case, a username):
         log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
        
         //test a role:
         if (currentUser.hasRole("schwartz")) {
             log.info("May the Schwartz be with you!");
         } else {
             log.info("Hello, mere mortal.");
         }
        
         //test a typed permission (not instance-level)
         if (currentUser.isPermitted("lightsaber:wield")) {
             log.info("You may use a lightsaber ring.  Use it wisely.");
         } else {
             log.info("Sorry, lightsaber rings are for schwartz masters only.");
         }
        
         //a (very powerful) Instance Level permission:
         if (currentUser.isPermitted("winnebago:drive:eagle5")) {
             log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                     "Here are the keys - have fun!");
         } else {
             log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
         }
        
         //all done - log out!
         currentUser.logout();
        
         System.exit(0);
        

        }
        }

  7. 启动,发现打印了日志信息

    在这里插入图片描述

  8. 表明,quickstart 完成

问题

  • 运行时报错

    在这里插入图片描述

  • 问题原因以及解决办法

    在这里插入图片描述

5、Shiro 的 Subject 分析

  1. 获取当前的用户对象 Subject

    Subject currentUser = SecurityUtils.getSubject();
    
  2. 通过当前用户拿到 session,并在 session 中设置至,以及获取值

    Session session = currentUser.getSession();
    session.setAttribute("someKey", "aValue");
    String value = (String) session.getAttribute("someKey");
    if (value.equals("aValue")) {
        log.info("Retrieved the correct value! [" + value + "]");
    }
    
  3. 判断当前用户是否被认证

    if (!currentUser.isAuthenticated()) {
        // Token:令牌
        UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
        // 设置记住我
        token.setRememberMe(true);
        try {
            // 执行登录操作
            currentUser.login(token);
        } catch (UnknownAccountException uae) { // 用户名错误
            log.info("There is no user with username of " + token.getPrincipal());
        } catch (IncorrectCredentialsException ice) { // 密码错误
            log.info("Password for account " + token.getPrincipal() + " was incorrect!");
        } catch (LockedAccountException lae) { // 多次用户密码错误,就会被锁定
            log.info("The account for username " + token.getPrincipal() + " is locked.  " +
                     "Please contact your administrator to unlock it.");
        }
        // ... catch more exceptions here (maybe custom ones specific to your application?
        catch (AuthenticationException ae) { // 认证是否成功
            //unexpected condition?  error?
        }
    }
    
  4. 获取当前用户认证

    log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
    
  5. 判断当前是否拥此角色

    • 角色都在 shiro.Ini 中配置

      if (currentUser.hasRole(“schwartz”)) {
      log.info(“May the Schwartz be with you!”);
      } else {
      log.info(“Hello, mere mortal.”);
      }

  6. 检测拥有什么权限

    • 权限也是在 shiro.Ini 中配置

    • 粗粒度

      if (currentUser.isPermitted("lightsaber:wield")) {
          log.info("You may use a lightsaber ring.  Use it wisely.");
      } else {
          log.info("Sorry, lightsaber rings are for schwartz masters only.");
      }
      
    • 细粒度

      if (currentUser.isPermitted("winnebago:drive:eagle5")) {
          log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'.  " +
                   "Here are the keys - have fun!");
      } else {
          log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
      }
      
  7. 注销

    //all done - log out!
    currentUser.logout();
    
  8. 结束

    System.exit(0);
    

6、集成 SpringBoot

6.1 搭建环境

在这里插入图片描述

  1. 创建一个新的 springboot 项目,导入 spring web 依赖

  2. 导入 thymeleaf 依赖

    <!--thymeleaf-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    
  3. 创建 config 目录,编写 Shiro 配置文件:ShiroConfig

    • 最好从后往前写

    • 通常 ShiroFilterFactoryBean 实现请求过滤,而 UserRealm 来进行真正的权限过滤

      package com.aze.config;

      import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
      import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
      import org.springframework.beans.factory.annotation.Qualifier;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;

      @Configuration
      public class ShiroConfig {

      // 3. ShiroFilterFactoryBean
      @Bean
      public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
          ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
          // 设置安全管理器
          bean.setSecurityManager(defaultWebSecurityManager);
          return bean;
      }
      
      // 2. DefaultWebSecurityManager
      @Bean(name = "securityManager")
      public DefaultWebSecurityManager getDDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
          DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
          // 关联 UserRealm
          securityManager.setRealm(userRealm);
          return securityManager;
      }
      
      // 1. 创建 realm 对象,需要自定义类
      @Bean(name = "userRealm")
      public UserRealm userRealm(){
          return new UserRealm();
      }
      

      }

  4. 自定义 UserReaml,需要继承 AuthorizingRealm

    package com.aze.config;
    
    import org.apache.shiro.authc.AuthenticationException;
    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    // 自定义 UserReaml 需要继承 AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了=>授权 doGetAuthorizationInfo");
            return null;
        }
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("执行了=>认证 doGetAuthenticationInfo");
            return null;
        }
    }
    
  5. 编写前端的测试页面

    • index

      <!DOCTYPE html>
      <html lang="en" xmlns:th="http://www.thymeleaf.org">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      
      <h1>首页</h1>
      <p th:text="${msg}"></p>
      <hr>
      
      <a th:href="@{/user/add}">add</a> | <a th:href="@{/user/update}">update</a>
      
      </body>
      </html>
      
    • add

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      
      <h1>add</h1>
      
      </body>
      </html>
      
    • update

      <!DOCTYPE html>
      <html lang="en">
      <head>
          <meta charset="UTF-8">
          <title>Title</title>
      </head>
      <body>
      
      <h1>update</h1>
      
      </body>
      </html>
      
  6. 创建 controller 目录,编写 MyController

    package com.aze.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class MyController {
    
        @RequestMapping({"/","/index","/index.html"})
        public String toIndex(Model model){
            model.addAttribute("msg","hello,shiro!");
            return "index";
        }
    
        @RequestMapping("/user/add")
        public String add(){
            return "/user/add";
        }
    
        @RequestMapping("/user/update")
        public String update(){
            return "/user/update";
        }
    }
    
  7. 启动项目,进行测试

    在这里插入图片描述

    在这里插入图片描述

    在这里插入图片描述

  8. 环境搭建成功

6.2 登陆拦截

  1. 编写登录页面 login.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div>
        <h1>登录</h1>
        <br>
        <form action="">
            账号:<input type="text" name="username"><br>
            密码:<input type="password" name="password"><br>
            <input type="submit">
        </form>
    </div>
    
    </body>
    </html>
    
  2. 在 MyController 中添加登录的跳转

    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }
    
  3. shiro 内置过滤器

    • anon:不需要认证就可以访问
    • authc:必须要认证了才可以访问
    • user:必须拥有“记住我”功能才可以使用
    • perms:拥有对某个资源的权限才可以访问
    • role:拥有某个角色的权限才可以访问
  4. 在 shiro 配置文件中,添加登录拦截的相关过滤器

    // 添加 shiro 的内置过滤器
    Map<String, String> filterMap = new LinkedHashMap<>();
    // 将 /user 下的所有路径都需要登录认证
    filterMap.put("/user/*","authc");
    bean.setFilterChainDefinitionMap(filterMap);
    // 设置登录请求,如果没通过认证就跳转至登录页面
    bean.setLoginUrl("/toLogin");
    return bean;
    

    在这里插入图片描述

  5. 测试

6.3 用户认证

  1. 在 MyController 中添加登录请求

    @RequestMapping("/login")
    public String login(String username,String password,Model model){
        // 获取当前用户
        Subject subject = SecurityUtils.getSubject();
        // 封装用户的登录数据
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        try {
            // 执行登录方法
            subject.login(token);
            return "index";
        }catch (UnknownAccountException e){ // 如果用户名不存在
            model.addAttribute("msg","用户名错误");
            return "login";
        }catch (IncorrectCredentialsException e){ // 密码错误
            model.addAttribute("msg","密码错误");
            return "login";
        }
    }
    
  2. 修改登录页面代码

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <div>
        <h1>登录</h1>
        <hr>
        <p th:text="${msg}" style="color: red"></p>
        <form th:action="@{/login}">
            账号:<input type="text" name="username"><br>
            密码:<input type="password" name="password"><br>
            <input type="submit">
        </form>
    </div>
    
    </body>
    </html>
    
  3. 编写 UserRealm,对用户和密码进行认证

    package com.aze.config;
    
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    
    // 自定义 UserReaml 需要继承 AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了=>授权 doGetAuthorizationInfo");
            return null;
        }
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("执行了=>认证 doGetAuthenticationInfo");
            // 用户名,密码
            String username = "admin";
            String password = "123456";
    
            UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
    
            if (!userToken.getUsername().equals(username)){
                // 如果用户名不正确抛出异常 UnknownAccountException
                return null;
            }
            // 密码认证,shiro 会自动帮你做了
            return new SimpleAuthenticationInfo("",password,"");
        }
    }
    
  4. 进行测试

    • 点击 add 或者 update 就会跳转至登录页面

    • 用户名错误

      在这里插入图片描述

    • 密码错误

      在这里插入图片描述

    • 用户名密码都正确,就会跳转首页,并且 add 和 update 都能使用

      在这里插入图片描述

6.4 整合 MyBatis

在这里插入图片描述

  1. 导入相关依赖

    <!--数据库连接-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!--log4j-->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
    <!--druid 数据源--><dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
    <!--引入 mybatis,这是 MyBatis 官方提供的,适配于 SpringBoot,而不是 SpringBoot 自己做的-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    <!--lombok-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
    </dependency>
    
  2. 编写 mybatis 配置文件(applicatrion.yaml)

    spring:
      datasource:
        username: root
        password: root
        # 有可能会报错,需要设置时区,没报错不用管,设置时区:serverTimezone=UTC
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
        driver-class-name: com.mysql.cj.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource
        #Spring Boot 默认是不注入这些属性值的,需要自己绑定
        #druid 数据源专有配置
        initialSize: 5
        minIdle: 5
        maxActive: 20
        maxWait: 60000
        timeBetweenEvictionRunsMillis: 60000
        minEvictableIdleTimeMillis: 300000
        validationQuery: SELECT 1 FROM DUAL
        testWhileIdle: true
        testOnBorrow: false
        testOnReturn: false
        poolPreparedStatements: true
    
        #配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
        #如果允许时报错  java.lang.ClassNotFoundException: org.apache.log4j.Priority
        #则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
        filters: stat,wall,log4j
        maxPoolPreparedStatementPerConnectionSize: 20
        useGlobalDataSourceStat: true
        connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
    
    # 整合 MyBatis
    mybatis:
      type-aliases-package: com.aze.pojo
      mapper-locations: classpath:mapper/*.xml
    
  3. 测试类中测试一下连接是否成功

    package com.aze;
    
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;
    
    import javax.sql.DataSource;
    import java.sql.SQLException;
    
    @SpringBootTest
    class Shiro02SpringbootApplicationTests {
    
        @Autowired
        DataSource dataSource;
    
        @Test
        void contextLoads() throws SQLException {
            System.out.println(dataSource.getConnection());
        }
    
  4. 连接成功即可进行下一步,编写 pojo 实体类

    package com.aze.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
        private Integer id;
        private String name;
        private String pwd;
    
    }
    
  5. 编写 UserMapper

    package com.aze.mapper;
    
    import com.aze.pojo.User;
    import org.apache.ibatis.annotations.Mapper;
    import org.springframework.stereotype.Repository;
    
    @Repository
    @Mapper
    public interface UserMapper {
    
        public User queryUserByName(String name);
    
    }
    
  6. 编写 UserMapper 的配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.aze.mapper.UserMapper">
    
        <select id="queryUserByName" parameterType="String" resultType="User">
            select * from mybatis.user where name=#{name};
        </select>
    
    </mapper>
    
  7. 编写 UserService

    package com.aze.service;
    
    import com.aze.pojo.User;
    
    public interface UserService {
    
        public User queryUserByName(String name);
    
    }
    
  8. 编写 UserService 的实现类 UserServiceImpl

    package com.aze.service;
    
    import com.aze.mapper.UserMapper;
    import com.aze.pojo.User;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    // 这里用 @Service 是一样的
    @Component
    public class UserServiceImpl implements UserService {
    
        @Autowired
        UserMapper userMapper;
    
        @Override
        public User queryUserByName(String name) {
            return userMapper.queryUserByName(name);
        }
    }
    
  9. 修改 UserRealm

    package com.aze.config;
    
    import com.aze.pojo.User;
    import com.aze.service.UserService;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.springframework.beans.factory.annotation.Autowired;
    
    // 自定义 UserReaml 需要继承 AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        UserService userService;
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了=>授权 doGetAuthorizationInfo");
            return null;
        }
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("执行了=>认证 doGetAuthenticationInfo");
    
            UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
    
            // 连接数据库
            User user = userService.queryUserByName(userToken.getUsername());
            if (user == null){
                // 如果没有这个用户,则报出异常 UnknownAccountException
                return null;
            }
    
            // 密码认证,shiro 会自动帮你做了
            return new SimpleAuthenticationInfo("",user.getPwd(),"");
        }
    }
    
  10. 编写完成后再测试类中进行测试,看看是否能从数据库中取得相对应的用户数据

    package com.aze;

    import com.aze.service.UserServiceImpl;
    import org.junit.jupiter.api.Test;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.boot.test.context.SpringBootTest;

    import javax.sql.DataSource;
    import java.sql.SQLException;

    @SpringBootTest
    class Shiro02SpringbootApplicationTests {

       @Autowired
       UserServiceImpl userService;
    
       @Autowired
       DataSource dataSource;
    
       @Test
       void contextLoads() throws SQLException {
           System.out.println(dataSource.getConnection());
           System.out.println(userService.queryUserByName("张三"));
       }
    

    }

  11. 成功

![在这里插入图片描述](https://img-blog.csdnimg.cn/435b83e926d1431bb3144883f391c5c3.png#pic_center)
  1. 测试成功,在网页上再次进行测试

  2. 都成功了

  3. 下班!

6.5 请求授权

  1. 修改 shiro 的配置文件

    package com.aze.config;
    
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    @Configuration
    public class ShiroConfig {
    
        // 3. ShiroFilterFactoryBean
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            // 设置安全管理器
            bean.setSecurityManager(defaultWebSecurityManager);
            // 添加 shiro 的内置过滤器
            /* anon:不需要认证就可以访问
             * authc:必须要认证了才可以访问
             * user:必须拥有“记住我”功能才可以使用
             * perms:拥有对某个资源的权限才可以访问
             * role:拥有某个角色的权限才可以访问
             * */
            Map<String, String> filterMap = new LinkedHashMap<>();
    
            // 授权,正常情况下,没有授权会跳转到未授权页面
            filterMap.put("/user/add","perms[user:add]");
            filterMap.put("/user/update","perms[user:update]");
    
            //filterMap.put("/user/add","authc");
            //filterMap.put("/user/update","authc");
            filterMap.put("/user/*","authc");
            bean.setFilterChainDefinitionMap(filterMap);
    
            // 设置登录请求
            bean.setLoginUrl("/toLogin");
            // 设置未授权页面
            bean.setUnauthorizedUrl("/noauth");
            return bean;
        }
    
        // 2. DefaultWebSecurityManager
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            // 关联 UserRealm
            securityManager.setRealm(userRealm);
            return securityManager;
        }
    
        // 1. 创建 realm 对象,需要自定义类
        @Bean(name = "userRealm")
        public UserRealm userRealm(){
            return new UserRealm();
        }
    
    }
    

    在这里插入图片描述

  2. 在 MyController 中添加未授权页面的请求

    @RequestMapping("/noauth")
    @ResponseBody
    public String unauthorized(){
        return "未经授权无法访问此页面!";
    }
    
  3. 编写用户的授权(UserRealm)

    package com.aze.config;
    
    import com.aze.pojo.User;
    import com.aze.service.UserService;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.authz.AuthorizationInfo;
    import org.apache.shiro.authz.SimpleAuthorizationInfo;
    import org.apache.shiro.realm.AuthorizingRealm;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.subject.Subject;
    import org.springframework.beans.factory.annotation.Autowired;
    
    // 自定义 UserReaml 需要继承 AuthorizingRealm
    public class UserRealm extends AuthorizingRealm {
    
        @Autowired
        UserService userService;
    
        // 授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了=>授权 doGetAuthorizationInfo");
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            //info.addStringPermission("user:add");
    
            // 取得当前登录的对象
            Subject subject = SecurityUtils.getSubject();
            User currentUser = (User) subject.getPrincipal();
    
            info.addStringPermission(currentUser.getPerms());
    
            return info;
        }
        // 认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
            System.out.println("执行了=>认证 doGetAuthenticationInfo");
    
            UsernamePasswordToken userToken = (UsernamePasswordToken) authenticationToken;
    
            // 连接数据库
            User user = userService.queryUserByName(userToken.getUsername());
            if (user == null){
                // 如果没有这个用户,则报出异常 UnknownAccountException
                return null;
            }
    
            // 密码认证,shiro 会自动帮你做了
            return new SimpleAuthenticationInfo(user,user.getPwd(),"");
        }
    }
    

    在这里插入图片描述

  4. 修改数据库,增添权限

    在这里插入图片描述

  5. 修改实体类

    package com.aze.pojo;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public class User {
    
        private Integer id;
        private String name;
        private String pwd;
        // 增添
        private String perms;
    
    }
    
  6. 测试不同用户权限

6.6 整合 Thymeleaf

标签

说明

shiro:guest

用户没有身份验证时显示相应信息,即游客访问信息

shiro:user

用户已经身份验证/记住我登录后显示相应的信息

shiro:authenticated

用户已经身份验证通过,即Subject.login登录成功,不是记住我登录的

shiro:notAuthenticated

用户已经身份验证通过,即没有调用Subject.login进行登录,包括记住我自动登录的也属于未进行身份验证

shiro: principal

相当于((User)Subject.getPrincipals()).getUsername()

shiro:lacksPermission

如果当前Subject没有权限将显示body体内容

shiro:hasRole

如果当前Subject有角色将显示body体内容

shiro:hasAnyRoles

如果当前Subject有任意一个角色(或的关系)将显示body体内容

shiro:lacksRole

如果当前Subject没有角色将显示body体内容

shiro:hasPermission

是否拥有此权限 如果当前Subject有权限将显示body体内容

  1. 导入 thymeleaf-extras-shiro 依赖

    <!--thymeleaf-shiro整合-->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.1.0</version>
    </dependency>
    
  2. 在 shiro 配置文件(ShiroConfig)中,增添 shiro 与 thymeleaf 的整合配置

    // 整合 shiroDialect:用来整合 shiro 与 thymeleaf
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
    
  3. 修改首页前端代码

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
                    xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    
    <h1>首页</h1>
    <p>
        <!--shiro:guest    用户没有身份验证时显示的信息 -->
        <a th:href="@{/toLogin}" shiro:guest>登录</a>
        <!--shiro:authenticated   用户已经身份验证通过,即Subject.login登录成功,但不是记住我登录的  -->
        <a th:href="@{/logout}" shiro:authenticated>注销</a>
    </p>
    <p th:text="${msg}"></p>
    <hr>
    <div shiro:hasPermission="'user:add'">
        <a th:href="@{/user/add}">add</a>
    </div>
    <div shiro:hasPermission="'user:update'">
        <a th:href="@{/user/update}">update</a>
    </div>
    
    </body>
    </html>
    

    在这里插入图片描述

  4. 启动项目进行测试

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
学习Shiro框架可以按照以下步骤进行: 1. 了解基础概念:首先,你需要了解Shiro的基本概念和术语,例如主体(Subject)、认证(Authentication)、授权(Authorization)、Realm等。可以阅读Shiro的官方文档或者相关的教程来获得这些知识。 2. 安装和配置:安装Shiro框架并进行基本的配置。你可以在Shiro的官方网站上找到安装指南和配置示例。 3. 认证功能:学习如何使用Shiro进行用户认证,包括用户名密码认证、Remember Me功能、多Realm认证等。可以尝试编写简单的认证示例来理解这些功能。 4. 授权功能:学习如何使用Shiro进行用户授权,包括角色授权和权限授权。了解如何定义角色和权限,并且如何在代码中进行授权判断。 5. Session管理:了解Shiro如何管理用户的会话信息,包括会话超时、会话验证等。学习如何使用Shiro提供的Session API来管理会话。 6. 整合框架:如果你使用其他的Java框架,例如Spring或者Spring Boot,学习如何将Shiro与这些框架进行整合,以便更好地利用Shiro的功能。 7. 安全性优化:深入了解Shiro安全性能优化技巧,例如密码加密、安全配置、防止常见安全漏洞等。 8. 实战练习:通过编写实际的应用程序来巩固所学的知识。可以尝试开发一个简单的Web应用程序,使用Shiro进行用户认证和授权。 除了官方文档和教程,还可以参考一些优秀的书籍或在线教程,例如《Apache Shiro官方指南》、《深入浅出Shiro安全框架》等。此外,加入Shiro的社区或者论坛,与其他开发者交流经验也是一个很好的学习方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值