SpringBoot 04 —— Shiro

系列文章

SpringBoot 01 —— HelloSpringBoot、yaml配置、数据校验、多环境切换
SpringBoot 02 —— Web简单探究、员工管理系统
SpringBoot 03 —— Spring Security
SpringBoot 04 —— Shiro



十二、Shiro

12.1、介绍

  • Apache Shiro 是一个Java的安全(权限)框架
  • Shiro可以非常容易的开发出足够好的应用,它不仅可以用在JavaSE环境,也可以用在JavaEE环境。
  • Shiro可以完成认证、授权、加密、会话管理、Web集成、缓存等功能。

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

GitHub:https://github.com/apache/shiro

 
详细功能:

在这里插入图片描述

  • Authentication(验证):进行身份验证、判断能否登录以及验证用户是否拥有相应权限
  • Authorization(授权):进行授权
  • Session Manager:会话管理,即用户登录后就是第一次会话,在没有推出前,它的信息都在会话中;类似JavaWeb的Session,而Shiro能在JavaSE环境也提供。
  • Cryptography:加密,保护数据的安全性;可以把密码加密后再存储到数据库中。
  • Web Support:Web支持,可以很容易集成到Web环境。
  • Caching:缓存,比如用户登录后,它的用户信息、权限等都不必每次去重新查询,这样能提高效率。
  • Concurrency:Shiro支持多线程应用的并发验证,即,在一个线程中开启另一个线程,就能把权限自动传播过去。
  • Testing:提供测试支持。
  • Run As:允许一个用户假装为另一个用户(如果他同意)的身份进行访问。
  • Remember Me:记住我。即登录一次后,下次自动登录。

 
Shiro架构:

在这里插入图片描述

  • Subject 对象:表示当前用户。和代码进行交互的就是Subject对象,它也是Shiro的对外API核心。Subject表示的用户不一定是个具体的人,而是说于当前应用交互的任何东西都是Subject,例如网络爬虫、机器人,与Subject的所有交互都会委托给SecurityManager进行管理。
  • SecurityManager 对象:安全管理器,所有与安全相关的操作都会涉及到SecurityManager,同时它也管理着所有的Subject,是Shiro的核心。它负责与Shiro的其他组件进行交互,相当于SpringMVC的DispatcherServlert。
  • Realm 对象:Shiro会从Realm获取安全数据(用户、角色、权限),也就是说SecurityManager要验证用户的身份需要从Realm获取相应的用户来进行比较,也需要从Realm得到用户相应的角色、权限等信息,来验证用户的操作是否可以进行下去。可以把Realm看成DataSource。

12.2、快速体验

这是官方提供的一个Sample,运行试试看,后面会具体讲解。

1、创建一个普通的Maven项目

2、导入依赖

<dependencies>
  <!-- Shiro  -->
  <dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-core</artifactId>
    <version>1.7.1</version>
  </dependency>

  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>jcl-over-slf4j</artifactId>
    <version>1.7.25</version>
  </dependency>

  <!-- 这个是日志门面,通过它能调用很多其他日志框架 -->
  <dependency>
    <groupId>org.slf4j</groupId>
    <artifactId>slf4j-log4j12</artifactId>
    <version>1.7.25</version>
  </dependency>
 
  <dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
  </dependency>

</dependencies>

放在resources目录下:

log4j.properties

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

shiro.ini (如果IDEA对.ini文件没有高亮,需要导入ini插件,去setting-plugins里安装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

3、Quickstart.java

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Quickstart {
    
    

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


    public static void main(String[] args) {
    
    

        // 创建带有配置的Shiro SecurityManager的最简单方法
        // 领域,用户,角色和权限是使用简单的INI配置
        // 我们将使用可提取.ini文件的工厂来完成此操作,返回一个SecurityManager实例:
        // 在类路径的根目录下使用shiro.ini文件(file:和url:前缀分别从文件和url加载):
        Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:shiro.ini");
        SecurityManager securityManager = factory.getInstance();
        //对于这个简单的示例快速入门,请使SecurityManager作为JVM单例访问。
      	//大多数应用程序都不会这样做,而是依靠其容器配置或web.xml进行webapps。
      	//这超出了此简单快速入门的范围,因此我们只做最低限度的工作,所以你可以继续感受一下。
				SecurityUtils.setSecurityManager(securityManager);
        

        // 现在已经建立了一个简单的Shiro环境,让我们看看您可以做什么:
        // 获取当前执行的用户:
        Subject currentUser = SecurityUtils.getSubject();

        // 使用Session做一些事情(不需要Web或EJB容器!!!)
        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 + "]");
        }

        // 判断当前用户是否被认证(授权)
        if (!currentUser.isAuthenticated()) {
    
    
            //token:即令牌
            UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
            token.setRememberMe(true);
            try {
    
    
                currentUser.login(token);//执行登录操作
            } catch (UnknownAccountException uae) {
    
    
                log.info("用户名不存在:" + token.getPrincipal());
            } catch (IncorrectCredentialsException ice) {
    
    //
                log.info("密码错误:" + token.getPrincipal());
            } catch (LockedAccountException lae) {
    
    
                log.info("用户被锁定了" + token.getPrincipal());
            }
            catch (AuthenticationException ae) {
    
    
                //未知错误
            }
        }

        //说出他们是谁:打印其标识主体(在这种情况下,为用户名):
        log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");

        //判断用户是否有相应权限(在shiro.ini里设置的)
        if (currentUser.hasRole("schwartz")) {
    
    
            log.info("May the Schwartz be with you!");
        } else {
    
    
            log.info("Hello, mere mortal.");
        }

        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!");

        //注销
        currentUser.logout();
        //结束系统
        System.exit(0);
    }
}

4、直接运行
在这里插入图片描述

12.3、SpringBoot集成Shiro

源码下载:

  • CSDN:https://download.csdn.net/download/qq_39763246/16264048
  • 百度云:https://pan.baidu.com/s/1BpMzuOHEKZknH52FmdsjAg 提取码: kdin

项目结构:

image-20210331100905576

需要提前建好数据库用户表:

在这里插入图片描述
 

实现后的效果:

  1. 访问主页

    image-20210331095928556
  2. 点击登录

image-20210331100027186 image-20210331100128570
  1. 注销,登录小红

    image-20210331100227435
image-20210331100342193
  1. 注销后,直接复制URL进行登录,被拦截。

    image-20210331100639933

 
所导入的依赖:

<!--thymeleaf—— 命名空间xmlns:th="http://www.thymeleaf.org" -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<!-- lombok简化pojo -->
<dependency>
  <groupId>org.projectlombok</groupId>
  <artifactId>lombok</artifactId>
  <version>1.18.12</version>
</dependency>

<!-- Shiro -->
<dependency>
  <groupId>org.apache.shiro</groupId>
  <artifactId>shiro-spring</artifactId>
  <version>1.7.1</version>
</dependency>
<dependency>
  <groupId>com.github.theborakompanioni</groupId>
  <artifactId>thymeleaf-extras-shiro</artifactId>
  <version>2.0.0</version>
</dependency>
<!-- MySQL驱动 -->
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
  <version>5.1.47</version>
</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.5</version>
</dependency>
<!-- mybatis的启动器 -->
<dependency>
  <groupId> org.mybatis.spring.boot </groupId>
  <artifactId> mybatis-spring-boot-starter </artifactId>
  <version> 2.1.3 </version>
</dependency>

1、配置Shiro

  • 编写UserReaml类

    作用:正如前面介绍部分所说,Realm是Shiro三大对象之一,它用于向SecurityManager提供用户信息,主要就是认证和授权方法。认证方法,即用户登录时会进入该方法判断用户名、密码等。授权方法,即用户登录时会进入该页面判断用户权限是否足够。

    //自定义的UserRealm
    public class UserRealm extends AuthorizingRealm {
          
          
        @Autowired
        UserServiceImpl userService;
    
        //授权(需要用户有相应权限才能访问)
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(
                PrincipalCollection principals) {
          
          
            System.out.println("执行了——>授权 doGetAuthorizationInfo");
    
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
    
            //对当前用户进行判断并授予相应权限
            Subject subject = SecurityUtils.getSubject();//获得当前用户
            User currentUser = (User) subject.getPrincipal();//拿到当前User对象,是认证里传过来的
    
            //该User是从数据库中读取的,User的Perms是该用户拥有的权限。多个权限用|分割。
            String[] strings = currentUser.getPerms().split("\\|");
            if(strings!=null){
          
          
                ArrayList<String> arrayList = new ArrayList<>();
                for (String string : strings) {
          
          
                    arrayList.add(string);
                }
                //授予当前用户的权限
                info.addStringPermissions(arrayList);
            }
    
            return info;
        }
    
    
        //认证(判断用户名和密码是否正确)
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(
                AuthenticationToken token) throws AuthenticationException {
          
          
            System.out.println("执行了——>认证 doGetAuthenticationInfo");
    
            UsernamePasswordToken userToken = (UsernamePasswordToken)token;
    
            //用户名认证
            User user = userService.queryUserByName(userToken.getUsername());
            if(user == null){
          
          //用户是否存在
                return null;//如果用户名不存在,则返回null,即会抛出异常 UnknownAccountException
            }
    
            //密码认证,由Shiro自己完成。可以进行加密。第一个参数 可以让其他方法拿到
            return new SimpleAuthenticationInfo(user, user.getPassword(), "");
        }
    }
    
  • 编写ShiroConfig类

    作用:这个类包含了Shiro三大对象的SecurityManager,在ShiroFilterFactoryBean方法里,我们可以设置拦截或者要求访问某些页面需要对应权限,以及被拦截后跳转的新页面(和SpringSecurity类似的)。后面两个方法DefaultWebSecurityManagerUserRealm都是连贯着的,具体看注释。

    @Configuration
    public class ShiroConfig {
          
          
    
        //3. ShiroFilterFactoryBean,后续的操作在这里进行添加,例如登录拦截
        @Bean
        public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
          
          
            ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
            //设置安全管理器
            bean.setSecurityManager(securityManager);
    
            /** 添加Shiro的内置过滤器,进行登录拦截。
             *  anon:无需认证就能访问
             *  authc:必须认证才能访问
             *  user:必须拥有"记住我"功能才能访问,一般不用
             *  perms:拥有"对某个资源的权限"才能访问
             *  role:拥有某个角色权限才能访问
             */
            Map<String, String> filterMap = new LinkedHashMap<>();
    
            //设置add和update页面为需要相应权限才能访问
            filterMap.put("/user/add", "perms[user:add]");
            filterMap.put("/user/update", "perms[user:update]");
    
            //注意!!! 要想设置权限,必须把拦截写在权限的下面,也就是下面这行代码必须在上面里昂行代码下面!!
            filterMap.put("/user/*", "authc");//使得/user/下的所有请求都需要认证了才能访问
    
            bean.setFilterChainDefinitionMap(filterMap);
    
            //设置权限不足的跳转页面
            bean.setUnauthorizedUrl("/unauthorized");
    
            //设置登录的请求,当被拦截时,跳转到登录页面
            bean.setLoginUrl("/toLogin");
    
            return bean;
        }
    
        //2. DefaultWebSecurityManager,
        // 注解@Qualifier 是按名称进行注入,Spring会通过userRealm()方法返回一个UserRealm对象。
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
          
          
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
            //关联UserRealm
            securityManager.setRealm(userRealm);
            return securityManager;
        }
    
        //1. 创建Realm对象,返回Bean,需要先自定义类。
        @Bean(name = "userRealm")//可省略name,默认就是通过userRealm()方法名来获取
        public UserRealm userRealm(){
          
          
            return new UserRealm();
        }
    
    
        //利用ShiroDialect来整合Thymeleaf,这个方法是为了在前端使用Shiro命名空间。
        @Bean
        public ShiroDialect getShiroDialect(){
          
          
            return new ShiroDialect();
        }
    }
    

2、MyBatis

  • 配置MyBatis和Druid数据源(可以不用这个数据源,就用默认的,具体看 员工管理系统的准备工作 ),使用yaml

    spring:
      datasource:
        username: root
        password: '123456'
        #一般需要增加时区配置:serverTimezone=Asia/Shanghai
        url: jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
        #SpringBoot现在推荐这个驱动 com.mysql.jdbc.Driver
        driver-class-name: com.mysql.jdbc.Driver
        type: com.alibaba.druid.pool.DruidDataSource #切换到Druid数据源
    
        #Spring Boot 默认是不注入这些属性值的,需要自己绑定(写Druid配置类)
        #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:
      type-aliases-package: com.zcy.pojo
      mapper-locations: classpath:mapper/*.xml
    
  • dao层

    @Mapper
    @Repository
    public interface UserMapper {
          
          
       User queryUserByName(String name);
    }
    
    <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      <mapper namespace="com.zcy.dao.UserMapper">
    
      <select id="queryUserByName" parameterType="String" resultType="User">
      	select * from user where name=#{
          
          name};
    	</select>
        
    </mapper>
    
  • service层

    public interface UserService {
          
          
        User queryUserByName(String name);
    }
    
    @Service
    public class UserServiceImpl implements UserService {
          
          
        @Autowired
        UserMapper userMapper;
    
        @Override
        public User queryUserByName(String name) {
          
          
            return userMapper.queryUserByName(name);
        }
    }
    
  • controller层

    @Controller
    public class MyController {
          
          
        @RequestMapping("/user/add")
        public String add(){
          
          
            return "user/add";
        }
    
        @RequestMapping("/user/update")
        public String update(){
          
          
            return "user/update";
        }
    
        @RequestMapping("toLogin")
        public String toLogin(){
          
          
            return "login";
        }
    
        @RequestMapping("/login")
        public String login(String username, String password, Model model){
          
          
            //获取当前用户,Subject对象就代表当前用户
            Subject subject = SecurityUtils.getSubject();
            //封装用户的登录数据(将前端传进来的用户名和密码放入token里)
            UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    
            try {
          
          
                subject.login(token);
                model.addAttribute("username", username);
                //登录成功则进入主页,失败则抛出异常并返回登录页
                return "index";
            }catch (UnknownAccountException e){
          
          
                //用户名不存在
                model.addAttribute("msg", "用户名错误");
                return "login";
            }catch (IncorrectCredentialsException e){
          
          
                //密码错误
                model.addAttribute("msg", "用户密码错误");
                return "login";
            }
        }
    
        @ResponseBody
        @RequestMapping("/unauthorized")
        public String unauthorized(){
          
          
            return "权限不足";
        }
        @RequestMapping("/logout")
        public String logout(){
          
          
            Subject subject = SecurityUtils.getSubject();
            subject.logout();
            return "login";
        }
    }
    

3、前端

  • index.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org"
          xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
    <head>
        <meta charset="UTF-8">
        <title>首页</title>
    </head>
    <body>
        <h1>首页</h1>
    
        <!-- 未认证,即未登录时就显示登录按钮    -->
        <div shiro:notAuthenticated>
            <a href="/toLogin">登录</a>
        </div>
        <!-- 已认证,即已登录时就显示注销按钮    -->
        <div shiro:authenticated="">
            <h2>欢迎<span th:text="${username}"></span></h2>
            <a href="/logout">注销</a>
        </div>
    
        <p th:text="${msg}"></p>
        <!-- 有add权限时才显示该链接    -->
        <div shiro:hasPermission="user:add">
            <a th:href="@{/user/add}">add</a>
        </div>
    
        <!-- 有update权限时才显示该链接    -->
        <div shiro:hasPermission="user:update">
            <a th:href="@{/user/update}">update</a>
        </div>
    </body>
    </html>
    
  • login.html

    <!DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
        <!-- 新增一个提示信息   -->
        <p th:text="${msg}" style="color:red;"></p>
        <form th:action="@{/login}">
            <p>用户名;<input type="text" name="username"></p>
            <p>密码;<input type="text" name="password"></p>
            <p><input type="submit" value="提交"></p>
        </form>
    </body>
    </html>
    
  • add.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>add</h1>
    <a href="/logout">注销</a>
    </body>
    </html>
    
  • update.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>Title</title>
    </head>
    <body>
    <h1>update</h1>
    <a href="/logout">注销</a>
    </body>
    </html>
    

猜你喜欢

转载自blog.csdn.net/qq_39763246/article/details/115344851