Shiro简介

shiro是什么?

Apache Shiro是一个强大且易用的java安全框架,有!!!<font color="red">身份验证、授权、密码学和会话管理</font>!!!。使用shiro的易于理解的API,您可以快速、轻松的获取任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。

!!!==常见的安全框架==!!!:

  • Spring Security重量级安全框架
  • Apacho Shiro轻量级安全框架

shiro结构和作用

shiro有四大基石组成---!!!<font color=red>身份验证、授权、会话管理和加密</font>!!!

Shiro 开发团队称为“应用程序的!!!==四大基石==!!!” ——身份验证,授权,会话管理和加密作为其目标。

  • Authentication(身份认证):

有时也简称为“登录”,这是一个证明用户是他们所说的他们是谁的行为。

  • Authorization(授权):

访问控制的过程,也就是绝对“谁”去访问“什么”权限。访问控制的过程,也就是绝对“谁”去访问“什么”权限。

  • Session Management:管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
  • Cryptography:通过使用加密算法保持数据安全同时易于使用。也提供了额外的功能来支持和加强在不同环境下所关注的方面,尤其是以下这些:

    1. Web Support: Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
    2. Caching:缓存是 Apache Shiro 中的第一层公民,来确保安全操作快速而又高效。
    3. Concurrency: Apache Shiro 利用它的并发特性来支持多线程应用程序。
    4. Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
    5. "Run As":允许一个用户假装为另一个用户(如果他们允许)的身份进行访问。
    6. "Remember Me":在会话中记住用户的身份,所以他们只需要在强制时候登录。

怎么使用?常用的使用场景?

使用shiro核心的api来实现

通常使用在:后台管理

shiro的核心API

三个核心组件:Subject, SecurityManager 和 Realms

  • Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。但考虑到大多数目的和用途,你可以把它认为是Shiro的“用户”概念。
  • Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
  • SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
  • Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。

从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
 Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现

shiro原理

  • 也就是说对于我们而言,最简单的一个 Shiro 应用:
    应用代码通过 Subject 来进行认证和授权,而 Subject 又委托给 SecurityManager;
  • 我们需要给 Shiro 的 SecurityManager 注入 Realm,从而让 SecurityManager 能得到合法的用户及其权限进行判断。

springboot2.0整合`shiro+thymeleaf小案例

  1. 在pom文件中导入依赖

    <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-devtools</artifactId>
            </dependency>
            <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.8</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>org.mybatis.spring.boot</groupId>
                <artifactId>mybatis-spring-boot-starter</artifactId>
                <version>1.1.1</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-core</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.apache.shiro</groupId>
                <artifactId>shiro-spring</artifactId>
                <version>1.4.0</version>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-thymeleaf</artifactId>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.0.28</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.6</version>
            </dependency>
                
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
                
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter</artifactId>
            </dependency>
                
            <!--thymeleaf和shiro的兼容依赖-->
            <dependency>
                <groupId>com.github.theborakompanioni</groupId>
                <artifactId>thymeleaf-extras-shiro</artifactId>
             <version>2.0.0</version>
            </dependency>
  1. 编写yml配置文件

    #spring和mybatis的包
    mybatis:
      type-aliases-package: com.sxh.bootandshiro
      ##mybatis配置路径
      mapper-locations: classpath:mappers/*.xml
    server:
      port: 9001
    spring:
      datasource:
        driver-class-name: com.alibaba.druid.proxy.DruidDriver
        url: jdbc:mysql://localhost:3306/shiro?useUnicode=true&characterEncoding=utf-8
        username: sxh
        password: xh100600.
      thymeleaf:
        cache: false
        encoding: utf-8
        suffix: .html
        servlet:
          content-type: text/html
        mode: HTML5
        prefix: classpath:/template
  1. 编写loginController类

    package com.sxh.bootandshiro.controller;
    
    import com.sxh.bootandshiro.pojo.Teacher;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
     * @author 司小虎 E-mail:sxh100600@gmail.com
     * @version 创建时间:2020/2/14 14:59
     */
    @Controller
    public class LoginController {
    
        @RequestMapping("/")
        public String index(){
            return "index";
        }
    
        @RequestMapping("/add")
        public String add(){
            return "add";
        }
        @RequestMapping("/update")
        public String update(){
            return "update";
        }
        @RequestMapping("/delete")
        public String delete(){
            return "delete";
        }
        @RequestMapping("/select")
        public String select(){
            return "select";
        }
    
        @RequestMapping("/loginUI")
        public String LoginUI(){
            return "login";
        }
    }
    
  1. 编写login.html、success.html,add.html,update.html,delete.html,select.html.index.html

loging.html(success.html和index.html的作用一样可以只写其中一个页面来进行测试)

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="UTF-8">
    <title>登录页面</title>
</head>
<body>
    <form action="/login" method="post">
        <p style="color: red" th:text="${msg}"></p>
        用户名:<input type="text" name="tname"/></br>
        密码:<input type="password" name="tpwd"/></br>
        <input type="submit" value="登录"/>
    </form>
</body>
</html>

add.html,update.html,delete.html,select.html只是一个跳转到测试的页面在里面随便写个路径就行。

index.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>

<p>这是首页</p>
    <a th:href="@{/loginUI}">登录页面</a>
<div>
    <p><a th:href="@{/update}">修改</a></p>
</div>
<div>
    <a th:href="@{/delete}">修改页面</a>
</div>
<div>
    <a th:href="@{/select}">查询页面</a>
</div>
<div>
    <a th:href="@{/add}">增加页面</a>
</div>
</body>
</html>

success.html

<!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>
</head>
<body>
    欢迎<span th:text="${session.teacher.tname}"></span>登录

    <p><a th:href="@{/add}">增加</a></p>
    <p> <a th:href="@{/delete}">删除</a></p>
    <p><a th:href="@{/update}">修改</a></p>
    <p><a th:href="@{/select}">查询</a></p>
</body>
</html>

!!!<font color="red">此时可以运行,测试一下,各个路径是否正常跳转</font>!!!

  1. 编写shiro过滤器

    package com.sxh.bootandshiro.config;
    
    import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
    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 sun.security.krb5.Realm;
    
    import javax.servlet.Filter;
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * @author 司小虎 E-mail:sxh100600@gmail.com
     * @version 创建时间:2020/2/14 17:31
     *
     * shiro的配置类
     */
    @Configuration
    public class ShiroConfig {
    
        //shiroFileterFactoryBaen
        @Bean
        public ShiroFilterFactoryBean getshiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
            //设置安全管理器
            shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
            //添加shiro的内置过滤器
            /*
            * anon:无需认证就可以访问
            * authc: 必须认证才能访问
            * user: 必须拥有  记住我  功能才能访问
            * perms: 拥有对某个资源的权限才能访问
            * role: 拥有某个角色权限才能访问
            * */
            //拦截
            Map<String, String> filtermap = new LinkedHashMap();
    
    
            //授权
            filtermap.put("/add","authc");
            filtermap.put("/delete","authc");
            filtermap.put("/update","authc");
            filtermap.put("/select","authc");
            filtermap.put("/loginUI","anon");
            filtermap.put("/login","anon");
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filtermap);
    
            //设置登录的请求
            shiroFilterFactoryBean.setLoginUrl("/loginUI");
            shiroFilterFactoryBean.setUnauthorizedUrl("/noauth");
    
    
            return shiroFilterFactoryBean;
        }
    
        //DefaultWebSecurityManager(2)
        @Bean(name = "securityManager")
        public DefaultWebSecurityManager getdefaultWebSecurityManager(@Qualifier("userRealm") MyRealm myRealm){
            DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
           //关联userRealm
            securityManager.setRealm(myRealm);
            return securityManager;
        }
    
        //创建realm对象,需要自定义(1)
        @Bean
        public MyRealm userRealm(){
            return new MyRealm();
        }
    
        //整合shiroDialect:用来整合shiro htymeleaf
        @Bean
        public ShiroDialect getshiroDialect(){
            return new ShiroDialect();
        }
    
    }
    
  1. 创建自己的realm,继承AuthorizingRealm

    package com.sxh.bootandshiro.config;
    
    import com.sxh.bootandshiro.pojo.Teacher;
    import com.sxh.bootandshiro.service.TeacherService;
    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.session.Session;
    import org.apache.shiro.subject.PrincipalCollection;
    import org.apache.shiro.subject.Subject;
    
    import javax.annotation.Resource;
    
    /**
     * @author 司小虎 E-mail:sxh100600@gmail.com
     * @version 创建时间:2020/2/14 17:33
     * 自定义的UserRealm
     */
    
    public class MyRealm extends AuthorizingRealm {
        @Resource
        TeacherService teacherService;
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
            System.out.println("执行了=>授权doGetAuthorizationInfo");
            //SimpleAuthorizationInfo
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
           // info.addStringPermission("add");   //给用户授权  死数据
            //拿到当前登录的这个对象
            Subject subject = SecurityUtils.getSubject();
            Teacher currentUser = (Teacher)subject.getPrincipal();//拿到老师对象
            //设置当前用户的权限
            info.addStringPermission(currentUser.getPrems());
            System.out.println(currentUser.getPrems());
            System.out.println(info.toString());
            //设置当前用户的权限
    
            return info;
        }
    
        //认证
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            System.out.println("执行了=>认证doGetAuthorizationInfo");
            //获取当前用户
            UsernamePasswordToken userToken = (UsernamePasswordToken) token;
            //连接真实数据库
            Teacher teacher = teacherService.findByName(userToken.getUsername());
            if (null==teacher){//没有这个人
                return null;
            }
            Subject subject = SecurityUtils.getSubject();
            Session session = subject.getSession();
            session.setAttribute("teacher",teacher);/*
            Teacher teacher1 = (Teacher) session.getAttribute("teacher");
            System.out.println(session.getAttribute("teacher").toString());*/
            //可以加密 MD5  和MD5盐值加密
            //密码认证 shiro做
            return new SimpleAuthenticationInfo(teacher,teacher.getTpwd(),"");
        }
    
    
    
    
    
    }
    
  1. 修改LoginController类,修改为以下内容

    package com.sxh.bootandshiro.controller;
    
    import com.sxh.bootandshiro.pojo.Teacher;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.IncorrectCredentialsException;
    import org.apache.shiro.authc.UnknownAccountException;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.session.Session;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    
    /**
     * @author 司小虎 E-mail:sxh100600@gmail.com
     * @version 创建时间:2020/2/14 14:59
     */
    @Controller
    public class LoginController {
    
        @RequestMapping("/")
        public String index(){
            return "index";
        }
    
        @RequestMapping("/add")
        public String add(){
            return "add";
        }
        @RequestMapping("/update")
        public String update(){
            return "update";
        }
        @RequestMapping("/delete")
        public String delete(){
            return "delete";
        }
        @RequestMapping("/select")
        public String select(){
            return "select";
        }
    
        @RequestMapping("/loginUI")
        public String LoginUI(){
            return "login";
        }
    
    
    
    
    
    
        @RequestMapping("/login")
        public String Login(Teacher teacher, Model model){
            //1.shiro获取当前永不 subject
            Subject currenUser = SecurityUtils.getSubject();
            //2.shiro 创建 账户和密码的token
            UsernamePasswordToken token = new UsernamePasswordToken(teacher.getTname(), teacher.getTpwd());
            //3.使用subject中的login方法  看是否有异常(账户和密码有错),没有异常就认证成功---登录到success页面
            //如果有错返回login页面
            try {
                currenUser.login(token);
                /*model.addAttribute("msg","登录成功");*/
               /* return "index";*/
            } catch (IncorrectCredentialsException e) {
                model.addAttribute("msg","密码不正确");
            }catch (UnknownAccountException e){
                model.addAttribute("msg","账号不存在");
            }
    
            System.out.println(currenUser.isAuthenticated());
            if (currenUser.isAuthenticated()){
                //没有异常就认证成功---登录到success页面
                System.out.println("认证成功");
                model.addAttribute("tname",teacher.getTname());
                return "success";
            }else {
                //如果有错返回login页面
                token.clear();
                return "login";
            }
    //        return null;
        }
    
        @RequestMapping("/noauth")
        @ResponseBody
            public String error(){
            return "未授权页面";
        }
    }
    
  1. 创建实体类

    package com.sxh.bootandshiro.pojo;
    
    import lombok.Getter;
    import lombok.Setter;
    import lombok.experimental.Accessors;
    
    /**
     * @author 司小虎 E-mail:sxh100600@gmail.com
     * @version 创建时间:2020/2/14 14:51
     */
    @Getter
    @Setter
    @Accessors(chain = true)  //链式
    public class Teacher {
        private int tid;
        private String tname;
        private String tpwd;
        private String prems;
    }
    
  1. 创建mapper类和mapper.xml,service类以及service的实现类

TeacherMapper

package com.sxh.bootandshiro.mapper;

import com.sxh.bootandshiro.pojo.Teacher;
import org.apache.ibatis.annotations.Mapper;

/**
 * @author 司小虎 E-mail:sxh100600@gmail.com
 * @version 创建时间:2020/2/14 14:56
 */
@Mapper
public interface TeacherMapper {
    Teacher findByName(String tname);
}

TeacherMapper.xml

<?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.sxh.bootandshiro.mapper.TeacherMapper">

    <resultMap id="resultmap" type="teacher">
        <id column="tid" property="tid"></id>
        <result column="tname" property="tname"></result>
        <result column="tpwd" property="tpwd"></result>
    </resultMap>

    <select id="findByName" resultMap="resultmap">
        select * from teacher where tname=#{tname}
    </select>
</mapper>

TeacherService

package com.sxh.bootandshiro.service;

import com.sxh.bootandshiro.pojo.Teacher;
import org.springframework.stereotype.Service;

/**
 * @author 司小虎 E-mail:sxh100600@gmail.com
 * @version 创建时间:2020/2/14 18:19
 */

public interface TeacherService {
    Teacher findByName(String tname);
}

TeacherServiceImp

package com.sxh.bootandshiro.service;

import com.sxh.bootandshiro.mapper.TeacherMapper;
import com.sxh.bootandshiro.pojo.Teacher;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 司小虎 E-mail:sxh100600@gmail.com
 * @version 创建时间:2020/2/14 18:20
 */
@Service
public class TeacherServiceImp implements TeacherService{
    @Resource
    private TeacherMapper teacherMapper;


    @Override
    public Teacher findByName(String tname){
        Teacher login = teacherMapper.findByName(tname);
        return login;
    }
}

如果你的mapper包、pojo包、service包、controller包没有在主启动类包的下面,你需要在主启动类中配置以下注解,扫描这些包

package com.sxh.bootandshiro;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;

@MapperScan("com.sxh.bootandshiro.mapper")
@EntityScan("com.sxh.bootandshiro.pojo")
@SpringBootApplication
public class BootandshiroApplication {

    public static void main(String[] args) {
        SpringApplication.run(BootandshiroApplication.class, args);
    }

}

整体思路:

  1. 先编写平常的登录逻辑,如果改程序没有出现bug,就继续集成shiro,这样的好处:当出现bug时,能够缩小bug的区域。
  2. 当集成时shiro会先通过一个shiro的过滤器,在shiro过滤器中通过SecurityUtils.getSubject()来获取subject主体。
  3. 再经过subject.isAuthenticated()判断当前用户是否认证过了,如果认证过了就放行了,

如果没有认证过,就把前台传递的账号密码封装为一个UserNamePasswordToken对象

  1. securityManager充当一个中介,数据通过securityManager来进行分辨和管理,再传输给Realm。

其中session会话由subject主体类管理。

Realm再进行具体的认证和授权(数据库中查到的权限)以及密码的加密加盐

  1. 最后根据配置shiro的真实过滤器跳转至登录成功页面或登录失败的页面
Last modification:February 19th, 2020 at 05:22 pm
如果觉得我的文章对你有用,请随意赞赏