Spring Boot结合JWT的会话管理

3

你能帮我处理这种情况吗?我正在开发一个移动应用程序,在Spring Boot服务器端没有维护会话。因此,我正在使用JWT,客户端每次请求时都会发送令牌。

客户端应用程序将数据与令牌一起(按请求逐页)发送到服务器。服务器需要临时存储这些数据并等待响应数据到达。它必须全部或不全部存储在数据库中。

通常,对于传统的Web应用程序,这是通过会话实现的。我尝试使用会话,但它无法保持维护。但是,当请求来自Postman时,会话是维护的。客户端应用程序运行在端口8000上,而服务器运行在SSL端口8443上。有一件事很明确,服务器认为来自同一客户端的每个请求都是匿名的,尽管每个请求都接收到了令牌。

SecurityConfigurer.java

@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter{

    @Autowired
    private MyUserDetailsService userDetailsService;
    
    @Autowired
    private JwtRequestFilter jwtRequestFilter;
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }
    
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
        .cors().and()
        .authorizeRequests().antMatchers("/authenticate").permitAll()
        .anyRequest().authenticated()
        .and().sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
    
    

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        // TODO Auto-generated method stub
        return super.authenticationManagerBean();
    }
    
}



JwtRequestFilter.java

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    @Autowired
    private MyUserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        final String authorizationHeader = request.getHeader("Authorization");
        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            if (jwtUtil.validateToken(jwt, userDetails)) {
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                usernamePasswordAuthenticationToken
                        .setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            }
        }
        
          response.setHeader("Access-Control-Allow-Origin", "*");
          response.setHeader("Access-Control-Allow-Credentials", "true");
          response.setHeader("Access-Control-Allow-Methods",
          "POST, GET, OPTIONS, DELETE"); response.setHeader("Access-Control-Max-Age",
          "3600");
         

        filterChain.doFilter(request, response);
    }

}

QuizController.java

@CrossOrigin("*")
@RestController
public class QuizController {

    @Autowired
    private QuizRepository service;

    @Autowired
    private QuizSummaryRespository summaryService;

    @Autowired
    private AuthenticationManager authenticationManager;
    
    @Autowired
    private MyUserDetailsService userDetailsService;
    
    @Autowired
    private JwtUtil jwtTokenUtil;

    @SuppressWarnings("unchecked")
    @ResponseBody
    @PostMapping("/quiz")
    public ResponseEntity<?> saveQuiz(Quiz quiz, @RequestParam String status, @RequestParam long time,
            HttpServletRequest request, HttpServletResponse response, @RequestHeader Map<String, String> headers) {
        

        Map<String, String> map = new HashMap<>();
        List<Quiz> myQuizzes = (List<Quiz>) request.getSession().getAttribute("code"); //This line always return null list

        if (quiz.getCode().equals("")) {
            quiz.setCode(Utility.generateCode());
            myQuizzes = new ArrayList<>();
        }

        myQuizzes.add(quiz);
        request.getSession().setAttribute("code", myQuizzes);

        map.put("code", quiz.getCode());

        return ResponseEntity.ok(map);
    }
}

所以在您的情况下,用户将分步提交响应,服务器需要保存所有内容。我只是想了解您的问题是否正确。 - nicholasnet
谢谢你的回复,伙计。 是的,你的理解是正确的。 请求来自应用程序,服务器不知道哪个客户端请求与同一客户端的先前请求相关。没有维护任何cookie或会话。客户端每次请求发送的唯一标识仅为服务器生成的唯一令牌。服务器仅为每个客户端第一次生成令牌。在随后的请求中,客户端发送此令牌。 是否有一种方法可以基于这些唯一令牌创建Spring会话?然后每次根据此令牌访问相同的会话。 - Karar Haider
1个回答

3

好的,我能想到几种解决这个问题的方法。

第一种方法

在前端将所有先前的响应存储在sessionStorage或localStorage中,并在完成时一次性发送所有响应。

第二种方法

在后端,在第一个请求期间使用唯一的ID存储响应并向客户端发送唯一的ID。在每个后续请求中,客户端将需要发送该唯一的ID以及订单和响应。完成后,获取所有响应并按顺序合并它们。您可以在此处使用任何类型的存储,无论是数据库、缓存还是纯数组,都可以满足您的需求。


在第二种方法中,您的意思是要使用唯一标识存储请求,对吗?这应该是可能的。只是为了澄清一下,我们没有办法基于唯一标识创建自己的会话对象吗?每次当我们从客户端请求获取唯一标识时,我们从内存中获取该会话对象并添加更多数据。我现在也得到了一个提示,“在这种情况下,会话的概念可能不适用”。 - Karar Haider
第一种或第二种方法都不涉及会话。在第二种方法中,当您第一次收到请求时,生成唯一的IDUUID.randomUUID().toString()(或其他ID生成器),然后使用该ID按顺序跟踪响应。 - nicholasnet
嗨,nicholasnet,我实现了第二种方法,对我来说效果很好。谢谢! - Karar Haider
我尝试接受,但它不允许我,可能是因为我的分数水平不够 :-( - Karar Haider

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接