文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

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

2023-09-15 17:33

关注

解决前后端分离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重定向问题

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/data2023-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编程】,欢迎喜欢我的文章的读者朋友们加个关注,大家一起交流学习,谢谢。
关注公众号

来源地址:https://blog.csdn.net/heshengfu1211/article/details/128698765

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     801人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     348人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     311人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     432人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     220人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-服务器
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯