Spring Security:在 SecurityContext 中找不到身份验证对象

问题描述 投票:0回答:3

以下配置(filterChain)在 SpringBoot-2.7.5 中工作正常,但在我尝试在 SpringBoot-3.0.0-RC1 中测试它之后,它不起作用并显示以下消息,如果想要的话我需要更改任何内容迁移到 Spring-Boot-3.0.0。谢谢。

{ “时间戳”:1667794247614, “状态”:401, "error": "未经授权", "message": "在 SecurityContext 中未找到身份验证对象", “路径”:“/api/admin/1”}

 @Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable()
        .exceptionHandling().authenticationEntryPoint(jwtAuthenticationProvider).and()
        .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
        .authorizeRequests()
        .antMatchers("/**").permitAll()            
        // private endpoints
        .anyRequest().authenticated();

    http.addFilterBefore(jwtTokenFilter, UsernamePasswordAuthenticationFilter.class);

    return http.build();
}

以下是jwtTokenFilter:

@Component
public class **JwtTokenFilter** extends OncePerRequestFilter {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    
    @Autowired
    private JPAUserDetailService jpaUserDetailService;

    

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        // Get authorization header and validate
        final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
        
        if (isEmpty(header) || !header.startsWith("Bearer ")) {
            chain.doFilter(request, response);
            return;
        }

        // Get jwt token and validate
        final String token = header.split(" ")[1].trim();
        
        if (!jwtTokenUtil.validate(token)) {
                                    
            chain.doFilter(request, response);
            
            return;
        }

        // Get user identity and set it on the spring security context
        UserDetails userDetails = jpaUserDetailService.loadUserByUsername(jwtTokenUtil.getUsername(token));

        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, (userDetails == null ? null : userDetails.getAuthorities()));

        
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        SecurityContextHolder.getContext().setAuthentication(authentication);
                
        chain.doFilter(request, response);
    }

}
spring-boot spring-security
3个回答
3
投票

在 Spring Security 6 中,默认行为是

SecurityContextHolderFilter
只会从
SecurityContext
读取
SecurityContextRepository
并将其填充到
SecurityContextHolder
中。现在,如果用户希望
SecurityContext
在请求之间持续存在,则必须将
SecurityContextRepository
SecurityContext
一起显式保存。只需在必要时写入
SecurityContextRepository
(即
HttpSession
)即可消除歧义并提高性能。

SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);

参见https://docs.spring.io/spring-security/reference/5.8/migration.html#_explicit_save_securitycontextrepository

如果这不起作用,请尝试返回到 5.x 默认值:

http
    .securityContext((securityContext) -> 
            .requireExplicitSave(false)
    )

1
投票

我想出了这个问题的解决方案。

正如您在 Spring Security 6.x 中看到的那样:

SecurityContextHolderFilter 负责加载 使用 SecurityContextRepository 的请求之间的 SecurityContext。

并且:

SecurityContextHolder 与 SecurityContext 还保存 SecurityContext 到 SecurityContextRepository(如果应该) 在请求之间持续存在。

所以我将 SecurityContextHolderRepository 添加到 SecurityFilterChain 中:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig{

    @Resource(name = "jwtService")
    private UserDetailsService userDetailsService;

    @Bean
    public PasswordEncoder encoder(){
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
        return config.getAuthenticationManager();
    }

    @Bean
    public AuthenticationProvider authenticationProvider(){
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService);
        authenticationProvider.setPasswordEncoder(encoder());
        return authenticationProvider;
    }

    @Bean
    public JwtAuthenticationFilter jwtAuthenticationFilter(){
        return new JwtAuthenticationFilter();
    }
    @Bean
    public SecurityContextRepository securityContextRepository(){
        return new NullSecurityContextRepository(); // I use Null Repository since I don't need it for anything except store information in UserDetails
    }
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception{
        http.cors().and()
                .csrf().disable()
                .authorizeHttpRequests()
                .requestMatchers("/api/auth/n/**").permitAll()
                .requestMatchers("/api/auth/r/testing").permitAll() // The API need JWT for authentication
                .and()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .securityContext((securityContext) -> securityContext.securityContextRepository(securityContextRepository())) // Add Security Context Holder Repository
                .authenticationProvider(authenticationProvider());

        http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class); // Add it before UserName and Password AuthenticationFilter
        return http.build();
    }
}

然后在我的自定义 Jwt Filter 中,我将 SecurityContext 保存到 NullContextRepository 中并解决了问题:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    private static final Logger LOG = LoggerFactory.getLogger(JwtAuthenticationFilter.class);

    @Resource
    private JwtTokenUtil jwtTokenUtil;

    @Resource
    private JwtUserDetailsService userDetailsService;

    @Resource
    private SecurityContextRepository securityContextRepository;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String jwt = getJwtFromRequest(request);

        if(!StringUtils.isEmpty(jwt)){
            jwtTokenUtil.validateToken(jwt);
            String userName = jwtTokenUtil.getUserNameFromToken(jwt);
            UserDetails userDetails = userDetailsService.loadUserByUsername(userName);

            if (userDetails != null && userDetails instanceof CustomUserDetails) {
                Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());

                SecurityContext securityContext = SecurityContextHolder.getContext();
                securityContext.setAuthentication(authentication);
                SecurityContextHolder.setContext(securityContext);

                securityContextRepository.saveContext(securityContext, request, response); // ADD this 
                LOG.info("Authentication has user name : " + ((UserDetails)authentication.getPrincipal()).getUsername());
            }
        }

        filterChain.doFilter(request, response);
    }

    private String getJwtFromRequest(HttpServletRequest request){
        String bearerToken = request.getHeader("Authorization");
        if(bearerToken == null){
            return null;
        }
        if(bearerToken.startsWith("Bearer ")){
            return bearerToken.substring(7);
        }
        return null;
    }
}

这就是结果:


0
投票

告诉他我们必须通过jwt吗?如果我们只想单独使用 spring security 行不通?

© www.soinside.com 2019 - 2024. All rights reserved.