【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法

文章目录

网站安全
Spring Security

概念
环境搭建
用户认证和授权

定义规则,编写基础配置类
内容解析

Security的授权和认证的内容
thymeleaf 的 SpringSecurity 的整合
登出注销流程
内容显示

测试项目

设置跳转自己的登录页面和记住账号密码功能

保存cookie账号
跳转到自己的登录页

Shiro

简介

快速开始
shiro-springBoot-web

新建一个shiro项目
Shiro导致项目出错

查错,寻找解决方案
手动注册Filter解决?,禁用自动配置,审慎使用AI

继续排查

前提
对比项目差别点,检查自身错误
检查错误信息

测试Shiro

编写认证授权的Relam
控制器
跳转的页面编写
测试

编写登录拦截
实现用户认证

根据`quickstart`,编写认证操作
关联数据库实现用户认证
设置加密

实现用户授权

准备工作
关联数据库实现用户授权

整合前端代码

准备工作
实现代码

网站安全

在Web开发过程中,安全是一个需要认真考虑的点。

一般在系统设计初期就要考虑进来,

如果应用的基本架构已经确定,要修复安全漏洞,可能需要对系统的架构做出比较重大的调整,因而需要更多的开发时间,影响应用的发布进程。

一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分。

用户认证指的是验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。
用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

Spring提供了一个功能强大的高度可定制的身份验证访问控制框架。它实际上是保护基于spring的应用程序的标准,也就是Spring Security

Spring Security

中文网站:https://springdoc.cn/spring-boot/web.html#web.security

概念

Spring Security是一个框架,侧重于为Java应用程序提供身份验证和授权。与所有Spring项目一样,Spring安全性的真正强大之处在于它可以轻松地扩展以满足定制需求
Spring Security 框架对于Web安全都有很好的支持。

在用户认证方面,Spring Security 框架支持主流的认证方式,包括 HTTP 基本认证、HTTP 表单验证、HTTP 摘要认证、OpenID 和 LDAP 等

使用用户名、密码,有时与身份验证因素结合使用

在用户授权方面,Spring Security 提供了基于角色的访问控制和访问控制列表(Access Control List,ACL),可以对应用中的领域对象进行细粒度的控制。

在系统成功验证您的身份后,最终会授予您访问资源(如信息,文件,数据库,资金,位置,几乎任何内容)的完全权限。

需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理
重要的几个类:

WebSecurityConfigurerAdapter:自定义Security策略
AuthenticationManagerBuilder:自定义认证策略
AuthenticationManagerBuilder:自定义认证策略

环境搭建

创建项目图片[1] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
检查pom的配置,确保引入Spring Secutrity

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

导入静态资源

编写控制器跳转

package com.demo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class RouterController {
            

    @RequestMapping(value = {
            "/","/index"})
    public String index() {
            
        return "index";
    }


    @RequestMapping(value = "/toLogin")
    public String toLogin() {
            
        return "views/login";
    }

    @RequestMapping("/level1/{id}")
    public String toLevel1(@PathVariable Integer id) {
            
        return "views/level1/"+ id;
    }

    @RequestMapping("/level2/{id}")
    public String toLevel2(@PathVariable Integer id) {
            
        return "views/level2/"+ id;
    }

    @RequestMapping("/level3/{id}")
    public String toLevel3(@PathVariable Integer id) {
            
        return "views/level3/"+ id;
    }
}

尝试启动,拦截器拦截到login页面,环境搭建完成

用户认证和授权

定义规则,编写基础配置类

Spring Security 6.0 开始(对应 Spring Boot 3.0+),WebSecurityConfigurerAdapter 这个类已经被彻底移除,不再存在。

旧版的功能写法

package com.kuang.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity // 开启WebSecurity模式
public class SecurityConfig extends WebSecurityConfigurerAdapter {
            

    @Override
    protected void configure(HttpSecurity http) throws Exception {
            

        // 定制请求的授权规则
        // 首页所有人可以访问
        http.authorizeRequests().antMatchers("/").permitAll()
        .antMatchers("/level1/**").hasRole("vip1")
        .antMatchers("/level2/**").hasRole("vip2")
        .antMatchers("/level3/**").hasRole("vip3");

        // 开启自动配置的登录功能
        // /login 请求来到登录页
        // /login?error 重定向到这里表示登录失败
        http.formLogin();

    }

    //定义认证规则
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            
        //在内存中定义,也可以在jdbc中去拿....
        //Spring security 5.0中新增了多种加密方式,也改变了密码的格式。
        //要想我们的项目还能够正常登陆,需要修改一下configure中的代码。我们要将前端传过来的密码进行某种方式加密
        //spring security 官方推荐的是使用bcrypt加密方式。

        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
        .withUser("kuangshen").password(new BCryptPasswordEncoder().encode("123456")).roles("vip2","vip3")
        .and()
        .withUser("root").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2","vip3")
        .and()
        .withUser("guest").password(new BCryptPasswordEncoder().encode("123456")).roles("vip1","vip2");
    }
} 

新版功能写法

@EnableWebSecurity
@Configuration
public class SecurityConfig  {
            

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            

        http.authorizeHttpRequests(request -> request
                .requestMatchers("/").permitAll()               // 允许所有人访问首页
//                .requestMatchers("/login").permitAll()               // 允许所有人访问首页
                .requestMatchers("/level1/**").hasRole("vip1")      // 允许vip1访问level1
                .requestMatchers("/level2/**").hasRole("vip2")      // 允许vip2访问level2
                .requestMatchers("/level3/**").hasRole("vip3")      // 允许vip3访问level3
                .anyRequest().authenticated()                 // 其他请求需要登录
        ).formLogin(AbstractAuthenticationFilterConfigurer::permitAll
        ).logout(LogoutConfigurer::permitAll
        );

        return http.build();
    }
    
    /**
     * 配置用户详情服务Bean
     * 该方法创建并配置一个基于内存的用户详情管理器,包含三个预定义用户:
     * 1. admin用户:拥有所有角色权限
     * 2. manager用户:拥有vip2和vip3角色权限
     * 3. employee用户:拥有vip3角色权限
     * 所有用户的初始密码都为"123456",使用委托密码编码器进行加密存储
     *
     * @return UserDetailsService 用户详情服务实例
     */
    @Bean
    public UserDetailsService userDetailsService() {
            

        // 创建委托密码编码器,支持多种密码编码方式
        PasswordEncoder encoder = PasswordEncoderFactories.createDelegatingPasswordEncoder();

        // 构建admin用户,拥有最高权限,包含所有角色
        UserDetails admin = User.builder()
                .username("admin")
                .password(encoder.encode("123456"))
                .roles("vip1","vip2","vip3")
                .build();

        // 构建manager用户,拥有中等级别权限
        UserDetails manager = User.builder()
                .username("manager")
                .password(encoder.encode("123456"))
                .roles("vip2","vip3")
                .build();

        // 构建普通employee用户,拥有基础权限
        UserDetails employee = User.builder()
                .username("user")
                .password(encoder.encode("123456"))
                .roles("vip3")
                .build();

        // 返回基于内存的用户详情管理器,包含所有预定义用户
        return new InMemoryUserDetailsManager(admin, manager, employee);
    }

}

内容解析:

登录:.formLogin(AbstractAuthenticationFilterConfigurer::permitAll等同于.formLogin(form -> form .permitAll() ) 表示 登录页面和处理URL允许所有人访问,默认跳转到Spring SecurityLogin页面;图片[2] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
注销LogoutConfigurer::permitAll同上所述,说的是logout的方法;
PasswordEncoder 作为密码编码器,将密码进行编码后存放到UserDetailspassword里面;
InMemoryUserDetailsManager(admin, manager, employee)基于memory内存认证;

内容解析

Security的授权和认证的内容

Authentication(授权):Authentication :: Spring Security
Authentication(认证):

thymeleaf 的 SpringSecurity 的整合

导入整合依赖启动器

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

导入命名空间: xmlns:sec="http://www.thymeleaf.org/extras/spring-security"

<!DOCTYPE html>
<html lang="en" xmlns:sec="http://www.thymeleaf.org/extras/spring-security"
  xmlns:th="http://www.thymeleaf.org">  <!-- 主要是这行代码 官方建议使用 -->
  <!--上述操作是加注释-->
  <head>
    <meta charset="UTF-8">
    <meta content="width=device-width, initial-scale=1, maximum-scale=1" name="viewport">
    <title>首页</title>
    <!--semantic-ui-->
    <link href="https://cdn.bootcss.com/semantic-ui/2.4.1/semantic.min.css" rel="stylesheet">
    <link rel="stylesheet" th:href="@{/qinjiang/css/qinstyle.css}">
  </head>
  <body>

    <!--主容器-->
    <div class="ui container">

      <div class="ui segment" id="index-header-nav" th:fragment="nav-menu">
        <div class="ui secondary menu">
          <a class="item" th:href="@{/index}">首页</a>

          <!--登录注销-->
          <div class="right menu">

            <!--如果未登录-->
            <div sec:authorize="!isAuthenticated()">
              <a class="item" th:href="@{/toLogin}">
                <i class="address card icon"></i> 登录
              </a>
            </div>

            <!--如果登录了 , 显示用户名与注销按钮-->
            <div sec:authorize="isAuthenticated()">
              <a class="item">
                用户名: <span sec:authentication="name"></span>
                角色: <span sec:authentication="principal.authorities"></span>
              </a>
            </div>

            <div sec:authorize="isAuthenticated()">
              <a class="item" th:href="@{/logout}">
                <i class="sign-out icon"></i> 注销
              </a>
            </div>

            <!--已登录
            <a th:href="@{/usr/toUserCenter}">
            <i class="address card icon"></i> admin
          </a>
            -->
          </div>
        </div>
      </div>

      <div class="ui segment">
        <h3>Spring Security Study by 秦疆</h3>
      </div>

      <div>
        <br>
        <div class="ui three column stackable grid">

          <!--菜单 , 根据用户的角色, 动态的实现-->
          <div class="column" sec:authorize="hasRole('vip1')">
            <div class="ui raised segment">
              <div class="ui">
                <div class="content">
                  <h5 class="content">Level 1</h5>
                  <hr>
                  <div><a th:href="@{/level1/1}"><i class="bullhorn icon"></i> Level-1-1</a></div>
                  <div><a th:href="@{/level1/2}"><i class="bullhorn icon"></i> Level-1-2</a></div>
                  <div><a th:href="@{/level1/3}"><i class="bullhorn icon"></i> Level-1-3</a></div>
                </div>
              </div>
            </div>
          </div>

          <div class="column" sec:authorize="hasRole('vip2')">
            <div class="ui raised segment">
              <div class="ui">
                <div class="content">
                  <h5 class="content">Level 2</h5>
                            <hr>
                            <div><a th:href="@{/level2/1}"><i class="bullhorn icon"></i> Level-2-1</a></div>
                            <div><a th:href="@{/level2/2}"><i class="bullhorn icon"></i> Level-2-2</a></div>
                            <div><a th:href="@{/level2/3}"><i class="bullhorn icon"></i> Level-2-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

            <div class="column" sec:authorize="hasRole('vip3')">
                <div class="ui raised segment">
                    <div class="ui">
                        <div class="content">
                            <h5 class="content">Level 3</h5>
                            <hr>
                            <div><a th:href="@{/level3/1}"><i class="bullhorn icon"></i> Level-3-1</a></div>
                            <div><a th:href="@{/level3/2}"><i class="bullhorn icon"></i> Level-3-2</a></div>
                            <div><a th:href="@{/level3/3}"><i class="bullhorn icon"></i> Level-3-3</a></div>
                        </div>
                    </div>
                </div>
            </div>

        </div>
    </div>

</div>


<script th:src="@{/qinjiang/js/jquery-3.1.1.min.js}"></script>
<script th:src="@{/qinjiang/js/semantic.min.js}"></script>

</body>
</html>
登出注销流程

前端设置跳转的连接:

<a class="item" th:href="@{/logout}">
   <i class="address card icon"></i> 注销
</a>

在Security里面开启自动注销的配置

Security5

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
            
    //....
    //开启自动配置的注销的功能
    // /logout 注销请求
    http.logout();
}
// .logoutSuccessUrl("/"); 注销成功来到首页
http.logout().logoutSuccessUrl("/");

Security6

@EnableWebSecurity
@Configuration
public class SecurityConfig  {
            

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
            

        http.authorizeHttpRequests(request -> request
                                   .logout(LogoutConfigurer::permitAll);
        return http.build();
    }
}
内容显示

根据角色判断:<div class="column" sec:authorize="hasRole('vip2')">
根据权限判断:登录:<div sec:authorize="isAuthenticated()">、未登录:<div sec:authorize="!isAuthenticated()">
如果注销404了,就是因为它默认防止csrf跨站请求伪造,因为会产生安全问题;

我们可以将请求改为post表单提交;
或者在spring security中关闭csrf功能;在 配置中增加 http.csrf().disable();

测试项目

admin图片[3] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
manager 图片[4] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
user

设置跳转自己的登录页面和记住账号密码功能

保存cookie账号

//定制请求的授权规则
@Override
protected void configure(HttpSecurity http) throws Exception {
            
   //记住我
   http.rememberMe();
}
 http.authorizeHttpRequests.rememberMe(config -> config
                                        .rememberMeCookieName("remember-me") );

跳转到自己的登录页

// AbstractAuthenticationFilterConfigurer::permitAll  // 登录页面和处理URL允许所有人
http.authorizeHttpRequests.formLogin(
                login -> login.loginPage("/toLogin").permitAll()

退出注销后,跳转登录页面,写一个controller

    @RequestMapping(value = "/logout")
    public String toLogout() {
            
        return "views/login";
    }

默认到根目录请求

效果

Shiro

简介

学习文档:Shiro

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

快速开始

shiro/samples/quickstart/pom.xml at main · apache/shiro

导入依赖

samples/quickstart/pom.xml

配置文件

samples/quickstart/src/main/resources/log4j2.xml

Helloword

samples/quickstart/src/main/java/Quickstart.java

启动成功图片[5] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
分析快速启动的项目,得到Subject的大部分内容

// 获取当前用户 Subject
Subject currentUser = SecurityUtils.getSubject();

// 通过用户获取session
Session session = currentUser.getSession();

// 令牌,判断当前用户是否已经认证
if (!currentUser.isAuthenticated()) {
            }

try {
            
    // 登录操作
    currentUser.login(token);
    
}  catch (UnknownAccountException uae) {
             // 不存在的账户信息
} catch (IncorrectCredentialsException ice) {
             // 账号密码错误
    log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
             // 账户被锁
} catch (AuthenticationException ae) {
             // 认证失败
}
if (currentUser.hasRole("schwartz")) {
            }// 角色判断

// 非实例级别 粗粒度权限
if (currentUser.isPermitted("lightsaber:wield")) {
            }

// 实例级权限 细粒度权限
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            }

currentUser.logout(); // 注销

System.exit(0); // 结束

以前的结构

shiro-springBoot-web

导入shiro的依赖
编写Shiro的核心配置

realm 用户认证授权
ShiroConfig

新建一个shiro项目

选择这些,之后我们再去导入依赖

检查pom.xml,确保无误;
在themleaf创建个首页,编写一个controller,测试springboot项目正常启动;
使用的Springboot3.x,所以看:https://github.com/apache/shiro/tree/main/samples/spring-boot-3-web
引入依赖

        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <version>2.0.0-alpha-4</version>
            <classifier>jakarta</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-web</artifactId>
            <version>2.0.0-alpha-4</version>
            <classifier>jakarta</classifier>
        </dependency>
        
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-web-starter</artifactId>
            <version>2.0.0-alpha-4</version>
            <classifier>jakarta</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring-boot-starter</artifactId>
            <version>2.0.0-alpha-4</version>
            <classifier>jakarta</classifier>
        </dependency>
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>2.0.0-alpha-4</version>
            <classifier>jakarta</classifier>
        </dependency>

编写Configuration 参照samples/spring-boot-3-web/src/main/java/org/apache/shiro/samples/WebApp.java

package com.demo.config;

import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Apache Shiro 核心配置类
 * 用于集成 Shiro 权限框架到 Spring Boot 3.x 应用
 */
@Configuration
public class ShiroConfig {
            

    /**
     * 创建并配置Realm Bean用于权限管理
     *
     * @return 配置好的TextConfigurationRealm实例
     */
    @Bean
    public Realm realm() {
            
        TextConfigurationRealm realm = new TextConfigurationRealm();

        // 配置用户及其对应的权限角色
        // 配置用户:格式为 username = password, role1, role2, ...
        realm.setUserDefinitions(
                "admin=admin,admin,manager,user
"
                        + "manager=manager,manager,user
"
                        + "user=user,user");

        // 配置角色及其关系 这里不是“角色继承”,而是“角色拥有的权限”
        // 配置角色权限(可选):格式为 role = permission1, permission2, ...
        realm.setRoleDefinitions(
                  "admin=*:*:*
"
                + "manager=read:write
"
                + "user=read"
        );

        return realm;
    }

    /**
     * 配置 Shiro 过滤器链
     * 定义哪些路径需要什么样的过滤器(权限控制)
     *
     * @return ShiroFilterChainDefinition 实例
     */
    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
            
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

        // /login 路径允许匿名访问(anon),即未登录用户也可以访问,用于登录页面或登录接口。
        chainDefinition.addPathDefinition("/login", "anon");
        // 访问 /logout 需要登出(logout),即用户可登出。
        chainDefinition.addPathDefinition("/logout", "logout");

        // 访问根路径 / 需要认证(authc),即用户必须已登录才可访问。
        chainDefinition.addPathDefinition("/**", "authc");

        return chainDefinition;
    }

    /**
     * 【关键】配置 SecurityManager
     * 这是 Shiro 的核心,必须显式声明为 Bean
     * shiro-spring-boot-starter 会自动使用这个 Bean
     *
     * @param realm 从 Spring 容器注入的 Realm
     * @return 配置好的 SecurityManager
     */
    @Bean
    public DefaultWebSecurityManager  securityManager(Realm realm) {
            
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager ();
        securityManager.setRealm(realm);
        return securityManager;
    }

}

Shiro导致项目出错

查错,寻找解决方案

期间一直报错java.lang.IllegalStateExceptionClassNotFoundException,多方查找资料后发现是因为 ShiroSpring Boot 版本不兼容导致的。是一个典型的 Jakarta EE 9+ 迁移问题,其中 javax.servlet 包被重命名为 jakarta.servlet

网络上提出的解决方案有下面几种

1. 降级 Spring Boot 版本,降到`SpringBoot 2.x` 使用`javax.servlet`的那个版本,因为本身想根据视频中的思路,学习最新的知识。故而pass;
2. 升级 Shiro 版本,目前所使用的版本就是直接从github上查找所取下来的最新版的master版本,`2.0.0-alpha-4`,所以不存在不是最新的版的情况,先放一边;
3. 添加 Jakarta Servlet API 兼容性依赖,添加一个桥接的依赖包,使`Javax`和`Jakarta`整合,合理;

使用上述方式意义尝试后,依旧提示有问题,又去通义上让AI给写一个Shiro的整合方案,检查后发现少注入的一个SecurityManager,补充后重新测试,程序能正常加载

重新捋一下流程

确定使用的Shiro版本是1.13.0,运行SpringBoot启动类正常
运行时报错 把配置类删掉,
确定使用的Shiro版本是2.0.0,运行SpringBoot启动类提示 java.lang.ClassNotFoundException,提示的内容说 Shiro 仍在引用旧的 javax.servlet 包,导致 ClassNotFoundException。但是在官方说现在已经支持SpringBoot 3.x,所以我们要检查下哪个个地方引的javax.servlet
根据错误堆栈,问题出在 ShiroWebFilterConfiguration.shiroFilterFactoryBean 的条件处理上,根本原因是 ClassNotFoundException: javax.servlet.Filter。,javax.servlet.Filter这个类没有找到;
检查javadoc,问题点依旧存在,底层包依旧使用的javax.servlet图片[6] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns="http://maven.apache.org/POM/4.0.0"
  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>3.5.4</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.demo</groupId>
  <artifactId>springboot-shiro</artifactId>
  <version>0.0.1-SNAPSHOT</version>
  <name>springboot-shiro</name>
  <description>springboot-shiro</description>
  <url/>
  <licenses>
    <license/>
  </licenses>
  <developers>
    <developer/>
  </developers>
  <scm>
    <connection/>
    <developerConnection/>
    <tag/>
    <url/>
  </scm>
  <properties>
    <java.version>17</java.version>
    <shiro.version>2.0.0</shiro.version>
    <!--        <spring-framework.version>6.2.9</spring-framework.version>-->
  </properties>

  <!-- 引入Shiro BOM统一版本管理(可选但推荐) -->
  <dependencyManagement>
    <dependencies>
      <dependency>
        <!--                <groupId>org.apache.shiro</groupId>-->
        <!--                <artifactId>shiro-bom</artifactId>-->
        <!--                <version>2.0.0</version> &lt;!&ndash; 使用最新兼容版本 &ndash;&gt;-->
        <!--                <type>pom</type>-->
        <!--                <scope>import</scope>-->
      </dependency>
      <!--            <dependency>-->
      <!--                <groupId>org.springframework</groupId>-->
      <!--                <artifactId>spring-framework-bom</artifactId>-->
      <!--                <version>6.2.9</version>-->
      <!--                <type>pom</type>-->
      <!--                <scope>import</scope>-->
      <!--            </dependency>-->
    </dependencies>
  </dependencyManagement>

  <dependencies>
    <!-- Shiro Spring Boot Starter (Web环境) -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring-boot-web-starter</artifactId>
      <classifier>jakarta</classifier>
      <version>${shiro.version}</version>
      <!-- 排除可能存在的旧版 shiro-web -->
      <exclusions>
        <exclusion>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
        </exclusion>
      </exclusions>
    </dependency>


    <!-- 显式引入 Shiro Web Jakarta 版本 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <classifier>jakarta</classifier>
      <version>${shiro.version}</version>
      <exclusions>
        <exclusion>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
        </exclusion>
<!--                <exclusion>-->
<!--                    <groupId>org.apache.shiro</groupId>-->
<!--                    <artifactId>shiro-core</artifactId>-->
<!--                </exclusion>-->
            </exclusions>
        </dependency>

<!--        若需手动引入核心组件(如非Spring Boot场景)   -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-core</artifactId>
            <classifier>jakarta</classifier>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>javax.servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
<!--        -->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <classifier>jakarta</classifier>
            <version>${shiro.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>javax.servlet</groupId>
                    <artifactId>javax.servlet-api</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>

        <!--    below dependencies may not be necessary in "real" applications -->
        <dependency>
            <groupId>jakarta.servlet</groupId>
            <artifactId>jakarta.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>4.0.1</version>
            <scope>provided</scope>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </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>
    </dependencies>
    
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

报错的log

The called method's class hierarchy was loaded from the following locations:

    org.springframework.boot.web.servlet.FilterRegistrationBean: file:/D:/work/apache-maven/apache-maven-3.9.3/org/springframework/boot/spring-boot/3.5.4/spring-boot-3.5.4.jar
    org.springframework.boot.web.servlet.AbstractFilterRegistrationBean: file:/D:/work/apache-maven/apache-maven-3.9.3/org/springframework/boot/spring-boot/3.5.4/spring-boot-3.5.4.jar
    org.springframework.boot.web.servlet.DynamicRegistrationBean: file:/D:/work/apache-maven/apache-maven-3.9.3/org/springframework/boot/spring-boot/3.5.4/spring-boot-3.5.4.jar
    org.springframework.boot.web.servlet.RegistrationBean: file:/D:/work/apache-maven/apache-maven-3.9.3/org/springframework/boot/spring-boot/3.5.4/spring-boot-3.5.4.jar


真是服了

手动注册Filter解决?,禁用自动配置,审慎使用AI

/**
 * Apache Shiro 核心配置类
 * 用于集成 Shiro 权限框架到 Spring Boot 3.x 应用
 */
@Configuration
@SpringBootApplication(exclude = {
            
    ShiroBeanAutoConfiguration.class,
    ShiroAnnotationProcessorAutoConfiguration.class,
    ShiroWebFilterConfiguration.class  // 关键:禁用有问题的自动配置
    })
public class ShiroConfigR {
            


    public FilterRegistrationBean<DelegatingFilterProxy> shiroFilterRegistration() {
            
        FilterRegistrationBean<DelegatingFilterProxy> registration = new FilterRegistrationBean<>();
        DelegatingFilterProxy filter = new DelegatingFilterProxy("shiroFilter");
        filter.setTargetFilterLifecycle(true);
        registration.setFilter(filter);
        registration.setEnabled(true);
        registration.addUrlPatterns("/*");
        registration.setOrder(1);

        // 使用新 API,不要用 setDispatcherTypes(...)
        // Spring Boot 3 中应使用 setDispatcherTypes 枚举集合
        registration.setDispatcherTypes(EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD));

        return registration;
    }

    // 使用简单内存 Realm(生产环境替换为数据库查询)
    @Bean
    public Realm realm() {
            
        SimpleAccountRealm realm = new SimpleAccountRealm();
        // 添加一个测试账户
        realm.addAccount("admin", "123456", "admin");
        return realm;
    }

    @Bean
    public DefaultSecurityManager securityManager() {
            
        DefaultSecurityManager manager = new DefaultSecurityManager();
        manager.setRealm(realm());
        return manager;
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean() {
            
        ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean();
        filter.setSecurityManager(securityManager());

        // 设置未登录跳转登录页
        filter.setLoginUrl("/login");

        // 登录成功后跳转首页
        filter.setSuccessUrl("/index");

        // 无权限跳转页
        filter.setUnauthorizedUrl("/unauthorized");

        // 配置拦截规则
        Map<String, String> chain = new LinkedHashMap<>();
        chain.put("/login", "anon");
        chain.put("/doLogin", "anon");
        chain.put("/css/**", "anon");
        chain.put("/js/**", "anon");
        chain.put("/favicon.ico", "anon");
        chain.put("/**", "authc"); // 其他路径需要认证

        filter.setFilterChainDefinitionMap(chain);
        return filter;
    }

SpringBoot2+Shiro

SpringBoot3 不要了 还是 +Spring Security吧

继续排查

根据成功者的项目学习排查错误寻找解决方案,Ruoyi是怎么实现的呢

前提

刷git的时候看到了ruoyi,ruoyi同时支持Spring boot2, spring boot3,而且他的权限模块也是使用的Shiro,去看一看他是怎么实现的。

参照学习: 插件集成 | RuoYi

对比项目差别点,检查自身错误

跟着若依吧他需要的包导进去,

没有使用shiro-spring-boot-starter;myproject:去掉,会不会使用依赖移动器找不到导致的导入失败;
重新定义了spring-webspring-webmvc;myproject:引入并指定版本
引入使用了jakarta.servlet-api,没有javax.servlet-api;myproject:删掉
为了使用Shiro 单独引入使用了shiro-core,shiro-spring,shiro-web,在shiro-web排除了shiro-web,并且都指定使用了jakartaclassifier
同一个pom.xml中引入的依赖,同一个包下所使用的版本都是同一个version

检查错误信息

发现导入 的Maven仓库里有两个spring-web,是不是这里出现的问题

mvn clean install ,清除缓存重新再引入依赖
删除不起作用反而会产生干扰的包

    <!-- 引入Shiro BOM统一版本管理(可选但推荐) -->
    <dependencyManagement>
        <dependencies>
           <dependency>
               <groupId>org.apache.shiro</groupId>
               <artifactId>shiro-bom</artifactId>
               <!-- 使用最新兼容版本 -->
               <version>${shiro.version}</version> 
               <type>pom</type>
               <scope>import</scope>
           </dependency>
           <dependency>
               <groupId>org.springframework</groupId>
               <artifactId>spring-framework-bom</artifactId>
               <version>6.2.9</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
        </dependencies>
    </dependencyManagement>

重新mvn clean install,检查依赖,没有Spring5.3.2的依赖了,可能是解决了 图片[7] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
验证,检查各个包中主要使用的类,是否都抛弃了javax换成了Jakarta 图片[8] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马图片[8] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
项目启动测试,没有问题 图片[9] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
为避免同情况发送,决定统一依赖包 使用<poperites> </poperites>管理

测试Shiro

编写认证授权的Relam

package com.demo.config;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;

public class UserRealm extends AuthorizingRealm {
            

    /**
     * 获取授权信息 授权
     *
     * @param principals 身份凭证集合,包含用户的身份信息
     * @return AuthorizationInfo 授权信息对象,包含用户的权限信息
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            
        // 创建简单的授权信息对象
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

        // 添加用户相关权限
        info.addStringPermission("user:add");
        info.addStringPermission("user:delete");
        info.addStringPermission("user:update");

        System.out.println("执行了授权-----》");
        return info;
    }

    /**
     * 执行身份认证信息获取操作 认证
     *
     * @param token 认证令牌,包含用户提交的认证信息
     * @return AuthenticationInfo 认证信息对象,包含正确的用户名和密码
     * @throws AuthenticationException 认证异常,当认证过程中出现错误时抛出
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            

        // 创建简单的认证信息对象,使用硬编码的用户名"admin"和密码"123456"
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo("admin", "123456", getName());

        System.out.println("执行了认证----》");

        return info;
    }
}

控制器

import org.apache.shiro.authz.annotation.RequiresAuthentication;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {
            

    @GetMapping("/index")
    @RequiresAuthentication
    public String index() {
            
        return "index";
    }

    @GetMapping("/unauthorized")
    public String unauthorized() {
            
        return "unauthorized";
    }
}

跳转的页面编写

测试

编写登录拦截

AbstractShiroWebFilterConfiguration这个类中,注入的时候根据所需要的内容给加载进去;

在前面的Autowired没有了ShiroFilterFactoryBean,多出了shiroFilterChainDefinition

往后看,在ShiroFilterFactoryBean的方法中,将shiroFilterChainDefinitiongetFilterChainMapset到 ShiroFilterFactoryBean中;

我们可以不去注入ShiroFilterChainDefinition这个方法,直接将所有的内容直接set进去;图片[10] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
但是最好还是再写一个ShiroFilterChainDefinition,毕竟ShiroFilterFactoryBean虽然仍然是作为整个 Shiro 权限的入口,但是使用shiroFilterChainDefinition 这个SpringBean可以更好的实现配置解耦;图片[11] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马图片[11] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
Shiro 初始化流程

Spring Boot 启动
     ↓
加载 Shiro 自动配置类 (AbstractShiroWebFilterConfiguration)
     ↓
创建 SecurityManager Bean
     ↓
创建 ShiroFilterChainDefinition Bean(定义 URL 权限规则)
     ↓
调用 shiroFilterFactoryBean() 方法
     ↓
设置 loginUrl / successUrl / unauthorizedUrl
     ↓
绑定 SecurityManager
     ↓
设置 filterChainDefinitionMap(核心权限规则)
     ↓
生成 ShiroFilterFactoryBean → 创建 ShiroFilter
     ↓
注册到 Spring Web Filter Chain
     ↓
拦截所有请求,执行认证授权逻辑

本着存疑的心态去社区看Sample,确实也是使用的ShiroFilterChainDefinition

/*
 * 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.
 */
package org.apache.shiro.samples;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.realm.text.TextConfigurationRealm;
import org.apache.shiro.spring.web.config.DefaultShiroFilterChainDefinition;
import org.apache.shiro.spring.web.config.ShiroFilterChainDefinition;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.ResponseStatus;

import java.util.HashMap;
import java.util.Map;

@Configuration
@ControllerAdvice
@SpringBootApplication
public class WebApp {
            

    private static Logger log = LoggerFactory.getLogger(WebApp.class);

    public static void main(String[] args) {
            

        SpringApplication.run(WebApp.class, args);
    }

    @ExceptionHandler(AuthorizationException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public String handleException(AuthorizationException e, Model model) {
            

        // you could return a 404 here instead (this is how github handles 403, so the user does NOT know there is a
        // resource at that location)
        log.debug("AuthorizationException was thrown", e);

        Map<String, Object> map = new HashMap<String, Object>();
        map.put("status", HttpStatus.FORBIDDEN.value());
        map.put("message", "No message available");
        model.addAttribute("errors", map);

        return "error";
    }

    @Bean
    public Realm realm() {
            
        TextConfigurationRealm realm = new TextConfigurationRealm();
        realm.setUserDefinitions("joe.coder=password,user
" + "jill.coder=password,admin");

        realm.setRoleDefinitions("admin=read,write
" + "user=read");
        realm.setCachingEnabled(true);
        return realm;
    }

    @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
            
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        // need to accept POSTs from the login form
        chainDefinition.addPathDefinition("/login.html", "authc");
        chainDefinition.addPathDefinition("/logout", "logout");
        return chainDefinition;
    }

    @ModelAttribute(name = "subject")
    public Subject subject() {
            
        return SecurityUtils.getSubject();
    }
}

测试,页面正常被拦截

实现用户认证

根据quickstart,编写认证操作

// 获取当前用户 Subject
Subject currentUser = SecurityUtils.getSubject();

// 通过用户获取session
Session session = currentUser.getSession();

// 令牌,判断当前用户是否已经认证
if (!currentUser.isAuthenticated()) {
            }

try {
            
    // 登录操作
    currentUser.login(token);
} catch (IncorrectCredentialsException ice) {
             // 账号密码错误
    log.info("Password for account " + token.getPrincipal() + " was incorrect!");

    if (currentUser.hasRole("schwartz")) {
            }// 角色判断

    // 非实例级别 粗粒度权限
    if (currentUser.isPermitted("lightsaber:wield")) {
            }

    // 实例级权限 细粒度权限
    if (currentUser.isPermitted("winnebago:drive:eagle5")) {
            }

    currentUser.logout(); // 注销

    System.exit(0); // 结束

新建一个/loginController,使用一个post请求提交表单,做简单处理

@PostMapping("/login")
public String doLogin(@RequestParam String username, @RequestParam String password,Model model) {
            
    System.out.println("username--------->"+username + "  password--------->"+password);
    Subject subject = SecurityUtils.getSubject();
    // 创建token
    UsernamePasswordToken token = new UsernamePasswordToken(username, password);
    System.out.println("token--------->"+token);
    try {
            
        // 登录
        subject.login(token);
        return "index";
    } catch (UnknownAccountException e) {
             // 账号请求
        model.addAttribute("error", "用户不存在");

        return "login";
        //            return "redirect:/login?error";

    } catch (AuthenticationException e) {
             // 权限认证
        model.addAttribute("error", "用户名或密码错误");
        return "login";
        //            return "redirect:/login?error";
    }
}

写一个login.html测试请求

新建一个/loginController,使用一个get请求跳转页面,测试 Shiro过滤器工厂Bean是否在正常加载

@GetMapping("/login")
public String login(@RequestParam(required = false) String error, Model model) {
            

    // 获取当前用户
    if (error != null) {
            
        model.addAttribute("error", error+"Shiro login 页面");
    }
    model.addAttribute("error", "Shiro login 页面");

    return "login";
}

在获取认证信息验证Shiro的拦截操作,写了一个令牌,通过log看流程

这里犯了错误,只用SimpleAuthenticationInfo只会进行权限认证,出现错误只会AuthenticationException,而我们会用到的有多种场景;

try {
            
    // 登录操作
    currentUser.login(token);
}  catch (UnknownAccountException uae) {
             // 不存在的账户信息
} catch (IncorrectCredentialsException ice) {
             // 账号密码错误
    log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) {
             // 账户被锁
} catch (AuthenticationException ae) {
             // 认证失败
}

对应修改内容

执行

正确的场合,页面正常加载,进入后面的页面 图片[12] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马图片[12] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
错误的场合,返回login页面,响应post请求的login内容,返回在post请求下的结果

根据步骤5 所验证的思路,使用Shiro的操作流程如下:

页面初次加载,指定登录页面,通过get请求进入登录页;
在登录页填写信息提交表单,将表单数据通过Pos请求提交至控制器;
调用AuthenticationInfo,获取实际授权的用户加密的令牌信息info
在控制器调用Subject,将在AuthenticationInfo 的info获取过去;
通过UsernamePasswordToken将我们的账号密码加密成令牌;
调用subject.login方法执行登录操作,期间对两个加密的token进行匹配;
监听这个subject的操作,正常则继续进行页面跳转,将token保存在cookie中;
捕获到异常则执行异常的处理逻辑

关联数据库实现用户认证

导入依赖

导入数据库驱动
导入数据源Druid
导入Mybatis
导入log4j,Druid和Mybatis使用

<dependency>
  <groupId>com.mysql</groupId>
  <artifactId>mysql-connector-j</artifactId>
  <scope>runtime</scope>
</dependency>

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid-spring-boot-3-starter</artifactId>
  <version>1.2.20</version> <!-- 请检查Maven仓库获取最新版本 -->
</dependency>

<dependency>
  <groupId>org.mybatis.spring.boot</groupId>
  <artifactId>mybatis-spring-boot-starter</artifactId>
  <version>3.0.5</version>
</dependency>

<!-- configure logging -->
<dependency>
  <groupId>org.slf4j</groupId>
  <artifactId>jcl-over-slf4j</artifactId>
  <version>1.7.25</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-slf4j2-impl</artifactId>
  <version>2.24.3</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.17.1</version>
  <scope>runtime</scope>
</dependency>

建包,mapper,service,pojo;
配置配置文件,数据库,映射包;
写接口,实现方法;
测试数据库连接,方法正常映射,数据正常抽出;图片[13] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
修改Realm的令牌验证的方法

设置加密

虽然实现了这个认证功能,但是密码还是明文显示的

找方法体,在SimpleAuthenticationInfo方法实现SaltedAuthenticationInfoMergableAuthenticationInfo,
用于对账户凭证进行加盐处理的盐值;若未使用盐值,则为 null图片[14] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
查看ByteSource,匹配实现方法

ByteSource.Util.bytes(user.getPassword())对Password进行加密;
测试,info的信息已经是加密后的了

实现用户授权

准备工作

@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition() {
            

    DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();

    // 添加Shiro的内置过滤器
    /*
        anno :   匿名用户可访问
        authc :  认证用户可访问
        logout : 登出
        user :   用户认证通过或RememberMe登录的都可以访问
        perms :  该资源必须得到资源权限才能访问
     */

    // 配置过滤器链映射关系
    Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
    filterChainDefinitionMap.put("/**", "authc");
    filterChainDefinitionMap.put("/login", "anon");
    filterChainDefinitionMap.put("/logout", "logout");
    filterChainDefinitionMap.put("/css/**", "anon");
    filterChainDefinitionMap.put("/js/**", "anon");

    // 添加其他路径=过滤器定义
    chainDefinition.addPathDefinitions(filterChainDefinitionMap);

    // 配置单独页面请求根据权限访问
    chainDefinition.addPathDefinition("/user/add", "perms[user:add]");
    chainDefinition.addPathDefinition("/user/update", "perms[user:update]");

    return chainDefinition;
}

设置perms

查看doGetAuthorizationInfo为默认状态,测试;

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            
// 创建简单的授权信息对象
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();

System.out.println("执行了授权-----》");

//        // 添加用户相关权限
//        info.addStringPermission("user:add");
//        info.addStringPermission("user:delete");
//        info.addStringPermission("user:update");
System.out.println(info);

return info;
}

设置未授权页面的跳转,测试

@GetMapping("/unauthorized")
@ResponseBody
public String noAuth() {
            
    return "未授权页面";
}

doGetAuthorizationInfo增加一个权限user:add

结合上面的内容测试,预定现象:add页面能够跳转,update页面跳转未授权;测试完成

关联数据库实现用户授权

在数据库中加一个字段perms,将前面我们所写入的user:add数据写入;图片[15] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
对应的实体类内容同步更新;

在用户认证的时候去取出用户信息,在info中利用principal字段,将 用户信息 传入Session中; 图片[16] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
AuthorizationInfo中,读取Subject对象的内容,addPerms中;图片[17] - 【SpringBoot】SpringBoot 中的 Shiro、Spring Security 学习过程及碰到的问题和解决方法 - 宋马
测试,root用户只能进add,不能进update,测试ok

整合前端代码

准备工作

导入依赖,若依用到这个

<!-- thymeleaf模板引擎和shiro框架的整合 -->
<dependency>
  <groupId>com.github.theborakompanioni</groupId>
  <artifactId>thymeleaf-extras-shiro</artifactId>
</dependency>

学习期间还是审慎好些,去这里找,最新版是2.1.0

<dependency>
  <groupId>com.github.theborakompanioni</groupId>
  <artifactId>thymeleaf-extras-shiro</artifactId>
  <version>2.1.0</version>
</dependency>

配置整合Shiro+thymeleaf;

// 配置ShiroDialect,用于thymeleaf中使用Shiro标签
@Bean
public ShiroDialect shiroDialect(){
            
    return new ShiroDialect();
}

实现代码

导入命名空间,xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"

<html lang="zh"
  xmlns:th="http://www.thymeleaf.org"
  xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">

写入内容,根据权限显示内容;

    <div shiro:hasPermission="user:add">
        <a href="/user/add">add</a>
    </div>
    <div shiro:hasPermission="user:update">
        <a href="/user/update">update</a>
    </div>

测试,根据既定想象内容正常显示 ;

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容