当前位置: 首页 > article >正文

SpringBoot + Shiro权限管理

一、Spring Boot 与 Shiro 整合基础

1. Shiro 简介

Apache Shiro 是一个强大且易用的 Java 安全框架,它提供了认证、授权、加密和会话管理等功能。通过 Shiro,我们可以轻松地实现用户身份验证和权限控制,保护应用程序的安全。

2. 在 Spring Boot 中集成 Shiro

首先,在 Spring Boot 项目的依赖中添加 Shiro 的相关依赖。例如:

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring-boot-starter</artifactId>
    <version>1.7.1</version>
</dependency>

然后,配置 Shiro 的相关 Bean,如SecurityManagerRealm等。一个简单的示例如下:

@Configuration
public class ShiroConfig {

    @Bean
    public SecurityManager securityManager(MyRealm myRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myRealm);
        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        // 配置登录页面等
        factoryBean.setLoginUrl("/login");
        factoryBean.setSuccessUrl("/index");
        factoryBean.setUnauthorizedUrl("/unauthorized");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 配置一些公共资源无需认证
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        // 其他请求都需要认证
        filterChainDefinitionMap.put("/**", "authc");
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return factoryBean;
    }

    // 自定义Realm
    @Bean
    public MyRealm myRealm() {
        return new MyRealm();
    }
}

这里的MyRealm是我们自定义的实现了Shiro Realm接口的类,用于进行用户认证和授权的逻辑处理。

二、使用 Thymeleaf 模板引擎开发动态菜单

1. Thymeleaf 简介

Thymeleaf 是一种现代的服务器端 Java 模板引擎,它能够在 Web 和非 Web 环境中使用。Thymeleaf 的主要优势在于它能够很好地与 Spring 框架集成,并且具有良好的 HTML 模板可读性,允许开发人员在模板中使用自然的 HTML 语法,同时还能动态地渲染数据和处理逻辑。

2. 数据准备

在后端,我们需要准备好菜单的数据结构。通常可以是一个包含菜单信息的对象列表,每个对象包含菜单的名称、链接、图标等信息,以及该菜单对应的权限标识。例如:

public class Menu {
    private String name;
    private String link;
    private String icon;
    private String permission;

    // 构造函数、Getter和Setter方法
}

然后,在服务层或控制器中,获取当前用户具有的菜单权限列表,并将其传递给视图层。假设我们有一个MenuService类来处理菜单相关的业务逻辑:

@Service
public class MenuService {

    public List<Menu> getMenusForUser(String username) {
        // 根据用户名获取用户的权限列表
        List<String> userPermissions = getUserPermissions(username);

        // 假设这里有一个所有菜单的列表
        List<Menu> allMenus = getAllMenus();

        // 过滤出用户具有权限的菜单
        List<Menu> userMenus = allMenus.stream()
               .filter(menu -> userPermissions.contains(menu.getPermission()))
               .collect(Collectors.toList());

        return userMenus;
    }

    private List<String> getUserPermissions(String username) {
        // 这里可以从数据库或其他数据源获取用户的权限
        // 为了简单示例,假设用户具有"menu1"和"menu2"权限
        return Arrays.asList("menu1", "menu2");
    }

    private List<Menu> getAllMenus() {
        // 这里返回所有菜单的列表,实际应用中可以从数据库获取
        List<Menu> menus = new ArrayList<>();
        Menu menu1 = new Menu("首页", "/index", "icon-home", "menu1");
        Menu menu2 = new Menu("用户管理", "/user", "icon-user", "menu2");
        Menu menu3 = new Menu("订单管理", "/order", "icon-order", "menu3");
        menus.add(menu1);
        menus.add(menu2);
        menus.add(menu3);
        return menus;
    }
}

3. 在 Thymeleaf 模板中渲染菜单

在 HTML 模板文件中,我们可以使用 Thymeleaf 的语法来循环渲染菜单。例如,在index.html文件中:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>首页</title>
</head>
<body>
    <div class="sidebar">
        <ul>
            <li th:each="menu : ${menus}" th:if="${menu.link!= null}">
                <a th:href="${menu.link}" th:text="${menu.name}">
                    <i th:if="${menu.icon!= null}" class="th:text-${menu.icon}"></i>
                </a>
            </li>
        </ul>
    </div>
</body>
</html>

在控制器中,将获取到的菜单列表传递给模板:

@Controller
public class IndexController {

    @Autowired
    private MenuService menuService;

    @RequestMapping("/index")
    public String index(Model model, Principal principal) {
        String username = principal.getName();
        List<Menu> menus = menuService.getMenusForUser(username);
        model.addAttribute("menus", menus);
        return "index";
    }
}

这样,在用户登录后访问首页时,页面上将会根据用户的权限动态显示相应的菜单。

三、指派权限功能实现

1. 权限数据模型设计

首先,我们需要设计权限的数据模型。可以创建一个权限实体类,包含权限的名称、描述等信息。例如:

@Entity
public class Permission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String description;

    // 构造函数、Getter和Setter方法
}

2. 页面设计与交互

创建一个用于指派权限的页面,例如permissionAssign.html。在页面上,可以使用表单来选择用户和要赋予的权限。可以使用 Thymeleaf 的表单绑定功能来实现数据的交互。例如:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>指派权限</title>
</head>
<body>
    <h1>指派权限</h1>
    <form th:action="@{/assignPermission}" method="post">
        <label for="username">用户:</label>
        <select id="username" name="username">
            <option th:each="user : ${users}" th:value="${user.username}" th:text="${user.username}"></option>
        </select>
        <label for="permission">权限:</label>
        <select id="permission" name="permission">
            <option th:each="perm : ${permissions}" th:value="${perm.name}" th:text="${perm.name}"></option>
        </select>
        <input type="submit" value="指派权限">
    </form>
</body>
</html>

在控制器中,获取用户列表和权限列表,并将其传递给页面。同时,处理表单提交的逻辑,实现权限的指派。例如:

@Controller
public class PermissionController {

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @RequestMapping("/assignPermission")
    public String assignPermission(String username, String permission) {
        // 根据用户名获取用户对象
        User user = userService.getUserByUsername(username);
        // 根据权限名称获取权限对象
        Permission perm = permissionService.getPermissionByName(permission);
        // 为用户赋予权限(这里假设在User实体中有一个与Permission关联的集合来存储用户的权限)
        user.getPermissions().add(perm);
        userService.updateUser(user);
        return "redirect:/success";
    }

    @RequestMapping("/permissionAssign")
    public String permissionAssignPage(Model model) {
        List<User> users = userService.getAllUsers();
        List<Permission> permissions = permissionService.getAllPermissions();
        model.addAttribute("users", users);
        model.addAttribute("permissions", permissions);
        return "permissionAssign";
    }
}

3. 服务层逻辑实现

在服务层中,实现获取用户列表、权限列表以及更新用户权限等相关方法。例如,UserServicePermissionService的部分代码如下:

@Service
public class UserService {

    @Autowired
    private UserRepository userRepository;

    public User getUserByUsername(String username) {
        return userRepository.findByUsername(username);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public void updateUser(User user) {
        userRepository.save(user);
    }
}

@Service
public class PermissionService {

    @Autowired
    private PermissionRepository permissionRepository;

    public Permission getPermissionByName(String name) {
        return permissionRepository.findByName(name);
    }

    public List<Permission> getAllPermissions() {
        return permissionRepository.findAll();
    }
}

这里假设使用了 Spring Data JPA 来进行数据访问操作,UserRepositoryPermissionRepository是对应的 JPA Repository 接口。

四、Shiro 清空用户权限缓存

在某些情况下,当用户的权限发生变化时,我们需要及时清空 Shiro 中该用户的权限缓存,以确保用户下次访问时能够获取到最新的权限信息。

1. 使用 Shiro 的缓存管理器

Shiro 通常会使用缓存来存储用户的认证和授权信息,以提高性能。要清空用户权限缓存,我们需要获取到 Shiro 的缓存管理器。首先,在MyRealm中注入缓存管理器:

@Configuration
public class MyRealm extends AuthorizingRealm {

    @Autowired
    private CacheManager cacheManager;

    // 其他代码...
}

2. 实现清空缓存的方法

MyRealm中添加一个方法来清空指定用户的权限缓存。例如:

public void clearUserCache(String username) {
    // 获取缓存对象
    Cache<String, AuthorizationInfo> cache = cacheManager.getCache("authorizationCache");
    if (cache!= null) {
        // 移除用户的授权信息缓存
        cache.remove(username);
    }
}

这里的"authorizationCache"是 Shiro 默认用于存储授权信息的缓存名称,可以根据实际配置进行调整。

3. 在权限更新后调用清空缓存方法

在服务层中,当用户的权限发生更新操作(如指派权限成功后),调用MyRealm中的clearUserCache方法来清空该用户的权限缓存。例如,在PermissionControllerassignPermission方法中添加如下代码:

@Controller
public class PermissionController {

    // 其他代码...

    @RequestMapping("/assignPermission")
    public String assignPermission(String username, String permission) {
        // 权限指派逻辑...
        // 清空用户权限缓存
        MyRealm myRealm = (MyRealm) SecurityUtils.getSubject().getPrincipals().getRealm();
        myRealm.clearUserCache(username);
        return "redirect:/success";
    }
}

这样,当用户的权限被更新后,Shiro 下次在进行授权验证时,会重新从数据源获取用户的最新权限信息,保证了权限的实时性。

五、更新过滤器

在 Shiro 的配置中,过滤器用于对用户的请求进行拦截和处理,根据不同的规则决定用户是否有权访问相应的资源。有时候,我们可能需要更新过滤器的配置,例如添加新的过滤规则或修改现有规则的逻辑。

1. 修改 ShiroFilterFactoryBean 的配置

ShiroConfig类中,我们可以通过修改ShiroFilterFactoryBeanfilterChainDefinitionMap来更新过滤器的配置。例如,如果我们要添加一个新的过滤规则,允许特定角色的用户访问某个新的路径,可以这样修改:

@Configuration
public class ShiroConfig {

    // 其他代码...

    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
        factoryBean.setSecurityManager(securityManager);
        // 配置登录页面等
        factoryBean.setLoginUrl("/login");
        factoryBean.setSuccessUrl("/index");
        factoryBean.setUnauthorizedUrl("/unauthorized");

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 配置一些公共资源无需认证
        filterChainDefinitionMap.put("/static/**", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        // 其他请求都需要认证
        filterChainDefinitionMap.put("/**", "authc");
        // 添加新的过滤规则
        filterChainDefinitionMap.put("/newPath/**", "roles[admin]");
        factoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return factoryBean;
    }
}

这里的/newPath/**是新的路径,roles[admin]表示只有具有admin角色的用户才能访问该路径。

2. 动态更新过滤器(可选)

如果需要在运行时动态更新过滤器的配置,而不是在项目启动时就固定配置,可以考虑使用一些更灵活的方式。例如,可以将过滤器配置存储在数据库中,然后在应用运行时定期从数据库读取最新的配置并更新 Shiro 的过滤器。这需要额外的代码来实现读取数据库配置和更新 Shiro 过滤器的逻辑。一种简单的思路是创建一个定时任务,每隔一段时间检查数据库中的过滤器配置是否有变化,如果有变化则更新ShiroFilterFactoryBeanfilterChainDefinitionMap

六、初始权限设置

当新用户注册或系统初始化时,可能需要为用户设置一些初始权限。这可以在用户创建或系统启动时进行处理。

1. 在用户注册时设置初始权限

假设我们有一个用户注册的流程,在用户注册成功后,可以为用户赋予一些默认的权限。例如,在UserServiceregisterUser方法中(假设存在这样一个方法)添加权限设置的逻辑:

@Service
public class UserService {

    // 其他代码...

    public void registerUser(User user) {
        // 注册用户逻辑...
        // 设置初始权限
        Permission defaultPerm = permissionService.getPermissionByName("defaultPermission");
        user.getPermissions().add(defaultPerm);
        userRepository.save(user);
    }
}

这里假设存在一个名为"defaultPermission"的默认权限,在用户注册时将其赋予用户。

2. 系统初始化时设置权限

如果是系统初始化,例如在项目启动时为一些特殊用户或角色设置初始权限,可以在一个初始化方法中进行处理。可以使用 Spring 的@PostConstruct注解来标记一个方法,使其在 Bean 创建后执行初始化逻辑。例如,在PermissionInitializer类中:

@Component
public class PermissionInitializer {

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @PostConstruct
    public void initPermissions() {
        // 查找管理员用户(假设管理员用户的用户名是"admin")
        User adminUser = userService.getUserByUsername("admin");
        if (adminUser == null) {
            // 如果管理员用户不存在,则创建并设置初始权限
            adminUser = new User("admin", "password123", "Admin User");
            Permission adminPerm = permissionService.getPermissionByName("adminPermission");
            adminUser.getPermissions().add(adminPerm);
            userService.registerUser(adminUser);
        } else {
            // 如果管理员用户已存在,检查并确保其具有必要的权限
            Permission adminPerm = permissionService.getPermissionByName("adminPermission");
            if (!adminUser.getPermissions().contains(adminPerm)) {
                adminUser.getPermissions().add(adminPerm);
                userService.updateUser(adminUser);
            }
        }
    }
}

这里在系统启动时,检查管理员用户是否存在,如果不存在则创建并赋予"adminPermission"权限,如果已存在则确保其具有该权限。

通过以上步骤,我们在 Spring Boot 项目中实现了基于 Shiro 的权限管理功能,包括使用 Thymeleaf 模板引擎开发动态菜单和指派权限功能,以及处理 Shiro 中的清空用户权限缓存、更新过滤器和初始权限设置等重要操作。这样可以有效地控制用户对系统资源的访问,提高系统的安全性和可用性。在实际应用中,可以根据具体的业务需求进一步扩展

如何使用 Thynekeaf 模板引擎开发动态菜单?

分享一些关于 Spring Boot + Shiro 权限管理的实战案例

如何使用 Shiro 实现会话管理?


http://www.kler.cn/a/373733.html

相关文章:

  • 新材料产业数据管理:KPaaS平台的创新驱动
  • 数字IC开发:布局布线
  • Chrome浏览器音/视频无法自动播放
  • 开源Verilog EDA工具--iverilog+GTKWave
  • 机器学习领域如何做小样本训练背后的原理和逻辑
  • 快速入门HTML
  • Linux下EDAC功能介绍
  • Linux第二讲:Linux权限理解
  • 若依框架部署到服务器后头像资源访问404
  • 图片懒加载(自定义指令)
  • 共享内存相关知识点
  • 架构师备考-数据库基础
  • 安科瑞AM5SE-IS 防逆流保护装置 功能全面 逆功率保护
  • Linux安装部署数据库:MongoDB
  • Redis高级篇之bigKey理论介绍以及优化
  • STATCOM静止同步补偿器原理及MATLAB仿真模型
  • Windows1学习笔记
  • 易考八股文之SpringBoot的启动流程
  • 【UBuntu20 配置usb网卡】 记录Ubuntu20配置usb网卡(特别是建立热点)
  • 企业奇门与金蝶云星空的数据集成解决方案
  • 数据库编程 SQLITE3 Linux环境
  • 必应Bing国内搜索广告代理商,必应广告如何开户投放?
  • Flink + Kafka 实现通用流式数据处理详解
  • 钉钉报销数据与金蝶云星空系统的集成解决方案
  • 前端如何安全存储密钥,防止信息泄露
  • JS:列表操作