以下配置(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 Security 6 中,默认行为是
SecurityContextHolderFilter
只会从 SecurityContext
读取 SecurityContextRepository
并将其填充到 SecurityContextHolder
中。现在,如果用户希望 SecurityContext
在请求之间持续存在,则必须将 SecurityContextRepository
与 SecurityContext
一起显式保存。只需在必要时写入 SecurityContextRepository
(即 HttpSession
)即可消除歧义并提高性能。
SecurityContextHolder.setContext(securityContext);
securityContextRepository.saveContext(securityContext, httpServletRequest, httpServletResponse);
如果这不起作用,请尝试返回到 5.x 默认值:
http
.securityContext((securityContext) ->
.requireExplicitSave(false)
)
我想出了这个问题的解决方案。
正如您在 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;
}
}
这就是结果:
告诉他我们必须通过jwt吗?如果我们只想单独使用 spring security 行不通?