shiro的前后端分离模式

shiro的前后端分离模式

前言:在上一篇《shiro的简单认证和授权》中介绍了shiro的搭建,默认情况下,shiro是通过设置cookie,使前端请求带有“JSESSION”cookie,后端通过获取该cookie判断用户是否登录以及授权。但是在前后端分离模式中,前后端不在同一个域内,后端无法自动给请求添加cookie,因而默认的模式不能实现正常的认证授权逻辑。

我们可以将subject的sessionId通过登录成功的请求返回给前端,前端获取sessionId后在每个请求的header中带上sessionId(token),后端通过继承DefaultWebSessionManager类,实现自定义的session管理器,改写getSession的方法,改在header中获取sessionId进行后续认证与授权。

以下例子只是demo,不注重正式开发细节,例如返回值封装,请求Restful风格等等,正式项目中注意甄别

1.登录成功,将sessionId返回给前端

    /**
     * 登录接口
     * @param username
     * @param password
     * @return
     */
    @GetMapping("/login")
    public String login(String username,String password){
            // 获取当前主体
            Subject subject = SecurityUtils.getSubject();
            // 构建登录token,login方法进入中进入我们自定义的realm的doGetAuthenticationInfo方法
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password);
            subject.login(usernamePasswordToken);
            String token = (String) SecurityUtils.getSubject().getSession().getId();
            return "登陆成功,token为:"+token;
    }

2.前端携带token请求

前端将获取到的token写到sessionStorage或者localStorage,然后请求时添加到请求头中,例如axios请求例子

    axios({
      method:"GET",
      url:"http://127.0.0.1:8080/testIndex",
      headers:{
        'Content-Type':' application/json',
        'token':'bd9ae5c3-570f-4ed2-ac57-8f935655ef55',   // token从localStorage中获取
      },
      withCredentials: true,
    }).then((r)=>{
      console.log(r)
    })

3.继承DefaultWebSessionManager

Shiro中DefaultWebSessionManager管理shiro的session会话,包括设置cookie,获取前端携带的shiro的sessionId等等。我们可以重写其方法,实现自己的逻辑,最后添加到SecurityManager中,例如重写getSessionId方法,改为从header中获取sessionId

public class CustomSessionManager extends DefaultWebSessionManager {

    private static final String HEADER_TOKEN = "token";

    private static final String REFERENCED_SESSION_ID_SOURCE = "Stateless request";

    public CustomSessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String id = WebUtils.toHttp(request).getHeader(HEADER_TOKEN);
        if (!StringUtils.isEmpty(id)) {
            request.setAttribute(ShiroHttpServletRequest.SESSION_ID_URL_REWRITING_ENABLED, isSessionIdUrlRewritingEnabled());
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return id;
        } else {

            return super.getSessionId(request, response);
        }
    }
}

4. 在ShiroConfig中加入SecurityManager

在ShiroConfig中,创建自定义的SessionManager实例,然后添加到SecurityManager

    /**
     * sessionManager
     */
    public DefaultWebSessionManager sessionManager() {
//        DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
        CustomSessionManager sessionManager = new CustomSessionManager();
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionDAO(redisSessionDAO());
        sessionManager.setSessionIdCookieEnabled(false);
        return sessionManager;
    }

    /**
     * SecurityManager
     */
    @Bean
    public SecurityManager securityManager(Realm myRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //设置一个Realm,这个Realm是最终用于完成我们的认证号和授权操作的具体对象
        securityManager.setRealm(myRealm);
        securityManager.setSessionManager(sessionManager());
        securityManager.setCacheManager(cacheManager());
        return securityManager;
    }

5.跨域设置

前后端分离肯定离不开跨域设置,跨域有多重设置方法,这里不赘述

/**
 * 解决跨域问题
 */
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowedOriginPatterns("*")
                .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
                .allowCredentials(true)
                .maxAge(3600)
                .allowedHeaders(CorsConfiguration.ALL);
//                .allowedHeaders("*");	
    }
}

6.添加过滤器

这里是一个坑,我以为完成以上步骤就可以实现前后端分离,但是测试后发现,有一些请求符合预期,但是有一些请求却报跨域,甚至乎不是寻常的报跨域
在这里插入图片描述

浏览器控制台输出

在这里插入图片描述

如果是跨域的话,按道理讲应该所有请求都会报跨域,但是login请求没有报

那么就应该跟shiro的FilterChainDefinitionMap有关,只有拦截做验证的请求才会报错。以为是菜狗,查了好久,都没有找到正确答案。然后在调试时发现报错的请求不是我写的get请求,而是options请求。

事实上在这之前我只听说过options,并没去了解options请求的作用,看到报差后才去百度一下

Options 请求是 HTTP 协议中的一种请求方法,它用于获取服务器支持的请求方法和其他选项。Options 请求通常不会包含实际的请求数据,而是用于获取服务器的元数据信息。

Options 请求的主要作用包括:

  1. 获取服务器支持的请求方法:Options 请求可以获取服务器支持的请求方法,例如 GET、POST、PUT、DELETE 等。这对于客户端来说非常有用,因为它可以确定服务器是否支持特定的请求方法。

  2. 检查服务器的能力:Options 请求可以检查服务器的能力,例如是否支持跨域请求、是否支持特定的 HTTP 头、是否支持特定的内容类型等。

  3. 探测服务器:Options 请求可以用于探测服务器,例如确定服务器的版本、操作系统、服务器软件等信息。这对于安全测试和漏洞扫描非常有用。

Options 请求通常被视为一种安全的请求方法,因为它不会对服务器产生实际的影响。但是,服务器可能会限制 Options 请求的使用,例如限制请求频率或限制请求的来源。因此,在使用 Options 请求时,应该遵守服务器的规定,并确保请求是合法和必要的。

原来options这么重要,那报的跨域肯定就是这个options搞的鬼了。当然就算知道问题所在,我这菜狗也是想不到解决方案的,但至少会百度。

然后就查到以下文章

[SpringBoot+Shiro放行OPTIONS请求,解决跨域问题] https://blog.csdn.net/qq_41289165/article/details/121529166

第一步,新建一个Filter继承Shiro的UserFilter,里面的preHandle方法对options请求直接放行。还有其他逻辑看注释,这里不赘述

@Slf4j
public class ShiroUserFilter extends UserFilter {
    /**
     * 在访问过来的时候检测是否为OPTIONS请求,如果是就直接返回true
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            log.info("OPTIONS放行");
            setHeader(httpRequest,httpResponse);
            return true;
        }
        return super.preHandle(request,response);
    }

    /**
     * 该方法会在验证失败后调用,这里由于是前后端分离,后台不控制页面跳转
     * 因此重写改成传输JSON数据
     */
    @Override
    protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        saveRequest(request);
        setHeader((HttpServletRequest) request,(HttpServletResponse) response);
        PrintWriter out = response.getWriter();
        //自己控制返回的json数据
        Map map = new HashMap();
        map.put("code",500);
        map.put("message","Shiro验证失败");
        out.println(map);
        out.flush();
        out.close();
    }

    /**
     * 为response设置header,实现跨域
     */
    private void setHeader(HttpServletRequest request, HttpServletResponse response){
        //跨域的header设置
        response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", request.getMethod());
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
        //防止乱码,适用于传输JSON数据
        response.setHeader("Content-Type","application/json;charset=UTF-8");
        response.setStatus(HttpStatus.OK.value());
    }
}

第二步,将该Filter添加到FilterFactoryBean中

在ShiroConfig中的shiroFilterFactoryBean方法中,添加shiroFilter.getFilters().put("authc", new ShiroUserFilter());

@Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);

        // 添加自定义filter
        shiroFilter.getFilters().put("authc", new ShiroUserFilter());

        // 认证失败(用户没登录时)会重定向到这个loginUrl
        shiroFilter.setLoginUrl("/");
        // 授权失败重定向路径
        shiroFilter.setUnauthorizedUrl("/noPermission");
        // 定义一个LinkHashMap,保存认证的路径和规则
        Map<String,String> map = new LinkedHashMap();
        // key为uri,anon为标识符,标识不需要验证
        map.put("/login","anon");
        // authc表示Authentication,即是需要认证
        map.put("/admin/**","authc");
        // key “/**"表示所有uri,”/**“必须写在最后,如果不写该项,默认放行所有没配置的uri
        map.put("/**","authc");
        // 将该map设置进shiroFilter
        shiroFilter.setFilterChainDefinitionMap(map);
        return shiroFilter;
    }

7.测试

至此,前后端分离实现完毕

options请求正常

在这里插入图片描述

验证通过的请求能正常访问接口

在这里插入图片描述

补充

以上已经实现shiro的前后端分离功能,这里再补充下,将shiro默认的set-cookie行为去掉,只需要设置SessionManager的SessionIdCookieEnabled为false即可,如下在ShiroConfig中的SessionManager

    public DefaultWebSessionManager sessionManager() {
        CustomSessionManager sessionManager = new CustomSessionManager();
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionDAO(redisSessionDAO());
        // 将shiro默认的set-cookie行为去掉
        sessionManager.setSessionIdCookieEnabled(false);
        return sessionManager;
    }

至此,全篇结束

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【开源】基于Vue和SpringBoot的木马文件检测系统

项目编号&#xff1a; S 041 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S041&#xff0c;文末获取源码。} 项目编号&#xff1a;S041&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 木马分类模块2.3 木…

JavaScript 表达式

JavaScript 表达式 目录 JavaScript 表达式 一、赋值表达式 二、算术表达式 三、布尔表达式 四、字符串表达式 表达式是一个语句的集合&#xff0c;计算结果是个单一值。 在JavaScript中&#xff0c;常见的表达式有4种&#xff1a; &#xff08;1&#xff09;赋值表达式…

Postman接口自动化测试之——批量参数化(参数文件)

接口请求中的参数引用格式&#xff1a;{{参数名}} 参数文件只适用于集合中。 创建参数文件 以记事本举例&#xff0c;也可以使用其他编辑器&#xff1b;第一行参数名&#xff0c;用半角逗号&#xff08;英文逗号&#xff09;隔开&#xff0c;第二行为参数值&#xff0c;一样…

栈详解(C语言)

文章目录 写在前面1 栈的定义2 栈的初始化3 数据入栈4 数据出栈5 获取栈顶元素6 获取栈元素个数7 判断栈是否为空8 栈的销毁 写在前面 本片文章详细介绍了另外两种存储逻辑关系为 “一对一” 的数据结构——栈和队列中的栈&#xff0c;并使用C语言实现了数组栈。 栈C语言实现源…

Java基于springoot开发的企业招聘求职网站

演示视频&#xff1a; https://www.bilibili.com/video/BV1xw411n7Tu/?share_sourcecopy_web&vd_source11344bb73ef9b33550b8202d07ae139b 技术&#xff1a;springootmysqlvuejsbootstrappoi制作word模板 主要功能&#xff1a;求职者可以注册发布简历&#xff0c;选择简…

Redis面试题:redis做为缓存,mysql的数据如何与redis进行同步呢?(双写一致性)

目录 强一致性&#xff1a;延迟双删&#xff0c;读写锁。 弱一致性&#xff1a;使用MQ或者canal实现异步通知 面试官&#xff1a;redis做为缓存&#xff0c;mysql的数据如何与redis进行同步呢&#xff1f;&#xff08;双写一致性&#xff09; 候选人&#xff1a;嗯&#xff…

抖音生态融合:开发与抖音平台对接的票务小程序

为了更好地服务用户需求&#xff0c;将票务服务与抖音平台结合&#xff0c;成为了一个创新的方向。通过开发票务小程序&#xff0c;用户可以在抖音平台上直接获取相关活动的票务信息&#xff0c;完成购票、预订等操作&#xff0c;实现了线上线下的有机连接。 一、开发过程 1…

赢麻了!义乌一个村有5000个网红,有人年收租就300万!

#义乌一村电商年成交额超300亿# ,在中国&#xff0c;电商行业的发展可谓是日新月异&#xff0c;而位于浙江省义乌市的江北下朱村&#xff0c;正是这股潮流的一个典型代表。这个村子&#xff0c;处处弥漫着“直播”的气息&#xff0c;仿佛每个人都在为这个新兴行业助力。 江北下…

网安融合新进展:Check Point+七云网络联合研发,加固大型企业边缘、分支侧安全

AI 爆火、万物互联&#xff0c;底层需要更灵活的网络设施提供支撑。据国际分析机构 Gartner 预测&#xff0c;到 2024 年&#xff0c;SD-WAN&#xff08;软件定义的广域网&#xff09;使用率将达到 60%。不过边缘和终端兴起&#xff0c;未经过数据中心的流量也在成为新的安全风…

居家适老化设计第三十一条---卫生间水龙头

以上产品图片均来源于淘宝 侵权联系删除 居家适老化中&#xff0c;水龙头是一个非常重要的设备。水龙头的选择应该考虑到老年人的特点和需求。首先&#xff0c;水龙头的操作应该简单方便&#xff0c;老年人手部灵活性可能不如年轻人&#xff0c;因此水龙头应该设计成易于转动和…

torch.nn.batchnorm1d,torch.nn.batchnorm2d,torch.nn.LayerNorm解释:

批量归一化是一种加速神经网络训练和提升模型泛化能力的技术。它对每个特征维度进行标准化处理&#xff0c;即调整每个特征的均值和标准差&#xff0c;使得它们的分布更加稳定。 Batch Norm主要是为了让输入在激活函数的敏感区。所以BatchNorm层要加在激活函数前面。 1.torch.…

处理分类问题的不平衡数据的 5 种技术

一、介绍 分类问题在机器学习领域很常见。正如我们所知&#xff0c;在分类问题中&#xff0c;我们试图通过研究输入数据或预测变量来预测类标签&#xff0c;其中目标或输出变量本质上是分类变量。 如果您已经处理过分类问题&#xff0c;那么您一定遇到过以下情况&#xff1a;其…

【Go实现】实践GoF的23种设计模式:备忘录模式

上一篇&#xff1a;【Go实现】实践GoF的23种设计模式&#xff1a;命令模式 简单的分布式应用系统&#xff08;示例代码工程&#xff09;&#xff1a;https://github.com/ruanrunxue/Practice-Design-Pattern–Go-Implementation 简介 相对于代理模式、工厂模式等设计模式&…

PyQt6把QTDesigner生成的UI文件转成python源码,并运行

锋哥原创的PyQt6视频教程&#xff1a; 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计18条视频&#xff0c;包括&#xff1a;2024版 PyQt6 Python桌面开发 视频教程(无废话版…

1.1 C语言之入门:使用Visual Studio Community 2022运行hello world

1.1 使用Visual Studio Community 2022运行c语言的hello world 一、下载安装Visual Studio Community 2022 与 新建项目二、编写c helloworld三、编译、链接、运行 c helloworld1. 问题记录&#xff1a;无法打开源文件"stdio.h"2. 问题记录&#xff1a;调试和执行按钮…

Pinctrl子系统和GPIO子系统

Pinctrl子系统&#xff1a; 借助Princtr子系统来设置一个Pin的复用和电气属性&#xff1b; pinctrl子系统主要做的工作是&#xff1a;1. 获取设备树中的PIN信息&#xff1b;2.根据获取到的pin信息来设置的Pin的复用功能&#xff1b;3.根据获取到的pin信息去设置pin的电气特性…

vue+elementui如何实现在表格中点击按钮预览图片?

效果图如上&#xff1a; 使用el-image-viewer 重点 &#xff1a; 引入 import ElImageViewer from "element-ui/packages/image/src/image-viewer"; <template><div class"preview-table"><el-table border :data"tableData" …

Proteus仿真--高仿真数码管电子钟

本文介绍基于数码管的高仿真电子钟&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真图如下 本设计中80C51单片机作为主控&#xff0c;用74LS138作为数码管显示控制&#xff0c;共有4个按键&#xff0c;其中分别用于12/24小时显示切换、时间设置、小时加减控制和…

ZKP11.2 Fiat-Shamir and SNARGs

ZKP学习笔记 ZK-Learning MOOC课程笔记 Lecture 11: From Practice to Theory (Guest Lecturer: Alex Lombardi) 11.2 Fiat-Shamir and SNARGs Succinct Non-Interactive Arguments (SNARGs) This class so far: constructions of SNARGs using IOPs and a random oracle. …

3、MSF使用

文章目录 一、利用ms17-010漏洞对靶机执行溢出攻击二、后渗透模块meterpreter的使用 一、利用ms17-010漏洞对靶机执行溢出攻击 分别输入以下命令&#xff0c;使用ms17_010_eternalblue模块对目标机的ms17-010漏洞进行利用&#xff1a; use exploit/windows/smb/ms17_010_eter…