Solve the 302 redirection problem that occurs after the front-end and back-end separate Vue projects are deployed to the server

Problem Description

Recently, I found that the front-end and back-end separation project of vue developed by myself uses the spring security security framework. Even if some normal interfaces are called after successful login authentication, there will always be inexplicable 302 redirection problems, resulting in interface data not coming out. The strange thing is that this problem does not exist in the local development environment, but only after it is deployed to the server.

The interface cannot load the response data

The interface redirection identifier Location shows that re-login authentication is required, and this request is still a GET request

Troubleshooting

This problem is obviously because the current user has lost the authentication information in Spring Security. The strange thing is that this problem does not occur in the local development environment. The reason is that the front end of my local development environment uses the front-end service started by Vite, and the deployment When it comes to the server, it is the front-end service started by Nginx. And the author registered a filter JwtAuthenticationFilterBean for Jwt token authentication in the configuration class of Spring Security, and registered it before UsernamePasswordAuthenticationFilter. Passing jwt token authentication is equivalent to spring security needing to authenticate each user's request first. If the user's authentication information is not saved in the authentication in the SecurityContext class, this redirection will occur when the non-login interface is called to obtain data. Problem with login page.

The source code of the custom Jwt token authentication class is as follows:

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"))){
            // If it is a login and security code verification request, it is directly released
            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();
                       // Check whether the token has expired
                       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;
                   }
               }
        }

    }

The elements in the above whiteRequestList are whitelist requests, and Spring Security does not intercept the whitelist requests, but releases them directly. After the request in the whitelist is deployed to the server, there will be no such problem of 302 redirection to the login page. Because these whitelist requests are also released in Spring Security, the source code is as follows.

SecurityConfig#configure(HttpSecurity) method source code:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        JwtAuthenticationFilterBean jwtAuthenticationFilterBean = new JwtAuthenticationFilterBean();
        // Register the jwt token authentication filter in the http filter chain
        http.addFilterBefore(jwtAuthenticationFilterBean, UsernamePasswordAuthenticationFilter.class);
        // Configure cross-domain
        http.cors().configurationSource(corsConfigurationSource())
                .and().logout().invalidateHttpSession(true).logoutUrl("/member/logout").permitAll()
        ;
        http.authorizeRequests()
                // Allow whitelist request
                .antMatchers("/member/checkSafetyCode").permitAll()
                .antMatchers("/doc.html").permitAll()
                .antMatchers("/common/kaptcha").permitAll()
                .antMatchers("/admin/login").permitAll()
                .anyRequest().authenticated()
                .and().httpBasic()
                // Form Login Authentication
                .and().formLogin()
                .loginPage(loginPageUrl)
                // Custom user login processing interface
                .loginProcessingUrl("/member/login")
                .successHandler((httpServletRequest, httpServletResponse, authentication) -> {              // The httpServletResponse parameter returns user information and jwt token to the client
httpServletResponse.setContentType("application/json;charset=utf-8");
                     httpServletResponse.setStatus(HttpStatus.OK.value());
                     PrintWriter printWriter = httpServletResponse.getWriter();
                     // Obtain user information from authentication information
                     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)); // Generate jwt token based on user information
                     ResponseResult<Map<String, Object>> responseResult = ResponseResult.success(dataMap, "login success");
                     printWriter.write(JSONObject.toJSONString(responseResult));
                     printWriter.flush();
                     printWriter.close();
                }).permitAll()
                .and().csrf().disable() // disable csrf
            .exceptionHandling() //Authentication exception handling
            .accessDeniedHandler(accessDeniedHandler());
    }

problem solution

There are two ways to solve the 302 redirection problem after deploying to the server

  • The first is to release the request with 302 redirection in the configure(HttpSecurity) method of the Spring Security configuration class, and process it like a whitelist request. However, this solution is equivalent to abandoning the Spring Security security framework, any user can access the background interface, the application is not safe at all, and it is not recommended;
  • The second way is to obtain the identity information of the accessing user by reversing the jwt token in the JwtAuthenticationFilterBean#doFilter method, and then store it in the authentication variable of the SecurityContext variable context bound to the current thread in the SpringSecurityContextHolder class. The source code is as follows :
try {
                       DecodedJWT decodedJWT = JWT.decode(authToken);
                       Map<String, Claim> claimMap = decodedJWT.getClaims();
                       Claim expireClaim = claimMap.get("exp");
                       Date expireDate = expireClaim.asDate();
                       // Check whether the token has expired
                       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("user:"+memAccountClaim.asString()+" call request "+request.getRequestURI()+" Need to re-certify");
                       // Assembly Certification Information
                       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);
                       // Assembly certification object
                       UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(memInfoDTO, memInfoDTO.getMemPwd(), memInfoDTO.getAuthorities());
                       // Put the authentication object into the SecurityContext
                       SecurityContextHolder.getContext().setAuthentication(authenticationToken);
                       // Request header authentication passed, release request
                       filterChain.doFilter(servletRequest, servletResponse);

Verify the modification effect

After modifying the source code, repackage and deploy to the server (there are many detailed guidance articles on the Internet about how to package and deploy, so I won’t go into details here)

After deploying the application and logging in, the system will automatically jump to the home page http://javahsf.club:3000/home

At this time, there will be no previous 302 redirection problems, and you can also see that the data on the page has been successfully loaded.


You can also see that there is no 302 redirection problem by checking the network request in F12 debugging mode, and the data is returned successfully.

In order to further verify that the user's login information needs to be re-authenticated when calling this interface, we can see the following lines of log information by executing the cat ./logs/spring.log command in the deployment directory

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      : user:heshengfu call request /bonus/openResult/page/data Need to re-certify

It is thus verified that the problem of 302 redirection is caused by the spring security framework that needs to re-authenticate the user login information before the interface but does not get the user's authentication information. You only need to call this interface to verify the jwt token information, and then parse out the user identity information. Save it again to the Authentication variable authentication in the SecurityContext type variable context of the SecurityContextHolder class, and the problem is solved.

Related Reading

[1]Integrate JWT Token token security access background API in Spring Security project
Friends who need the source code of this article can obtain it through the method of obtaining the source code of the project at the end of the article published by the author on the personal WeChat public account

write at the end

This article is the first personal WeChat public account [Afu talks about Web programming]. Readers and friends who like my article are welcome to add attention, and everyone can communicate and learn together, thank you.

Tags: Front-end Vue Vue.js server

Posted by nineseveninteractive on Mon, 16 Jan 2023 01:57:39 +0300