shiro@RequiresPermission校验实现

本文介绍了如何利用Spring AOP结合shiro-spring,通过AuthorizationAttributeSourceAdvisor实现Shiro的注解式校验,包括`@RequiresPermission`注解的处理流程和关键步骤。讲解了如何创建advisor,匹配方法以及在Spring框架中的应用过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

shiro-spring

借助Spring AOP特性实现shiro的注解式校验

引入shiro-spring依赖后一定要注入AuthorizationAttributeSourceAdvisor以便借助spring aop进行shiro注解校验

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

 

 

StaticMethodMatcherPointcut

如何接入spring框架,借助spring实现自身AOP操作

一个抽象类实现 PointcutAdvisor, Ordered, Serializable 三个接口

public abstract class StaticMethodMatcherPointcut extends StaticMethodMatcher implements Pointcut

这是一个桥模式,实现Pointcut接口代表这是一个切点,但extends StaticMethodMatcher 代表这个抽象类更加适合表示为方法匹配器,一个切点+方法匹配的聚合抽象类,允许两个维度的扩展

一个双重维度的方便super class,

一些SpringAOP相关的接口

StaticMethodMatcherPointcut统合spring AOP相关描述的两个重要接口

  1. MethodMatcher

    一个抽象接口用于判断方法能否被增强(advice)

    • TrueMethodMatcher

      一个饿汉实现的单例模式,是一个匹配所有方法的规范(Canonical)匹配器

      实现3-arg的matches方法直接抛出异常代表该方法在运行时不可被调用

          @Override
          public boolean matches(Method method, Class<?> targetClass, Object... args) {
              // Should never be invoked as isRuntime returns false.
              throw new UnsupportedOperationException();
          }

       

    • boolean matches(Method method, Class<?> targetClass);执行static的检测目标method是否符合被增强的条件

    • boolean isRuntime();运行时是否需要调用boolean matches(Method method, Class<?> targetClass, Object... args);,即使2参数的matches方法判断为true

    • boolean matches(Method method, Class<?> targetClass, Object... args);只有2-arg的matches方法被调用并返回true且isRuntime也返回true该方法被立刻调用判断是否运行时动态判断

  2. Pointcut

    切入点抽象接口,由classFilter和MethodMatcher组成

    所有的网络请求在servlet中都有filter来实现AOP的横切拦截处理,那么class在AOP实现中也可以有自己的ClassFilter

    • ClassFilter getClassFilter();获取类过滤器

    • Return the MethodMatcher for this pointcut. 获取该pointcut的方法匹配器

  3. Advisor

    pointcut采取的动作

    在某个aspect的增强动作,可以是前置、后置、环绕等

  4.  

 

StaticMethodMatcherPointcut将大量抽象实现,只留出最重要的2-arg method match

一个想变成pointcut的methodMatcher

 

StaticMethodMatcherPointcutAdvisor

public abstract class StaticMethodMatcherPointcutAdvisor extends StaticMethodMatcherPointcut
      implements PointcutAdvisor, Ordered, Serializable

一个拥有增强方法以及pointcut双重身份的超类

继承该抽象类的子类只需实现一个方法是否匹配需要被增强的方法即可boolean matches(Method method, Class<?> targetClass);

借助spring实现注解式安全校验

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
package org.apache.shiro.spring.security.interceptor;
​
import org.apache.shiro.authz.annotation.*;
import org.apache.shiro.mgt.SecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.core.annotation.AnnotationUtils;
​
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
​
​
/**
 * TODO - complete JavaDoc
 *
 * @since 0.1
 */
@SuppressWarnings({"unchecked"})
public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
​
    private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class);
​
    private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
            new Class[] {
                    RequiresPermissions.class, RequiresRoles.class,
                    RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
            };
​
    protected SecurityManager securityManager = null;
​
    /**
     * Create a new AuthorizationAttributeSourceAdvisor.
     * 初始化注入增强动作(校验用户身份/权限
     */
    public AuthorizationAttributeSourceAdvisor() {
        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
    }
​
    public SecurityManager getSecurityManager() {
        return securityManager;
    }
​
    public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
        this.securityManager = securityManager;
    }
​
    /**
     * 实现方法匹配,寻找被标注shiro相关注解的方法使之成为pointcut
     * Returns <tt>true</tt> if the method or the class has any Shiro annotations, false otherwise.
     * The annotations inspected are:
     * <ul>
     * <li>{@link org.apache.shiro.authz.annotation.RequiresAuthentication RequiresAuthentication}</li>
     * <li>{@link org.apache.shiro.authz.annotation.RequiresUser RequiresUser}</li>
     * <li>{@link org.apache.shiro.authz.annotation.RequiresGuest RequiresGuest}</li>
     * <li>{@link org.apache.shiro.authz.annotation.RequiresRoles RequiresRoles}</li>
     * <li>{@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions}</li>
     * </ul>
     *
     * @param method      the method to check for a Shiro annotation
     * @param targetClass the class potentially declaring Shiro annotations
     * @return <tt>true</tt> if the method has a Shiro annotation, false otherwise.
     * @see org.springframework.aop.MethodMatcher#matches(java.lang.reflect.Method, Class)
     */
    public boolean matches(Method method, Class targetClass) {
        Method m = method;
        // 判断当前方法有没有标注shiro校验相关注解
        if ( isAuthzAnnotationPresent(m) ) {
            return true;
        }
​
        //The 'method' parameter could be from an interface that doesn't have the annotation.
        //Check to see if the implementation has it.
        // 方法存在继承关系寻找实现类(方法)是否标记
        if ( targetClass != null) {
            try {
                m = targetClass.getMethod(m.getName(), m.getParameterTypes());
                return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass);
            } catch (NoSuchMethodException ignored) {
                //default return value is false.  If we can't find the method, then obviously
                //there is no annotation, so just use the default return value.
            }
        }
​
        return false;
    }
​
    private boolean isAuthzAnnotationPresent(Class<?> targetClazz) {
        for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
            Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass);
            if ( a != null ) {
                return true;
            }
        }
        return false;
    }
​
    /**
     * 校验方法上是否被标注shiro权限校验相关注解
     */
    private boolean isAuthzAnnotationPresent(Method method) {
        for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) {
            Annotation a = AnnotationUtils.findAnnotation(method, annClass);
            if ( a != null ) {
                return true;
            }
        }
        return false;
    }
​
}
​

 

 

小结

通过Spring的IOC+AOP的特性,向IOC容器中注入一个PointCut检测器,由于AOP特性进行动态切面检测,并为匹配到的pointcut进行增强操作

@RequiresPermission注解校验流程

  1. AuthorizationAttributeSourceAdvisor#matches校验方法是否加上shiro相关注解,这里主要校验是否加上@RequiresPermission注解

    后面是spring框架的AOP流程

    被DefaultAdvisorChainFactory#getInterceptorsAndDynamicInterceptionAdvice调用

    被AdvisedSupport#getInterceptorsAndDynamicInterceptionAdvice调用

  2. 调用PermissionAnnotationHandler处理@RequiresPermissions注解,根据ThreadContext取出当前线程会话存储的subject来判断当前登录用户是否拥有某个权限,用户权限被shiro存储在缓存中(这里可使用 redis保存shiro的用户授权信息序列化后的数据)

 

一些授权校验的动作回去缓存中获取当前登录用户(在某个会话中)缓存的授权信息,因此执行用户切换/用户权限变动需执行缓存清理工作

    /**
     * 根据principalCollection删除授权缓存
     * @param attribute
     */
    public void deleteCacheBySimplePrincipalCollection(SimplePrincipalCollection attribute) {
        //删除Cache,再访问受限接口时会重新授权
        DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager();
        Authenticator authc = securityManager.getAuthenticator();
        ((LogoutAware) authc).onLogout(attribute);
    }

可调用subject.runAs来进行用户身份切换

 

疑问

  1. 抽象类实现methodMatcher不用实现全部方法也不会报错么

    不会,抽象类是抽象描述意味着不需要实现同样是抽象描述的接口中的任何方法,但具体的实现类不同,如果继承的interface中的某个接口没有默认实现则一定要实现,否则运行时报错

    Class 'ConcreteStaticMethodMatcher' must either be declared abstract or implement abstract method 'matches(Method, Class<?>)' in 'MethodMatcher'

    package priv.wzb.javabase.abstractAndInterface;
    ​
    import java.lang.reflect.Method;
    ​
    /**
     * @program: Design_Pattern
     * @author: yuzuki
     * @create: 2021-05-29 13:38
     * @description: AOP_方法匹配接口
     **/
    public interface MethodMatcher {
        /**
         * method是否匹配,在AOP中就是method是否成为一个连接点/切点
         * @param method
         * @param targetClass
         * @return
         */
        boolean matches(Method method,Class<?> targetClass);
    ​
        /**
         * 匹配后是否需动态调用3-arg匹配
         * @return
         */
        boolean isRuntime();
    ​
        /**
         * matchers=true isRuntime=true时触发
         * @param method
         * @param targetClass
         * @param args
         * @return
         */
        boolean methes(Method method,Class<?> targetClass,Object... args);
    }
    package priv.wzb.javabase.abstractAndInterface;
    ​
    import java.lang.reflect.Method;
    ​
    /**
     * @program: Design_Pattern
     * @author: wangzibai01
     * @create: 2021-05-29 13:45
     * @description: 静态方法检测
     **/
    ​
    public abstract class StaticMethodMatcher implements MethodMatcher {
        @Override
        public boolean isRuntime() {
            return false;
        }
    ​
        @Override
        public boolean methes(Method method, Class<?> targetClass, Object... args) {
    //      return false;
            throw new UnsupportedOperationException();
        }
    }
    ​
    package priv.wzb.javabase.abstractAndInterface;
    ​
    import java.lang.reflect.Method;
    ​
    /**
     * @program: Design_Pattern
     * @author: wangzibai01
     * @create: 2021-05-29 13:46
     * @description: StaticMethodMatcher实现类
     **/
    ​
    public class ConcreteStaticMethodMatcher extends StaticMethodMatcher {
        @Override
        public boolean matches(Method method, Class<?> targetClass) {
            return false;
        }
        public static void main(String[] args) {
            System.out.println("new ConcreteStaticMethodMatcher().isRuntime() = " + new ConcreteStaticMethodMatcher().isRuntime());
        }
    }
    ​

     

  2. 泛型和通配符

    乏型和通配符的区别

    乏型: 抽象指定类型,让开发者使用时更方便,优化、设计方面的作用。泛型可以防止类特征消失 通配符: 指定类型范围,避免开发不知道该数据只支持的范围,设置错误类型导致出现bug。灵活使用

  3. org.springframework.core.annotation.AnnotationUtils工具类可校验方法/类上是否标注某个注解

<think>嗯,用户想让我介绍如何在现有的开发基础上实现几个新功能,包括折线图展示库存变化、仓库平面图可视化、库存预警、用户权限管理和数据导出为Excel。他们要求步骤详细,代码齐全。首先,我需要确定用户的技术栈,但可能他们已经在使用Spring Boot和Vue,因为这在前后端分离项目中很常见。所以我会基于这个假设来回答。 首先,针对每个功能,我需要分步骤说明前后端需要做什么。比如折线图可能需要后端提供库存历史数据的API,前端用ECharts或AntV来渲染图表。然后仓库平面图可能需要使用SVG或者图片,结合点击事件,可能需要后端提供货架位置的数据。库存预警需要设置阈值,后端要有定时检查或者实时通知的机制,数据库可能需要添加阈值字段。用户权限管理通常涉及RBAC模型,需要用户、角色、权限表,Spring Security或Shiro来做权限控制。数据导出Excel的话,后端可以用Apache POI或EasyExcel生成文件,前端通过接口下载。 接下来要考虑每个功能的具体实现细节。例如,库存变化趋势的折线图,后端可能需要按时间分组查询库存数量,前端接收数据后配置图表选项。仓库平面图可能需要一个可视化的库,比如使用svg-map组件,或者第三方库如D3.js。库存预警功能除了设置阈值,还需要有通知机制,比如邮件或站内消息,这里可能需要集成消息队列或者定时任务。用户权限管理模块需要设计数据库表结构,接口权限控制,前端路由的动态加载。数据导出Excel要注意大数据量的处理,避免内存溢出,可能需要分页查询或者使用流式写入。 然后要检查是否有遗漏的步骤,比如数据库的变更,每个功能都可能需要新增表或字段,需要提供SQL示例。代码部分要确保关键部分给出示例,比如控制层、服务层的代码,前端组件和API调用。同时要注意权限管理部分,如何与现有系统集成,是否需要登录状态验证,接口的安全措施等。 可能用户的实际项目结构可能与示例不同,所以需要说明如何调整代码以适应现有项目。比如,包名、类名可能需要根据实际情况修改。另外,数据导出的文件格式和样式可能需要根据需求调整,比如单元格样式、标题行等。 还要考虑异常处理和性能优化,比如折线图数据量大的时候是否需要分页或按时间范围查询,库存预警的实时性要求,权限管理的数据校验等。确保提供的代码示例有基本的错误处理,比如try-catch块,以及日志记录。 最后,需要总结这些功能的实现步骤,确保用户能按部就班地完成开发。同时提醒用户根据具体需求调整代码,进行测试,特别是权限管理和数据导出部分可能需要更多的测试用例。</think>以下是为已有系统增加功能的详细实现方案,基于常见的Spring Boot + Vue前后端分离架构: ### 一、扩展折线图展示库存变化趋势 #### 后端实现Spring Boot) 1. 创建数据访问层: ```java // InventoryHistoryRepository.java public interface InventoryHistoryRepository extends JpaRepository<InventoryHistory, Long> { @Query("SELECT new com.warehouse.dto.InventoryTrendDTO(DATE_FORMAT(h.recordTime, '%Y-%m-%d'), SUM(h.changeAmount)) " + "FROM InventoryHistory h WHERE h.productId = :productId " + "GROUP BY DATE_FORMAT(h.recordTime, '%Y-%m-%d')") List<InventoryTrendDTO> findTrendByProductId(@Param("productId") Long productId); } ``` 2. 新增控制器: ```java // InventoryController.java @GetMapping("/trend") public ResponseEntity<List<InventoryTrendDTO>> getInventoryTrend( @RequestParam Long productId, @DateTimeFormat(pattern = "yyyy-MM-dd") Date startDate, @DateTimeFormat(pattern = "yyyy-MM-dd") Date endDate) { return ResponseEntity.ok(inventoryService.getTrend(productId, startDate, endDate)); } ``` #### 前端实现(Vue + ECharts) ```vue <template> <div ref="chart" style="width: 100%; height: 400px"></div> </template> <script> import * as echarts from 'echarts' export default { props: ['productId'], mounted() { this.fetchData() }, methods: { async fetchData() { const { data } = await this.$axios.get('/api/inventory/trend', { params: { productId: this.productId } }) const chart = echarts.init(this.$refs.chart) chart.setOption({ xAxis: { type: 'category', data: data.map(d => d.date) }, yAxis: { type: 'value' }, series: [{ data: data.map(d => d.amount), type: 'line', smooth: true, areaStyle: {} }] }) } } } </script> ``` ### 二、仓库平面图可视化 #### 后端实现 1. 添加仓库布局实体: ```java @Entity public class WarehouseLayout { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Lob private String svgContent; // 存储SVG矢量数据 private String coordinateSystem; // 坐标系定义 } ``` 2. 新增仓库布局服务: ```java // WarehouseLayoutService.java public WarehouseLayout getLayoutWithInventory(Long warehouseId) { WarehouseLayout layout = layoutRepository.findById(warehouseId).orElseThrow(); List<StorageLocation> locations = locationRepository.findByWarehouseId(warehouseId); layout.setLocations(locations); return layout; } ``` #### 前端实现 ```vue <template> <div class="warehouse-map"> <svg :viewBox="viewBox" @click="handleSvgClick"> <g v-html="svgContent"></g> <rect v-for="loc in locations" :x="loc.x" :y="loc.y" :width="loc.width" :height="loc.height" :class="['storage-area', { 'low-stock': loc.currentStock < loc.minStock }]" @mouseover="showTooltip(loc)"/> </svg> </div> </template> <script> export default { data() { return { svgContent: '', locations: [], viewBox: '0 0 1000 800' } }, async mounted() { const { data } = await this.$axios.get(`/api/warehouse/${this.warehouseId}/layout`) this.svgContent = data.svgContent this.locations = data.locations } } </script> ``` ### 三、库存预警功能 #### 后端实现 1. 添加预警配置实体: ```java @Entity public class InventoryAlert { @Id @GeneratedValue private Long id; private Long productId; private Integer minStock; private Integer maxStock; private String notifyEmails; } ``` 2. 定时检查任务: ```java // InventoryAlertJob.java @Scheduled(cron = "0 0 8 * * ?") // 每天8点执行 public void checkInventory() { List<InventoryAlert> alerts = alertRepository.findAll(); alerts.forEach(alert -> { Integer currentStock = inventoryService.getCurrentStock(alert.getProductId()); if (currentStock < alert.getMinStock() || currentStock > alert.getMaxStock()) { emailService.sendAlert(alert.getNotifyEmails(), currentStock); } }); } ``` #### 前端实现 ```vue <template> <div class="alert-config"> <el-form :model="alertForm"> <el-form-item label="最低库存"> <el-input-number v-model="alertForm.minStock" :min="0"/> </el-form-item> <el-form-item label="最高库存"> <el-input-number v-model="alertForm.maxStock" :min="alertForm.minStock + 1"/> </el-form-item> </el-form> </div> </template> ``` ### 四、用户权限管理模块 #### 后端实现 1. 数据库设计: ```sql CREATE TABLE role ( id BIGINT PRIMARY KEY, name VARCHAR(50) UNIQUE ); CREATE TABLE permission ( id BIGINT PRIMARY KEY, code VARCHAR(50) UNIQUE, description VARCHAR(100) ); CREATE TABLE user_role ( user_id BIGINT, role_id BIGINT, PRIMARY KEY (user_id, role_id) ); CREATE TABLE role_permission ( role_id BIGINT, permission_id BIGINT, PRIMARY KEY (role_id, permission_id) ); ``` 2. 权限拦截器: ```java @Interceptor public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String permissionCode = ((HandlerMethod) handler).getMethodAnnotation(RequiresPermission.class).value(); if (!currentUser.hasPermission(permissionCode)) { throw new AccessDeniedException("权限不足"); } return true; } } ``` #### 前端实现 ```javascript // permission.js router.beforeEach((to, from, next) => { const requiredPermissions = to.meta.permissions if (requiredPermissions) { const userPermissions = store.getters.permissions if (!requiredPermissions.every(p => userPermissions.includes(p))) { next('/403') return } } next() }) ``` ### 五、数据导出Excel功能 #### 后端实现 ```java // ExportController.java @GetMapping("/export") public void exportInventory(HttpServletResponse response) { List<Inventory> data = inventoryService.getAll(); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setHeader("Content-Disposition", "attachment; filename=inventory.xlsx"); try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) { WriteSheet sheet = EasyExcel.writerSheet("库存数据") .head(Inventory.class) .build(); writer.write(data, sheet); } } ``` #### 前端实现 ```javascript export function exportExcel(params) { return axios({ url: '/api/inventory/export', method: 'GET', responseType: 'blob' }).then(response => { const url = window.URL.createObjectURL(new Blob([response.data])) const link = document.createElement('a') link.href = url link.setAttribute('download', 'inventory.xlsx') document.body.appendChild(link) link.click() }) } ``` ### 实施步骤建议: 1. 数据库变更:按需执行DDL语句创建新表 2. 后端开发顺序建议: -实现权限模块(作为基础功能) - 然后开发数据导出(基础工具功能) - 接着库存预警(需要定时任务支持) - 最后可视化相关功能 3. 前端开发顺序: - 先集成权限管理路由 - 开发数据导出按钮 - 权限控制的边界情况 - 定时任务的异常处理 - 可视化组件的响应式布局 根据实际项目结构需要调整包路径和类命名,建议使用代码生成工具(如MyBatis Generator)生成基础数据访问层代码。可视化部分可根据具体需求选择不同的图表库(如ECharts、AntV等)。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值