怎么处理整合了shiro的应用的RPC接口鉴权问题

news2024/11/27 7:41:26

这篇文章分享一下:当一个服务提供者整合了shiro安全框架来实现权限访问控制时,服务消费者通过feign请求服务提供者的接口时的鉴权不通过问题。

问题描述

博主有一个项目pms(权限管理系统),使用了shiro框架来实现鉴权功能,使用的是shiro内置的perms过滤器。

 而服务消费者端,通过feign访问RPC接口,很显然,由于是不同的项目,鉴权肯定是失败的。

package cn.edu.sgu.www.cms.feign.impl;

import cn.edu.sgu.www.cms.dto.PermissionInitDTO;
import cn.edu.sgu.www.cms.entity.Permission;
import cn.edu.sgu.www.cms.entity.User;
import cn.edu.sgu.www.cms.feign.FeignService;
import cn.edu.sgu.www.cms.restful.JsonResult;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;
import java.util.Set;

/**
 * 权限平台feign接口
 * @author heyunlin
 * @version 1.0
 */
@FeignClient(name = "pms")
public interface PmsFeignService extends FeignService {

    /**
     * 根据用户名查询用户信息
     * @param username 用户名
     * @param service 应用名
     * @return JsonResult<User>
     */
    @RequestMapping(value = "/user/selectByUsername", method = RequestMethod.GET)
    JsonResult<User> selectByUsername(
            @RequestParam("username") String username,
            @RequestParam("service") String service
    );

    /**
     * 权限数据初始化
     * @param permissionInitDTO 权限信息
     * @return JsonResult<Void>
     */
    @RequestMapping(value = "/permission/resources", method = RequestMethod.POST)
    JsonResult<Void> resources(PermissionInitDTO permissionInitDTO);

    /**
     * 查询应用的非匿名子权限
     * @param service 应用名
     * @return JsonResult<List<Permission>>
     */
    @RequestMapping(value = "/permission/selectPermissions", method = RequestMethod.GET)
    JsonResult<List<Permission>> selectPermissions(@RequestParam("service") String service);

    /**
     * 查询应用的匿名子权限
     * @param service 应用名
     * @return JsonResult<List<String>>
     */
    @RequestMapping(value = "/permission/selectAnonymityPermissions", method = RequestMethod.GET)
    JsonResult<List<String>> selectAnonymityPermissions(@RequestParam("service") String service);

    /**
     * 通过用户名查询用户权限
     * @param username 用户名
     * @param service 应用名
     * @return JsonResult<Set<String>>
     */
    @RequestMapping(value = "/permission/selectUserPermissions", method = RequestMethod.GET)
    JsonResult<Set<String>> selectUserPermissions(
            @RequestParam("username") String username,
            @RequestParam("service") String service
    );
}

而在服务消费者启动过程中,需要向pms查询当前应用的权限,所以导致feign请求报错,项目无法启动问题。

package cn.edu.sgu.www.cms.config;

import cn.edu.sgu.www.cms.entity.Permission;
import cn.edu.sgu.www.cms.feign.impl.PmsFeignService;
import cn.edu.sgu.www.cms.restful.JsonResult;
import cn.edu.sgu.www.cms.shiro.UserRealm;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Shiro配置类
 * @author heyunlin
 * @version 1.0
 */
@Configuration
public class ShiroConfig {

    @Value("${spring.application.name}")
    private String service;

    /**
     * 登录页面地址
     */
    private String loginPage = "/login.html";

    /**
     * 未授权的URL
     */
    private String unauthorizedUrl = "/user/unauthorized";

    private final PmsFeignService feignService;

    @Autowired
    public ShiroConfig(PmsFeignService feignService) {
        this.feignService = feignService;
    }

    /**
     * 配置安全管理器
     * @param userRealm UserRealm
     * @return DefaultWebSecurityManager
     */
    @Bean
    public DefaultWebSecurityManager securityManager(UserRealm userRealm, CacheManager cacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 设置缓存管理器
        securityManager.setCacheManager(cacheManager);
        // 把UserRealm注册到安全管理器
        securityManager.setRealm(userRealm);

        return securityManager;
    }

    /**
     * 配置Shiro过滤器工厂
     * @param securityManager 安全管理器
     * @return ShiroFilterFactoryBean
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();

        // 注册安全管理器
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        /*
         * 设置登录页面的地址
         * 当用户访问认证资源的时候,如果用户没有登录,就会跳转到指定的页面
         */
        shiroFilterFactoryBean.setLoginUrl(loginPage);

        /*
         * 设置访问未授权资源时重定向的地址
         * 当用户访问需要授权才能访问资源的时候,如果用户没有该权限,就会跳转到指定的地址
         */
        shiroFilterFactoryBean.setUnauthorizedUrl(unauthorizedUrl);

        // 定义资源访问规则
        Map<String, String> filterMap = new LinkedHashMap<>();

        /*
         * 定义需要认证才能访问的资源
         */
        filterMap.put("/", "authc");
        filterMap.put("/index.html", "authc");
        filterMap.put("/html/*.html", "authc");
        // knife4j
        filterMap.put("/doc.html", "authc");
        filterMap.put("/v2/api-docs", "authc");
        filterMap.put("/swagger-resources", "authc");

        /*
         * 定义不需要认证就能访问的资源
         */
        filterMap.put(loginPage, "anon");
        filterMap.put("/user/login", "anon");
        filterMap.put(unauthorizedUrl, "anon");

        // 查询当前服务的所有匿名子权限
        JsonResult<List<String>> jsonResult = feignService.selectAnonymityPermissions(service);

        if (jsonResult.isSuccess()) {
            List<String> data = jsonResult.getData();

            for (String url : data) {
                filterMap.put(url, "anon");
            }
        }

        /*
         * 定义需要指定权限才能访问的资源
         */
        // 查询当前服务的所有非匿名子权限
        JsonResult<List<Permission>> result = feignService.selectPermissions(service);

        if (result.isSuccess()) {
            List<Permission> list = result.getData();

            for (Permission permission : list) {
                filterMap.put(permission.getUrl(), "perms[" + permission.getValue() + "]");
            }
        }

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);

        return shiroFilterFactoryBean;
    }

}

之前就遇到了这个问题,但是一直没有管,因为当时还没有想到什么解决的办法。

今天写项目,又遇上了,真气人~

解决方案

博主想到的是通过设置一个请求头,通过过滤器鉴权时先判断请求头是否正确,正确的话直接返回true,不再进行后续的操作。

于是在feign的官网看一下有没有能设置请求头的方法,果然,找到一个请求拦截器可以达到这个效果~

添加请求头

定义一个RequestInterceptor,给请求设置一个请求头perms。

package cn.edu.sgu.www.cms.feign;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;

/**
 * @author heyunlin
 * @version 1.0
 */
@Component
public class RequestHeaderInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        template.header("perms", "12345");
    }

}

自定义过滤器

在服务生产者项目pms中定义一个过滤器PermsFilter,名字见名知义,就是shiro的perms过滤器。

重写shiro的perms过滤器(PermissionsAuthorizationFilter)的鉴权方法,先判断请求头是否为指定的值,如果是就跳过鉴权,直接返回true。

这样就避免了其他应用访问本应用的接口导致的鉴权失败问题了~

package cn.edu.sgu.www.pms.filter;

import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

/**
 * 定义PermsFilter过滤器(覆盖shiro的perms过滤器)
 * @author heyunlin
 * @version 1.0
 */
@Slf4j
public class PermsFilter extends PermissionsAuthorizationFilter {

    @Override
    public boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws IOException {
        String perms = ((HttpServletRequest) request).getHeader("perms");

        if (perms != null && perms.equals("12345")) {
            log.debug("发现请求头perms,当前请求为搜权应用发起的请求,跳过鉴权~");

            return true;
        }

        return super.isAccessAllowed(request, response, mappedValue);
    }

}

注册过滤器到shiro

将刚刚创建的过滤器注册到shiro,博主直接把原来的perms过滤器覆盖掉了。

// 自定义过滤器
Map<String, Filter> filterMap = new HashMap<>();

filterMap.put("perms", new PermsFilter());

shiroFilterFactoryBean.setFilters(filterMap);

好了,文章就分享到这里了,希望看完之后对你有所帮助~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1848759.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C语言】解决C语言报错:Stack Overflow

文章目录 简介什么是Stack OverflowStack Overflow的常见原因如何检测和调试Stack Overflow解决Stack Overflow的最佳实践详细实例解析示例1&#xff1a;递归调用过深示例2&#xff1a;分配过大的局部变量示例3&#xff1a;嵌套函数调用过多 进一步阅读和参考资料总结 简介 St…

Python轻松设置Excel单元格数字显示格式

Excel作为强大的数据处理与分析工具&#xff0c;不仅能够存储大量数据&#xff0c;还支持复杂的数据处理与可视化功能。而如何恰当地展示Excel表格中的数据是Excel文件制作的关键之一。这便涉及到Excel单元格数字格式的设置。数字格式不仅关乎数据的美学呈现&#xff0c;如货币…

开发中遇到的一个bug

遇到的报错信息是这样的&#xff1a; java: Annotation processing is not supported for module cycles. Please ensure that all modules from cycle [hm-api,hm-common,hm-service] are excluded from annotation processing 翻译过来就是存在循环引用的情况&#xff0c;导…

Golang | Leetcode Golang题解之第172题阶乘后的零

题目&#xff1a; 题解&#xff1a; func trailingZeroes(n int) (ans int) {for n > 0 {n / 5ans n}return }

MySQL的DDL语句

文章目录 ☃️概述☃️DDL&#xff08;数据定义语言&#xff09;☃️数据库操作☃️表操作☃️DDL的重要性 ☃️概述 MySQL 通用语法分类 ● DDL: 数据定义语言&#xff0c;用来 定义数据库对象&#xff08;数据库、表、字段&#xff09; ● DML: 数据操作语言&#xff0c;用…

天擎客户端卸载 自我保护异常

问题&#xff1a;客户端卸载失败提示“检测到自我保护状态异常&#xff0c;停止卸载” 下列操作&#xff0c;均在客户端进行&#xff0c;别改成服务端的了 进入天擎客户端主目录&#xff0c;默认路径为 C:\Program Files (x86)\Qianxin\Tianqing 将avsecbase.dll 重命名为 1…

JAVA NIO(二) Buffer和Channel

一&#xff0c;基本使用 1&#xff0c; 一个Socket连接使用一个Channel来表示&#xff0c;以前直接操作Socket文件描述符来对读写缓冲区操作&#xff0c;比如读数据到用户空间的一个byte数组&#xff0c;NIO中Channel对这个过程作了封装&#xff0c;其中用户空间的byte数组就类…

电商数据:准确性与覆盖率的关键意义

在当今的电商环境下&#xff0c;以电商数据为驱动&#xff0c;品牌能够开展诸多与渠道相关的工作&#xff0c;像是电商数据监测、数据分析以及低价治理等。然而&#xff0c;所有这些工作的首要前提便是数据的准确性必须达到较高水平。倘若准确性无法得到有效保障&#xff0c;那…

C语言 | Leetcode C语言题解之第172题阶乘后的零

题目&#xff1a; 题解&#xff1a; int trailingZeroes(int n) {int ans 0;while (n) {n / 5;ans n;}return ans; }

【STM32】时钟树系统

1.时钟树简介 1.1五个时钟源 LSI是低速内部时钟&#xff0c;RC振荡器&#xff0c;频率为32kHz左右。供独立看门狗和自动唤醒单元使用。 LSE是低速外部时钟&#xff0c;接频率为32.768kHz的石英晶体。这个主要是RTC的时钟源。 HSE是高速外部时钟&#xff0c;可接石英*/陶瓷谐振…

el-input-number 限制输入正整数

vue 页面 限制输入最小值为0 :min"0" <el-input-number v-model"scope.row.num" change"handleNumChange(scope)" keydown.enter.prevent style"width: 200px; " :min"0" />methods 里面限制输入的数字不为小数 使…

DPDK与传统收发报文的区别

1.去除中断 传统的收发报文方式都必须采用硬中断来做通讯&#xff0c;每次硬中断大约消耗100微秒&#xff0c;这还不算因为终止上下文所带来的Cache Miss。 DPDK采用轮询模式驱动(PMD)。 PMD由用户空间的特定的驱动程序提供的API组成&#xff0c;用于对设备和它们相应的…

Swoole_loader扩展安装图文教程 Swoole扩展文件下载

Swoole_loader扩展安装图文教程 Swoole扩展文件下载 安装和配置Swoole Loader 1 - 下载Swoole Loader 请下载兼容PHP7.2和非线程安全的Swoole Loader扩展&#xff0c;点击下载适配环境的扩展文件 2 - 安装Swoole Loader 将刚才下载的Swoole Loader扩展文件&#xff08;swo…

Redis—List数据类型及其常用命令详解

文章目录 一、Redis概述List类型1 LPUSH:将一个或多个值插入到列表头部2 RPUSH:将一个或多个值插入到列表尾部3 LPOP:从列表头部弹出并移除一个或多个元素4 RPOP&#xff1a;从列表尾部弹出一个或多个元素5 LLEN:获取 Redis 列表的长度6 LRANGE:获取 Redis 列表中指定范围内的元…

SpringCloud 基于Nacos和Eureka 实现双注册双订阅

一、使用场景/原因 过渡期迁移: 当系统从一个服务注册中心迁移到另一个时&#xff0c;例如从 Eureka 迁移到 Nacos&#xff0c;可以在过渡期内同时使用两个注册中心&#xff0c;确保服务平稳迁移&#xff0c;逐步过渡&#xff0c;避免一次性切换带来的风险。 兼容性考虑: 不同的…

http/2 二进制分帧层 (Binary Framing Layer)讲解

文章目录 二进制帧HTTP/2 中的帧、消息和流1. 帧&#xff08;Frame&#xff09;2. 消息&#xff08;Message&#xff09;3. 流&#xff08;Stream&#xff09;总结示例&#xff1a; 二进制帧结构1.帧头部结构2.帧负载数据 请求和响应多路复用 链接参考&#xff1a;https://web.…

C#修改 EXE 文件图标和 winForm 窗口图标

修改 EXE 文件图标 1.准备好图片&#xff0c;转换为 Icon 图片&#xff1b; 2.右键工程&#xff0c;选择属性&#xff1b; 3.选择 Icon 图标即可&#xff1b; 4.重新生成可执行文件&#xff0c;查看。 修改 winForm 窗口图标 1.选中 winForm &#xff0c;查看属性&#x…

天马学航——智慧教务系统(移动端)开发日志三

天马学航——智慧教务系统(移动端)开发日志三 日志摘要&#xff1a;更新了学生选课模块、我的课程模块以及退课的功能&#xff0c;优化了后端数据库的缓存 1、学生选课模块 学生选课模块主要实现&#xff0c;学生根据需求进行选课操作&#xff0c;通过后端查询到所有教师的课…

mysql 主从延迟

mysql 主从延迟 精华推荐 | 【MySQL技术专题】「主从同步架构」全面详细透析MySQL的三种主从复制&#xff08;Replication&#xff09;机制的原理和实战开发&#xff08;原理实战&#xff09; https://blog.csdn.net/l569590478/article/details/128329929 mysql主从之多线程复…

Nuxt 3 路由系统详解:配置与实践指南

title: Nuxt 3 路由系统详解&#xff1a;配置与实践指南 date: 2024/6/21 updated: 2024/6/21 author: cmdragon excerpt: 摘要&#xff1a;本文是一份关于Nuxt 3路由系统的详尽指南。它从介绍Nuxt 3的基本概念开始&#xff0c;包括Nuxt 3与Nuxt 2的区别和选择Nuxt 3的理由。…