Shiro概述

目录

Shiro简介

什么是Shiro?

[Shiro 的特点](#Shiro 的特点)

核心组件

Shiro入门

身份认证

Realm

身份授权

Springboot集成Shiro


Shiro简介

什么是Shiro?

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

Shiro 的特点

Shiro 是一个强大而灵活的开源安全框架,能够非常清晰的处理认证、授权、管理会话以及密码加 密。如下是它所具有的特点:

  • 易于理解的 Java Security API;
  • 简单的身份认证(登录),支持多种数据源(LDAP,JDBC 等);
  • 对角色的简单的签权(访问控制),也支持细粒度的鉴权;
  • 支持一级缓存,以提升应用程序的性能;
  • 内置的基于 POJO 企业会话管理,适用于 Web 以及非 Web 的环境;
  • 异构客户端会话访问;
  • 非常简单的加密 API;
  • 不跟任何的框架或者容器捆绑,可以独立运行。

核心组件

Shiro架构图

Subject

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

SecurityManager

SecurityManager权限管理器,它是shiro的核心,负责对所有的subject进行安全管理。通过 SecurityManager可以完成subject的认证、授权等,SecurityManager是通过Authenticator进行认 证,通过Authorizer进行授权,通过SessionManager进行会话管理等。SecurityManager是一个接 口,继承了Authenticator, Authorizer, SessionManager这三个接口。

Authenticator

Authenticator即认证器,对用户登录时进行身份认证。

Authorizer

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

Realm(数据库读取+认证功能+授权功能实现)

Realm领域,相当于datasource数据源,securityManager进行安全认证需要通过Realm获取用户权限数据

比如: 如果用户身份数据在数据库那么realm就需要从数据库获取用户身份信息。

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

SessionManager

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

SessionDAO

SessionDAO即会话dao,是对session会话操作的一套接口

比如: 可以通过jdbc将会话存储到数据库 ;也可以把session存储到缓存服务器。

CacheManager

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

Cryptography

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

Shiro入门

身份认证

流程如下:

1、Shiro把用户的数据封装成标识token,token一般封装着用户名,密码等信息

2、使用Subject门面获取到封装着用户的数据的标识token

3、Subject把标识token交给SecurityManager,在SecurityManager安全中心中,SecurityManager 把标识token委托给认证器Authenticator进行身份验证。认证器的作用一般是用来指定如何验证,它规定本次认证用到哪些Realm

4、认证器Authenticator将传入的标识token,与数据源Realm对比,验证token是否合法

Realm

在 Shiro 框架中,Realm 是用于连接数据源并提供安全相关数据的组件,可以理解为 "安全数据源" 的抽象。它是 Shiro 与实际存储用户、角色、权限等安全信息的数据源(如数据库、LDAP、文件等)之间的桥梁。

在实际开发中,我们通常需要自定义 Realm (继承 Shiro 提供的抽象类,如 AuthorizingRealm),并重写关键方法:

  • doGetAuthenticationInfo():实现从数据源获取用户认证信息(如根据用户名查密码)
  • doGetAuthorizationInfo():实现从数据源获取用户授权信息(如角色、权限)
java 复制代码
public class MyRealm extends AuthorizingRealm {
    // 授权:获取用户的角色和权限
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        // 从 principals 中获取用户名
        String username = (String) principals.getPrimaryPrincipal();
        // 从数据库/其他数据源查询该用户的角色和权限
        Set<String> roles = ...; // 查询角色
        Set<String> permissions = ...; // 查询权限
        // 封装授权信息并返回
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setRoles(roles);
        info.setStringPermissions(permissions);
        return info;
    }

    // 认证:获取用户的身份凭证(用于登录验证)
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        // 从 Token 中获取用户名
        String username = (String) token.getPrincipal();
        // 从数据库/其他数据源查询该用户的密码
        String password = ...; // 查询密码
        if (password == null) {
            throw new UnknownAccountException("用户不存在");
        }
        // 封装认证信息并返回(Shiro 会自动对比 Token 中的密码和此处返回的密码)
        return new SimpleAuthenticationInfo(username, password, getName());
    }
}

Shiro 可以配置多个 Realm,认证器(Authenticator)会根据配置的策略(如 "至少一个成功""全部成功" 等)协调这些 Realm 完成认证。

身份授权

基本流程:

1、首先调用Subject.isPermitted/hasRole接口,其会委托给SecurityManager。

2、SecurityManager接着会委托给内部组件Authorizer;

3、Authorizer再将其请求委托给我们的Realm去做;Realm才是真正干活的;

4、Realm将用户请求的参数封装成权限对象。再从我们重写的doGetAuthorizationInfo方法中获取从 数据库中查询到的权限集合。

5、Realm将用户传入的权限对象,与从数据库中查出来的权限对象,进行一一对比。如果用户传入的权限对象在从数据库中查出来的权限对象中,则返回true,否则返回false。

进行授权操作的前提:用户必须通过认证。

Springboot集成Shiro

以下为常用注解

关键代码:

pom.xml:

XML 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.13</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ape</groupId>
    <artifactId>springboot_data_shiro</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot_data_shiro</name>
    <description>springboot_data_shiro</description>

    <properties>
        <java.version>8</java.version>
        <!--shiro-->
        <shiro.version>1.3.2</shiro.version>
    </properties>
    <dependencies>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--        &lt;!&ndash; mybatis &ndash;&gt;-->
        <!--        <dependency>-->
        <!--            <groupId>org.mybatis.spring.boot</groupId>-->
        <!--            <artifactId>mybatis-spring-boot-starter</artifactId>-->
        <!--            <version>2.3.1</version>-->
        <!--        </dependency>-->
        <!-- mp -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.2</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-test</artifactId>
            <scope>test</scope>
        </dependency>


        <!-- SECURITY begin -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-ehcache</artifactId>
            <version>${shiro.version}</version>
        </dependency>
        <!-- SECURITY end -->

        <!--shiro不要用启动器,因为很多配置不需要人工实现了-->
        <!--        <dependency>-->
        <!--            <groupId>org.apache.shiro</groupId>-->
        <!--            <artifactId>shiro-spring-boot-starter</artifactId>-->
        <!--            <version>1.9.1</version>-->
        <!--        </dependency>-->
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

controller:

java 复制代码
    @RequestMapping("/login")
    public String login(peUser user) {
        try {
            //构造登录令牌
            UsernamePasswordToken token=new UsernamePasswordToken(user.getUsername(),user.getPassword());
            //获取subject
            Subject subject= SecurityUtils.getSubject();
            subject.login(token);
            return "登录成功";
        } catch (AuthenticationException e) {
            return "用户名或密码错误";
        }
    }

MyRealm:

java 复制代码
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private peUserService userService;
    @Autowired
    private pePermissionService permissionService;

    @Autowired
    private peRoleService roleService;

    /**
     * 授权方法
     *      操作的时候,判断用户是否具有响应的权限
     *          一定先认证再授权
     *          先认证 -- 安全数据
     *          再授权 -- 根据安全数据获取用户具有的所有操作权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //获取已认证的用户数据
        peUser user = (peUser) principalCollection.getPrimaryPrincipal();//得到唯一的安全数据

        //根据用户数据获取用户的权限信息(所有角色,所有权限)

        int userId = user.getId();
        //获取用户所有角色
        List<peRole> roleList=roleService.getRoleByUserId(userId);
        Set<String> roles=new HashSet<>();//所有角色
        for (peRole role : roleList){
            roles.add(role.getName());
        }
        //获取用户所有权限
        List<pePermission> permissionList=permissionService.getPermissionByRoleId(userId);
        Set<String> perms=new HashSet<>();//所有权限
        for (pePermission permission : permissionList){
            perms.add(permission.getCode());
        }
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();//存储用户拥有的角色和权限信息,
        info.setStringPermissions(perms);
        info.setRoles(roles);
        return info;

    }

    /**
     * 认证方法
     *  参数:传递的用户名密码
     */

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //获取登录的用户名密码(token)
        UsernamePasswordToken token=(UsernamePasswordToken) authenticationToken;
        String username = token.getUsername();//用户录入的账号
        //根据用户名查询数据库
        QueryWrapper<peUser> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("username", username);
        peUser user=userService.getOne(queryWrapper);

        //判断用户是否存在或者密码是否一致
        if(user!=null){
            SimpleAuthenticationInfo info=new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getSalt()),"myRealm");
            return info;
        }
        return null;
    }


    /**
     * @Description 自定义密码比较器
     * @param
     * @return
     * bean标签 init-method属性
     */

    @PostConstruct
    public void initCredentialsMatcher(){
        //指定加密算法
        HashedCredentialsMatcher hashedCredentialsMatcher=new HashedCredentialsMatcher(DigestsUtil.SHA1);

        //指定迭代次数
        hashedCredentialsMatcher.setHashIterations(DigestsUtil.COUNTS);

        //生效密码比较器
        setCredentialsMatcher(hashedCredentialsMatcher);
    }
}
相关推荐
小许学java17 小时前
Spring事务和事务传播机制
java·数据库·spring·事务
大学生资源网17 小时前
基于Javaweb技术的宠物用品商城的设计与实现(源码+文档)
java·mysql·毕业设计·源码·springboot
汤姆yu17 小时前
基于springboot的热门文创内容推荐分享系统
java·spring boot·后端
星光一影17 小时前
教育培训机构消课管理系统智慧校园艺术舞蹈美术艺术培训班扣课时教务管理系统
java·spring boot·mysql·vue·mybatis·uniapp
lkbhua莱克瓦2418 小时前
MySQL介绍
java·开发语言·数据库·笔记·mysql
武昌库里写JAVA18 小时前
在iview中使用upload组件上传文件之前先做其他的处理
java·vue.js·spring boot·后端·sql
董世昌4118 小时前
什么是事件冒泡?如何阻止事件冒泡和浏览器默认事件?
java·前端
好度18 小时前
配置java标准环境?(详细教程)
java·开发语言
teacher伟大光荣且正确18 小时前
关于Qt QReadWriteLock(读写锁) 以及 QSettings 使用的问题
java·数据库·qt
nightseventhunit18 小时前
base64字符串String.getByte导致OOM Requested array size exceeds VM limit
java·oom