Shiro使用自定义注解使用权限的细粒度控制

背景

针对同一个接口中,根据 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));
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值