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,如SecurityManager
、Realm
等。一个简单的示例如下:
@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. 服务层逻辑实现
在服务层中,实现获取用户列表、权限列表以及更新用户权限等相关方法。例如,UserService
和PermissionService
的部分代码如下:
@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 来进行数据访问操作,UserRepository
和PermissionRepository
是对应的 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
方法来清空该用户的权限缓存。例如,在PermissionController
的assignPermission
方法中添加如下代码:
@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
类中,我们可以通过修改ShiroFilterFactoryBean
的filterChainDefinitionMap
来更新过滤器的配置。例如,如果我们要添加一个新的过滤规则,允许特定角色的用户访问某个新的路径,可以这样修改:
@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 过滤器的逻辑。一种简单的思路是创建一个定时任务,每隔一段时间检查数据库中的过滤器配置是否有变化,如果有变化则更新ShiroFilterFactoryBean
的filterChainDefinitionMap
。
六、初始权限设置
当新用户注册或系统初始化时,可能需要为用户设置一些初始权限。这可以在用户创建或系统启动时进行处理。
1. 在用户注册时设置初始权限
假设我们有一个用户注册的流程,在用户注册成功后,可以为用户赋予一些默认的权限。例如,在UserService
的registerUser
方法中(假设存在这样一个方法)添加权限设置的逻辑:
@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 实现会话管理?