SpringBoot 04 —— Shiro

系列文章

SpringBoot 01 —— HelloSpringBoot、yaml配置、数据校验、多环境切换
SpringBoot 02 —— Web简单探究、员工管理系统
SpringBoot 03 —— Spring Security
SpringBoot 04 —— Shiro
SpringBoot 05 —— Swagger
SpringBoot 06 —— 异步任务、邮件任务、定时任务
SpringBoot 07 —— 分布式:Dubbo+Zookeeper



十二、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>
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

老板来碗小面加蛋~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值