使用 Shiro 和 JPA 结合 MySQL 实现一个简易权限管理系统
1. 项目设置
首先,确保你的项目已经配置好 Maven 或 Gradle 依赖管理工具,并添加以下依赖:
Maven 依赖
<dependencies>
<!-- Shiro 核心库 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.9.0</version>
</dependency>
<!-- Shiro Web 支持 -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.9.0</version>
</dependency>
<!-- JPA 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
<!-- Spring Boot Web 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot 安全支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- 其他依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
2. 配置 MySQL 数据库
在 application.properties
或 application.yml
中配置 MySQL 数据库连接:
spring.datasource.url=jdbc:mysql://localhost:3306/shiro_demo
spring.datasource.username=root
spring.datasource.password=yourpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
3. 创建实体类
使用 JPA 创建用户、角色和权限的实体类。
用户实体 (User)
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
private Set<Role> roles;
// Getters and Setters
}
角色实体 (Role)
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(fetch = FetchType.EAGER)
private Set<Permission> permissions;
// Getters and Setters
}
权限实体 (Permission)
@Entity
public class Permission {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Getters and Setters
}
4. 创建 Repository 接口
使用 Spring Data JPA 创建 Repository 接口。
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
public interface RoleRepository extends JpaRepository<Role, Long> {
}
public interface PermissionRepository extends JpaRepository<Permission, Long> {
}
5. 配置 Shiro
创建一个 Shiro 配置类,配置 Realm 和 SecurityManager。
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(Realm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
return securityManager;
}
@Bean
public Realm realm(UserRepository userRepository) {
return new JpaRealm(userRepository);
}
}
6. 自定义 Realm
创建一个自定义的 Realm 类,用于从数据库中获取用户信息。
public class JpaRealm extends AuthorizingRealm {
private final UserRepository userRepository;
public JpaRealm(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
User user = userRepository.findByUsername(username);
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
for (Role role : user.getRoles()) {
authorizationInfo.addRole(role.getName());
for (Permission permission : role.getPermissions()) {
authorizationInfo.addStringPermission(permission.getName());
}
}
return authorizationInfo;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String username = (String) token.getPrincipal();
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UnknownAccountException("User not found");
}
return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName());
}
}
7. 创建控制器
创建一个简单的控制器来处理登录和访问控制。
@Controller
public class HomeController {
@GetMapping("/home")
public String home() {
return "home";
}
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/login")
public String doLogin(@RequestParam String username, @RequestParam String password) {
Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);
return "redirect:/home";
} catch (AuthenticationException e) {
return "redirect:/login?error";
}
}
@GetMapping("/logout")
public String logout() {
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "redirect:/login";
}
}
8. 创建视图
创建简单的 Thymeleaf 视图来展示登录页面和主页。
login.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Login</title>
</head>
<body>
<h1>Login</h1>
<form th:action="@{/login}" method="post">
<label for="username">Username:</label>
<input type="text" id="username" name="username" required>
<br>
<label for="password">Password:</label>
<input type="password" id="password" name="password" required>
<br>
<button type="submit">Login</button>
</form>
<p th:if="${param.error}" style="color: red;">Invalid username or password</p>
</body>
</html>
运行 HTML
home.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<title>Home</title>
</head>
<body>
<h1>Welcome to the Home Page</h1>
<a th:href="@{/logout}">Logout</a>
</body>
</html>
9. 运行项目
启动 Spring Boot 应用程序,访问 http://localhost:8080/login
进行登录。登录成功后,你将能够访问 /home
页面。
10. 权限控制
你可以在控制器中使用 @RequiresRoles
或 @RequiresPermissions
注解来控制访问权限。
@Controller
public class AdminController {
@RequiresRoles("admin")
@GetMapping("/admin")
public String admin() {
return "admin";
}
}
11、创建测试数据脚本
-- 插入权限数据
INSERT INTO permission (name) VALUES ('user:read');
INSERT INTO permission (name) VALUES ('user:write');
INSERT INTO permission (name) VALUES ('admin:read');
INSERT INTO permission (name) VALUES ('admin:write');
-- 插入角色数据
INSERT INTO role (name) VALUES ('user');
INSERT INTO role (name) VALUES ('admin');
-- 关联角色和权限
-- 用户角色拥有 user:read 和 user:write 权限
INSERT INTO role_permissions (role_id, permissions_id) VALUES (
(SELECT id FROM role WHERE name = 'user'),
(SELECT id FROM permission WHERE name = 'user:read')
);
INSERT INTO role_permissions (role_id, permissions_id) VALUES (
(SELECT id FROM role WHERE name = 'user'),
(SELECT id FROM permission WHERE name = 'user:write')
);
-- 管理员角色拥有所有权限
INSERT INTO role_permissions (role_id, permissions_id) VALUES (
(SELECT id FROM role WHERE name = 'admin'),
(SELECT id FROM permission WHERE name = 'user:read')
);
INSERT INTO role_permissions (role_id, permissions_id) VALUES (
(SELECT id FROM role WHERE name = 'admin'),
(SELECT id FROM permission WHERE name = 'user:write')
);
INSERT INTO role_permissions (role_id, permissions_id) VALUES (
(SELECT id FROM role WHERE name = 'admin'),
(SELECT id FROM permission WHERE name = 'admin:read')
);
INSERT INTO role_permissions (role_id, permissions_id) VALUES (
(SELECT id FROM role WHERE name = 'admin'),
(SELECT id FROM permission WHERE name = 'admin:write')
);
-- 插入用户数据
-- 密码使用 Shiro 的加密方式(例如 MD5 加密)
INSERT INTO user (username, password) VALUES ('user1', 'password1');
INSERT INTO user (username, password) VALUES ('admin1', 'password1');
-- 关联用户和角色
-- 用户 user1 拥有 user 角色
INSERT INTO user_roles (user_id, roles_id) VALUES (
(SELECT id FROM user WHERE username = 'user1'),
(SELECT id FROM role WHERE name = 'user')
);
-- 用户 admin1 拥有 admin 角色
INSERT INTO user_roles (user_id, roles_id) VALUES (
(SELECT id FROM user WHERE username = 'admin1'),
(SELECT id FROM role WHERE name = 'admin')
);
测试登录
启动应用程序后,使用以下测试用户登录:
-
用户:
user1
,密码:password1
,角色:user
-
用户:
admin1
,密码:password1
,角色:admin
登录后,你可以根据角色和权限访问不同的页面。