解决前后端分离Vue项目部署到服务器后出现的302重定向问题

news2024/11/28 10:56:15

解决前后端分离Vue项目部署到服务器后出现的302重定向问题

  • 问题描述
    • 问题原因定位
    • 问题解决方案
    • 校验修改效果
    • 相关阅读
    • 写在最后

问题描述

最近发现自己开发的vue前后端分离项目因为使用了spring security 安全框架,即使在登录认证成功之后再调用一些正常的接口总是会莫名奇妙地出现302重定向的问题,导致接口数据出不来。奇怪的是这个问题在本地开发环境并没有,而是部署到了服务器之后才会有。
无法加载接口响应数据
接口无法加载响应数据

接口重定向标识Location显示需要重新登录认证,而且这个请求还是GET请求
302重定向

问题原因定位

出现这个问题很显然是当前用户在Spring Security中丢失了认证信息,奇怪的是本地开发环境并不会出现这种问题,原因是我本地开发环境的前端用的是Vite启动的前端服务,而部署到服务器时却是Nginx起的前端服务。而笔者在Spring Security的配置类中注册了一个用于Jwt token认证的过滤器JwtAuthenticationFilterBean, 并注册在UsernamePasswordAuthenticationFilter之前。通过jwt token认证相当于spring security需要对用户的每次请求都先认证一次,如果用户的认证信息没有保存到SecurityContext类中的authentication中就会在调用非登录接口获取数据时出现这种重定向到登录页面的问题。

自定义的Jwt token认证类源码如下:

JwtAuthenticationFilterBean

 private final static Logger logger =             LoggerFactory.getLogger(JwtAuthenticationFilterBean.class);

    private String AUTHORIZATION_NAME = "Authorization";

    // private String BEARER = "Bearer";

    private static List<String> whiteRequestList = new ArrayList<>();

    static {
        whiteRequestList.add("/bonus/member/checkSafetyCode");
        whiteRequestList.add("/bonus/login");
        whiteRequestList.add("/bonus/member/login");
        whiteRequestList.add("/bonus/common/kaptcha");
        whiteRequestList.add("/bonus/admin/login");
        whiteRequestList.add("/bonus/favicon.ico");
        whiteRequestList.add("/bonus/doc.html");
        whiteRequestList.add("/bonus/error");
    }
@Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        logger.info("requestUrl="+request.getRequestURI());
        if(HttpMethod.OPTIONS.name().equals(request.getMethod())){
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        }
        if(whiteRequestList.contains(request.getRequestURI()) || (request.getRequestURI().contains("admin/dist") &&
                request.getRequestURI().endsWith(".css") || request.getRequestURI().equals(".js") ||
                request.getRequestURI().endsWith(".png") || request.getRequestURI().endsWith("favicon.ico"))){
            // 如果是登录和安全码验证请求直接放行
            filterChain.doFilter(servletRequest, servletResponse);
            return;
        } else {
               Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
               if(authentication!=null && authentication.getPrincipal()!=null){
                   MemInfoDTO memInfoDTO = (MemInfoDTO) authentication.getPrincipal();
                   logger.info("memInfoDTO={}", JSONObject.toJSONString(memInfoDTO));
                   filterChain.doFilter(servletRequest, servletResponse);
                   return;
               }
               String authToken = request.getHeader(AUTHORIZATION_NAME);
               if(StringUtils.isEmpty(authToken)){
                   String message = "http header Authorization is null, user Unauthorized";
                   response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                   response.setStatus(HttpStatus.UNAUTHORIZED.value());
                   this.printException(response, HttpStatus.UNAUTHORIZED.value(), message);
                   return;
               } else {
                   try {
                       DecodedJWT decodedJWT = JWT.decode(authToken);
                       Map<String, Claim> claimMap = decodedJWT.getClaims();
                       Claim expireClaim = claimMap.get("exp");
                       Date expireDate = expireClaim.asDate();
                       // 校验token 是否过期
                       if(expireDate.before(DateUtil.date(System.currentTimeMillis()))){
                           String message = "Authorization token expired";
                           this.printException(response, HttpStatus.UNAUTHORIZED.value(), message);
                           return;
                       }
                       Claim memAccountClaim = claimMap.get("memAccount");
                       if(memAccountClaim==null || StringUtils.isEmpty(memAccountClaim.asString())){
                           String message = "memAccount cannot be null";
                           response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                           response.setStatus(HttpStatus.UNAUTHORIZED.value());
                           this.printException(response, HttpStatus.UNAUTHORIZED.value(), message);
                           return;
                       }
                       filterChain.doFilter(servletRequest, servletResponse);
                   } catch (JWTDecodeException e) {
                       String message = "JWT decode authToken failed, caused by " + e.getMessage();
                       this.printException(response, HttpStatus.UNAUTHORIZED.value(), message);
                       return;
                   }
               }
        }

    }

上面的whiteRequestList中的元素为白名单请求,对于白名单请求Spring Security不进行拦截,直接放行。对于白名单中的请求部署到服务器后是不会有这种302重定向到登录页面的问题。因为这些白名单请求在Spring Security中也进行了放行, 源码如下。

SecurityConfig#configure(HttpSecurity)方法源码:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        JwtAuthenticationFilterBean jwtAuthenticationFilterBean = new JwtAuthenticationFilterBean();
        // http过滤器链中注册jwt token 认证过滤器
        http.addFilterBefore(jwtAuthenticationFilterBean, UsernamePasswordAuthenticationFilter.class);
        // 配置跨域
        http.cors().configurationSource(corsConfigurationSource())
                .and().logout().invalidateHttpSession(true).logoutUrl("/member/logout").permitAll()
        ;
        http.authorizeRequests()
                // 放行白名单请求
                .antMatchers("/member/checkSafetyCode").permitAll()
                .antMatchers("/doc.html").permitAll()
                .antMatchers("/common/kaptcha").permitAll()
                .antMatchers("/admin/login").permitAll()
                .anyRequest().authenticated()
                .and().httpBasic()
                // 表单登录认证
                .and().formLogin()
                .loginPage(loginPageUrl)
                // 自定用户登录处理接口
                .loginProcessingUrl("/member/login")
                .successHandler((httpServletRequest, httpServletResponse, authentication) -> {              // httpServletResponse参数中返回用户信息和jwt token给客户端
httpServletResponse.setContentType("application/json;charset=utf-8");
                     httpServletResponse.setStatus(HttpStatus.OK.value());
                     PrintWriter printWriter = httpServletResponse.getWriter();
                     // 从认证信息中获取用户信息
                     MemInfoDTO memInfoDTO = (MemInfoDTO) authentication.getPrincipal();
                     Map<String, Object> userMap = new HashMap<>();
                     userMap.put("memId", memInfoDTO.getMemId());
                     userMap.put("memAccount", memInfoDTO.getMemAccount());
                     userMap.put("memPwd", memInfoDTO.getMemPwd());
                     BigDecimal totalCredit = memInfoDTO.getTotalCreditAmount()!=null?new BigDecimal(memInfoDTO.getTotalCreditAmount()/100, mathContext): new BigDecimal("0.0");
                     userMap.put("totalCreditAmount", totalCredit);
                     BigDecimal usedCredit = memInfoDTO.getUsedCreditAmount()!=null?new BigDecimal(memInfoDTO.getUsedCreditAmount()/100, mathContext):new BigDecimal("0.0");
                     userMap.put("usedCreditAmount", usedCredit);
                     Long remainCredit = (memInfoDTO.getTotalCreditAmount()==null?0:memInfoDTO.getTotalCreditAmount()) - (memInfoDTO.getUsedCreditAmount()==null?0:memInfoDTO.getUsedCreditAmount());
                     BigDecimal remainCreditAmount = new BigDecimal(remainCredit/100, mathContext);
                     userMap.put("remainCreditAmount", remainCreditAmount);
                     userMap.put("authorities", memInfoDTO.getAuthorities());
                     Map<String, Object> dataMap = new HashMap<>();
                     dataMap.put("memInfo", userMap);
                     dataMap.put("authenticatedToken", JwtTokenUtil.genAuthenticatedToken(userMap)); // 根据用户信息生成jwt token
                     ResponseResult<Map<String, Object>> responseResult = ResponseResult.success(dataMap, "login success");
                     printWriter.write(JSONObject.toJSONString(responseResult));
                     printWriter.flush();
                     printWriter.close();
                }).permitAll()
                .and().csrf().disable() // 禁用csrf
            .exceptionHandling() //认证异常处理
            .accessDeniedHandler(accessDeniedHandler());
    }

问题解决方案

有两种方式解决这个部署到服务器后产生的302重定向问题

  • 第一种就是在Spring Security的配置类的configure(HttpSecurity)方法中对出现302重定向的请求进行放行,向放行白名单请求一样进行处理。不过这种方式解决的话相当于弃用了Spring Security安全框架,任意用户都能访问后台接口,应用没有安全可言,不推荐使用;
  • 第二种方式便是在JwtAuthenticationFilterBean#doFilter方法中通过反解jwt token得到访问用户的身份信息后,再将其存入SpringSecurityContextHolder类中与当前线程绑定的SecurityContext类变量contextauthentication变量中,源码如下:
try {
                       DecodedJWT decodedJWT = JWT.decode(authToken);
                       Map<String, Claim> claimMap = decodedJWT.getClaims();
                       Claim expireClaim = claimMap.get("exp");
                       Date expireDate = expireClaim.asDate();
                       // 校验token 是否过期
                       if(expireDate.before(DateUtil.date(System.currentTimeMillis()))){
                           String message = "Authorization token expired";
                           this.printException(response, HttpStatus.UNAUTHORIZED.value(), message);
                           return;
                       }
                       Claim memAccountClaim = claimMap.get("memAccount");
                       if(memAccountClaim==null || StringUtils.isEmpty(memAccountClaim.asString())){
                           String message = "memAccount cannot be null";
                           response.setContentType(MediaType.APPLICATION_JSON_VALUE);
                           response.setStatus(HttpStatus.UNAUTHORIZED.value());
                           this.printException(response, HttpStatus.UNAUTHORIZED.value(), message);
                           return;
                       }
                      logger.info("用户:"+memAccountClaim.asString()+" 调用请求 "+request.getRequestURI()+" 需要重新获得认证");
                       // 组装认证信息
                       MemInfoDTO memInfoDTO = new MemInfoDTO();
                       memInfoDTO.setMemAccount(memAccountClaim.asString());
                       Claim memIdClaim = claimMap.get("memId");
                       memInfoDTO.setMemId(memIdClaim.asLong());
                       Claim memPwdClaim = claimMap.get("memPwd");
                       memInfoDTO.setMemPwd(memPwdClaim.asString());
                       Claim totalCreditClaim = claimMap.get("totalCreditAmount");
                       Double totalCreditAmount = totalCreditClaim.asDouble()*100;
                       String totalCreditAmountStr = String.valueOf(totalCreditAmount);
                       logger.info("totalCreditAmountStr={}", totalCreditAmountStr);
                       if(totalCreditAmountStr.lastIndexOf(".")>-1){
                           memInfoDTO.setTotalCreditAmount(Long.valueOf(totalCreditAmountStr.substring(0, totalCreditAmountStr.lastIndexOf("."))));
                       } else {
                           memInfoDTO.setTotalCreditAmount(Long.valueOf(totalCreditAmountStr));
                       }
                       Claim usedCreditClaim = claimMap.get("usedCreditAmount");
                       Double usedCreditAmount = usedCreditClaim.asDouble()*100;
                       String usedCreditAmountStr = String.valueOf(usedCreditAmount);
                       if(usedCreditAmountStr.lastIndexOf(".")>-1){
                           memInfoDTO.setUsedCreditAmount(Long.valueOf(usedCreditAmountStr.substring(0, usedCreditAmountStr.lastIndexOf("."))));
                       } else {
                           memInfoDTO.setUsedCreditAmount(Long.valueOf(usedCreditAmountStr));
                       }
                       Claim authorityClaim = claimMap.get("authorities");
                       List<String> authorities = authorityClaim.asList(String.class);
                       List<GrantedAuthority> authorityList = new ArrayList<>(authorities.size());
                       for(String authority: authorities){
                           SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(authority);
                           authorityList.add(grantedAuthority);
                       }
                       memInfoDTO.setAuthorities(authorityList);
                       // 组装认证对象
                       UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(memInfoDTO, memInfoDTO.getMemPwd(), memInfoDTO.getAuthorities());
                       // 将认证对象放入SecurityContext中
                       SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                       // 请求头认证通过, 放行请求
                       filterChain.doFilter(servletRequest, servletResponse);

校验修改效果

修改好源码后重新打包部署到服务器(关于如何打包部署,网上已有很多详细的指导文章,这里就不赘述了)

部署好应用之后登录之后系统会自动跳转到首页http://javahsf.club:3000/home

这时候就不会有之前的302重定向问题了,也可以看到页面的数据成功加载出来了

bonusResult
通过F12调试模式查看网络请求也可以看到没有302重定向的问题了,数据也成功返回了
getDataSuccess
为了进一步验证调用这个接口时需要重新认证用户的登录信息,我们通过在部署目录执行 cat ./logs/spring.log命令可以看到下面这几行日志信息

2023-01-15 16:22:10.418  INFO 9638 --- [http-nio-0.0.0.0-8090-exec-2] c.b.b.c.JwtAuthenticationFilterBean      : requestUrl=/bonus/openResult/page/data
2023-01-15 16:22:10.509  INFO 9638 --- [http-nio-0.0.0.0-8090-exec-2] c.b.b.c.JwtAuthenticationFilterBean      : 用户:heshengfu 调用请求 /bonus/openResult/page/data 需要重新获得认证

由此验证了302重定向的问题是接口之前是spring security框架需要重新认证用户登录信息却没有拿到用户的认证信息导致的,只需要调用这个接口验证jwt token信息,然后解析出用户身份信息后重新保存到SecurityContextHolder类的SecurityContext类型变量context中的Authentication变量authentication中,问题就得到了解决。

相关阅读

【1】Spring Security的项目中集成JWT Token令牌安全访问后台API
需要本文源码的朋友可通过笔者发布在个人微信公众号上的这篇在文末的获取项目源码的方式获取

写在最后

本文首发个人微信公众号【阿福谈Web编程】,欢迎喜欢我的文章的读者朋友们加个关注,大家一起交流学习,谢谢。
关注公众号

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

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

相关文章

Xilinx 7系列FPGA之Spartan-7产品简介

以最低的成本获得无与伦比的性能和功耗效率如果您对功耗或性能的要求与成本要求一样严苛&#xff0c;那么请使用 Spartan -7 FPGA。该系列采用 TSMC&#xff08;台积电&#xff09; 的 28nm HPL 工艺制造&#xff0c;将小尺寸架构的Xilinx 7 系列FPGA 的广泛功能和符合 RoHS 标…

结构体专题详解

目录 &#x1f94e;什么是结构体&#xff1f; ⚾结构体的声明 &#x1f3c0;简单结构体的声明 &#x1f3d0;结构体的特殊声明 &#x1f3c8;结构体嵌套问题 &#x1f3c9;结构体的自引用 &#x1f3b3;结构体的内存大小 &#x1f94c;结构体的内存对齐 ⛳内存对齐的优点 ⚽还…

SAP 服务器参数文件详细解析

一、SAP参数的说明 SAP参数的学习需要了解SAP参数的作用、参数的启动顺序、参数的配置&#xff1b; 1、参数的启动顺序 a) 启动Start profileb) 启动default profilec) 启动instance profile 2、参数的位置 a) 启动参数Start profile的位置&#xff1a;/usr/sap//SYS/prof…

计讯物联数字乡村解决方案赋能乡村振兴

项目背景 数字乡村是乡村振兴的战略方向&#xff0c;是推动农村现代化的重要途径。当前&#xff0c;数字乡村建设正在加速推进&#xff0c;打造乡村数字治理新模式&#xff0c;提升乡村的数字化水平&#xff0c;进一步推动乡村振兴进入高质量发展新赛道。计讯物联作为数字乡村…

机器学习的几个公式

今天看了几个公式的推演过程&#xff0c;有些推演过程还不是很明白&#xff0c;再着担心自己后面会忘记&#xff0c;特来此记下笔记。python 是由自己特定的公式符号的&#xff0c;但推演过程需要掌握&#xff0c;其实过程不过程不是重点&#xff0c;只要是要记得公式的含义&am…

SocketCAN 命名空间 VCAN VXCAN CANGW 举例

文章目录NAMESPACESocketCAN最新 can-utils 安装VCAN 举例VXCAN 举例CANGW 举例参考NAMESPACE namespaces, 命名空间, 将全局系统资源包装在抽象中, 使命名空间中的进程看起来拥有自己全局资源的独立实例. 命名空间的一个用途是实现容器. Linux 命名空间类型及隔离物(Isolate…

译文 | Kubernetes 1.26:PodDisruptionBudget 守护不健康 Pod 时所用的驱逐策略

对于 Kubernetes 集群而言&#xff0c;想要确保日常干扰不影响应用的可用性&#xff0c;不是一个简单的任务。上月发布的 Kubernetes v1.26 增加了一个新的特性&#xff1a;允许针对 PodDisruptionBudget (PDB) 指定不健康 Pod 驱逐策略&#xff0c;这有助于在节点执行管理操作…

电商云仓是如何包装发货的?

包装不时是为了维护产品&#xff0c;而它从工厂地板移动到大型仓库&#xff0c;并最终经过批发或批发店抵达消费者。但是&#xff0c;自21世纪初以来&#xff0c;消费者希望与那些不时吸收着某种情感的品牌联络在一同&#xff0c;同时央求他们在心理上对品牌中止投资&#xff0…

【Java AWT 图形界面编程】Canvas 组件中使用 Graphics 绘图 ④ ( AWT 绘图窗口闪烁问题 )

文章目录一、AWT 绘图窗口闪烁问题二、完整代码示例一、AWT 绘图窗口闪烁问题 使用 Graphics 第一次绘图 完成后 , 如果在循环中 持续调用 Canvas#repaint() 函数刷新界面 , 代码如下 : import java.awt.*;public class HelloAWT {public static void main(String[] args) thr…

MySQL进阶——存储引擎

MySQL有9种存储引擎&#xff0c;不同的引擎&#xff0c;适合不同的场景&#xff0c;我们最常用的&#xff0c;可能就是InnoDB&#xff0c;应该是从5.5开始&#xff0c;就成为了MySQL的默认存储引擎。 show engines可以查询MySQL支持的这几种存储引擎&#xff0c;从表头能看出来…

SVN工程转Git工程Github托管

SVN工程转Git工程&Github托管1. 介绍2. autoAudioTest之SVN转Github步骤Step 1 工作环境(ubuntu)Step 2 安装升级必要软件Step 3 转换脚本Step 4 检查软件运行环境Step 5 生成authors.txtStep 6 SVN转换Git格式Step 7 Github新建空工程Step 8 Git提交已有工程Step 9 Git提交…

Dubbo快速入门看这一篇文章就够了

网站用户少,流量小,抗压力差(eg: ssm)网站用户量进一步增长,流量增多,服务器不能平滑扩容(eg: 多个ssm)网站用户和流量随时间稳步升高,需要随时进行服务器扩容(eg: rpc/http) 第2节 分布式框架解决的问题 1 2 3 4 5 6随着互联网架构的越来越复杂,由原来的单一架构 ...到... 流动…

M320、M601、HD1(RTU)功能对比

M320、M601、HD1_RTU硬件 / 软件功能对比一、硬件1.HD1-RTU2.Haas506-M3203.Haas506-M6014.对比区别二、软件1.对比区别一、硬件 1.HD1-RTU 详情参考HaaS506-HD1 (RTU) - 硬件介绍 2.Haas506-M320 详情参考HaaS506-M320 - 开发板介绍 3.Haas506-M601 详情参考HaaS506…

(二十二)简单算法和Lambda表达式

目录 前言: 1.选择排序 2.二分查找 3.Lambda表达式 前言: 算法是一个程序和软件的灵魂&#xff0c;要成为一名优秀的程序员&#xff0c;只有对基础算法全面掌握&#xff0c;才能在设计程序和编写代码的过程中显得得心应手。常用的基础算法有快速排序算法、堆排序算法、归并排…

每日一问-ChapGPT-20230115-关于断舍离

文章目录每日一问-ChapGPT系列起因每日一问-ChapGPT-20230115-关于断舍离人类脑是适合专心做一件事&#xff0c;还是适合并行做多件事做事情的优先顺序怎样安排chapGPT你是怎么学到这么多知识的chapGPT你拥有智慧吗chapGPT你是实时更新自己的模型吗chapGPT你有情感吗chapGPT你有…

C++程序卡死、UI界面卡顿问题的原因分析与总结

目录 1、概述 2、软件卡死问题 2.1、死循环 2.2、死锁 3、客户端软件的UI界面卡顿问题 3.1、UI线程在频繁地写日志到文件中&#xff0c;导致UI线程时不时的卡顿 3.2、从网上拷贝的代码中调用Sleep函数&#xff0c;导致UI界面有明显的卡顿 4、总结 VC常用功能开发汇总&a…

COCO_03 制作COCO格式数据集 dataset 与 dataloader

文章目录1 引言2 pycocotools介绍3 Dataset 构建4 Dataloader 构建4.1 解决batch中tensor维度不一致的打包问题4.2 collate_fn()函数分析AppendixA. convert_coco_poly_maskB. COCO_Transform参考1 引言 在之前的文章中&#xff0c;我们认识了COCO数据集的基本格式https://blo…

【设计模式】创建型模式·工厂模式

设计模式学习之旅(四) 查看更多可关注后查看主页设计模式DayToDay专栏 一.引子 需求&#xff1a;设计一个咖啡店点餐系统。 设计一个咖啡类&#xff08;Coffee&#xff09;&#xff0c;并定义其两个子类&#xff08;美式咖啡【AmericanCoffee】和拿铁咖啡【LatteCoffee】&…

NoSQLBooster for MongoDB 8.0.1 Crack

最智能的 MongoDB IDE NoSQLBooster 是 MongoDB Server 3.6-6.0 的跨平台 GUI 工具&#xff0c;它提供内置的 MongoDB 脚本调试器、全面的服务器监控工具、链接流畅查询、SQL 查询、查询代码生成器、任务调度、ES2020 支持和高级 IntelliSense经验。新版本 8.0 现已推出&#x…

Laravel文档阅读笔记-How to Build a Rest API with Laravel: A Beginners Guide①

随着移动端和JavaScript框架的发展&#xff0c;比如React和Vue&#xff0c;Restful风格的API越来越流行。使用Restful风格的好处就是一个后端程序可以与多个版本的前端用户界面关联。 Laravel提供了创建Rest API的环境和生态。 首先得导入依赖包比如Laravel Passport和Larave…