Spring Boot中使用AOP实现权限管理
权限管理的实现方法有很多种,也有很多集成不错的框架。现在用AOP和自定义注解实现一个简单易理解的权限管理~
默认已经有做好了RBAC(role based access control),基于角色的访问控制。就是数据库中已经有了用户表,角色表,权限表,用户角色表,角色权限表。我们判断权限是取一个用户所有角色拥有权限的并集。
目录
权限判断注解
AOP 实现
获取用户权限服务层
使用权限注解
解释
总结
优化
权限判断注解
PermissionCheck.java
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PermissionCheck {
String value();
}
AOP 实现
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class PermissionAspect {
@Autowired
private PermissionService permissionService;
@Pointcut("execution(* *(..)) && @annotation(PermissionCheck)")
public void permissionCheckPointcut() {}
@Before("permissionCheckPointcut()")
public void checkPermission() throws Throwable {
MethodSignature signature = (MethodSignature) ProxyMethodInvocation.currentInvocation().getMethod().getSignature();
Method method = signature.getMethod();
PermissionCheck permissionCheck = method.getAnnotation(PermissionCheck.class);
String requiredPermission = permissionCheck.value();
String username = getUsernameFromRequest();
Set<String> userPermissions = permissionService.getUserPermissions(username);
if (!userPermissions.contains(requiredPermission)) {
throw new RuntimeException("没有权限");
}
}
private String getUsernameFromRequest() {
// 从请求中获取用户名,例如从 token 或 session 中获取
return "username";
}
}
解释:
@Pointcut("execution(* *(..)) && @annotation(PermissionCheck)")
:定义切点为带有PermissionCheck
注解的所有方法。@Before("permissionCheckPointcut()")
:在这些方法执行前执行checkPermission
方法。MethodSignature signature = (MethodSignature) ProxyMethodInvocation.currentInvocation().getMethod().getSignature();
:获取方法签名。Method method = signature.getMethod();
:获取被调用的方法。PermissionCheck permissionCheck = method.getAnnotation(PermissionCheck.class);
:获取方法上的PermissionCheck
注解。String requiredPermission = permissionCheck.value();
:获取注解中的权限信息。Set<String> userPermissions = permissionService.getUserPermissions(username);
:调用PermissionService
获取用户权限并集。- 检查用户是否具有所需权限,若不具有则抛出异常。
获取用户权限服务层
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@Service
public class PermissionService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PermissionRepository permissionRepository;
public Set<String> getUserPermissions(String username) {
User user = userRepository.findByUsername(username);
List<Role> roles = roleRepository.findByUserId(user.getId());
Set<String> permissions = new HashSet<>();
for (Role role : roles) {
List<Permission> rolePermissions = permissionRepository.findByRoleId(role.getId());
for (Permission permission : rolePermissions) {
permissions.add(permission.getName());
}
}
return permissions;
}
}
解释:
getUserPermissions
方法:- 根据用户名查找用户。
- 通过用户查找其所有角色。
- 遍历角色,找到每个角色对应的权限列表。
- 将权限存储在
HashSet
中,自动去重,得到权限并集。
使用权限注解
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/api")
public class SampleController {
@GetMapping("/createUser")
@PermissionCheck("user::add")
public String createUser() {
// 执行创建用户的逻辑
return "用户创建成功";
}
@GetMapping("/editUser")
@PermissionCheck("user::edit")
public String editUser() {
// 执行编辑用户的逻辑
return "用户编辑成功";
}
}
解释
1. PermissionService:
- 使用
HashSet
存储用户权限,自动去重,确保权限列表中的元素唯一性。 - 遍历用户的所有角色,将每个角色的权限添加到
HashSet
中,实现权限并集的计算。
2. PermissionAspect:
- 从方法注解中获取所需的权限信息。
- 通过
PermissionService
获取用户权限并集。 - 检查用户是否具有所需权限,若不具有则抛出异常。
总结
-
服务层:
PermissionService
负责根据用户查找其所有角色的权限,并将这些权限存储在HashSet
中,实现权限并集的计算。
-
AOP 切面:
- 使用
PermissionAspect
在方法执行前检查用户是否具有所需权限。
- 使用
-
自定义注解:
PermissionCheck
注解用于标记需要权限检查的方法,并传递权限信息。
优化
在实际应用中,还可以进一步优化,例如使用缓存来提高权限查询的性能,避免频繁的数据库查询,以及使用更安全的认证和授权机制,如 JWT 等。
以下是使用 JWT 提取用户名的示例,用于 getUsernameFromRequest
方法:
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
private String getUsernameFromRequest() {
String token = getTokenFromRequest();
Claims claims = Jwts.parser()
.setSigningKey("your_secret_key")
.parseClaimsJws(token)
.getBody();
return claims.getSubject();
}
private String getTokenFromRequest() {
// 从请求头中获取 JWT 令牌
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
String bearerToken = request.getHeader("Authorization");
if (bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
解释:
getUsernameFromRequest
方法:- 从请求中获取 JWT 令牌。
- 解析 JWT 令牌获取
claims
。 - 从
claims
中获取用户名(通常存储在subject
字段)。
这样可以根据用户的多个角色进行权限并集的计算和检查,同时使用 JWT 来增强安全性和用户信息的传递,提高系统的安全性和可扩展性。