背景
针对同一个接口中,根据 action属性 包含多种操作,现在需要实现对action做细粒度权限控制;
shiro的鉴权注解是接口维度的;
实现
增强shiro鉴权能力:使用自定义注解,支持通过接口参数(action)实现对权限点的细粒度控制;
代码实现
新增注解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 描述:自定义注解:
* 针对一个接口有多种操作
* 通过请求参数实现多种操作的细粒度控制
*
* @author xxx
* @since 2025/05/30
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresParamPermission {
/**
* 类:aop从入参中找到指定类,递归查找指定属性
*/
Class<?> searchClazz() default String.class;
/**
* 属性名称,aop从该属性名称中拿到具体值,默认名称是action
*/
String searchKey() default "action";
/**
* 权限点前缀
*/
String permPrefix();
}
添加注解切面
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.util.Strings;
import org.apache.shiro.authz.UnauthorizedException;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.alibaba.fastjson.JSON;
import cn.com.common.utils.JSONUtils;
import lombok.extern.slf4j.Slf4j;
/**
* 描述: 切面 针对一个接口有多个操作 根据请求参数实现权限点细粒度鉴权
*
* @author xxx
* @since 2025/05/30
*/
@Aspect
@Component
@Slf4j
public class ParamPermissionAspect {
@Before("@annotation(requiresParamPermission)")
public void checkPermission(JoinPoint joinPoint, RequiresParamPermission requiresParamPermission) {
Object[] args = joinPoint.getArgs();
// 权限点后缀
String permSuffix = Strings.EMPTY;
ServletRequestAttributes attributes = (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
String method = null;
if (attributes != null) {
method = attributes.getRequest().getMethod();
}
if (StringUtils.equalsAnyIgnoreCase(method, RequestMethod.GET.name(), RequestMethod.DELETE.name())) {
// 从请求参数中拿到
if (attributes != null) {
HttpServletRequest request = attributes.getRequest();
Map<String, String[]> paramMap = request.getParameterMap();
for (Map.Entry<String, String[]> entry : paramMap.entrySet()) {
String key = entry.getKey();
if (StringUtils.equals(key, requiresParamPermission.searchKey()) && entry.getValue() != null
&& entry.getValue().length > 0) {
permSuffix = entry.getValue()[0];
break;
}
}
}
} else { // 从请求体中拿到
for (Object arg : args) {
if (requiresParamPermission.searchClazz().isInstance(arg)) {
// 递归查找权限点后缀
permSuffix = JSONUtils.findPath(JSON.toJSON(arg), requiresParamPermission.searchKey());
break;
}
}
}
// 操作鉴权 操作权限点组成: 前缀:后缀
String completePerm = requiresParamPermission.permPrefix() + ":" + permSuffix;
log.info("current-method[{}] need permission:{}", joinPoint.getSignature().getName(), completePerm);
if (!SecurityUtils.getSubject().isPermittedAll(completePerm)) {
throw new UnauthorizedException("没有权限执行该操作");
}
}
}
json工具类
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@Slf4j
public class JSONUtils {
/**
* 递归遍历:在目标对象中查找属性值
* @param targetObj 目标对象
* @param searchName 查找属性
* @return 属性值
*/
public static String findPath(Object targetObj, String searchName) {
if (targetObj instanceof JSONObject) {
JSONObject jsonObject = (JSONObject) targetObj;
if (jsonObject.containsKey(searchName)) {
return jsonObject.getString(searchName);
}
for (String key : jsonObject.keySet()) {
Object value = jsonObject.get(key);
String result = findPath(value, searchName);
if (result != null) {
return result;
}
}
} else if (targetObj instanceof JSONArray) {
JSONArray jsonArray = (JSONArray) targetObj;
for (Object item : jsonArray) {
String result = findPath(item, searchName);
if (result != null) {
return result;
}
}
}
return null;
}
}
业务使用
在需要细粒度控制的接口上添加自定义注解:
@RequiresParamPermission(permPrefix = "account:app:action:query")
@GetMapping("/query")
BaseResponse<Map<String, List<String>>> queryAppAction(
@RequestParam @NotNull(message = "设备ID不能为空") String deviceId,
@RequestParam @NotNull(message = "action不能为空") String action) {
return BaseResponse.ok(appActionService.queryAppAction(deviceId, action));
}