Spring Security所有请求返回403 Forbidden问题排查求助
Spring Security JWT认证配置问题
所有请求均返回403 Forbidden响应。移除@Configuration和@EnableWebSecurity注解后能看到localhost:8080默认登录页,但启用配置后无法访问;禁用CSRF也未解决问题。此外,自定义过滤器链配置异常:希望JWTGeneratorFilter在JWTAuthenticationFilter之前执行,且两者都在BasicAuthenticationFilter之前,但配置后抛出异常。
相关代码
SecurityConfiguration
@Configuration @EnableWebSecurity public class SecurityConfiguration { private final SecurityUtil securityUtil; public SecurityConfiguration(SecurityUtil securityUtil) { this.securityUtil = securityUtil; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) .cors(corsCustomizer -> corsCustomizer.configurationSource(new CorsConfigurationSource() { @Override public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(Collections.singletonList("http://localhost:4200")); config.setAllowedMethods(Collections.singletonList("*")); config.setAllowCredentials(true); config.setAllowedHeaders(Collections.singletonList("*")); config.setExposedHeaders(Arrays.asList("Authorization")); config.setMaxAge(3600L); return config; } })).csrf().disable() .addFilterAfter(new JWTGeneratorFilter(securityUtil), BasicAuthenticationFilter.class) .addFilterBefore(new JWTAuthenticationFilter(), BasicAuthenticationFilter.class) .authorizeHttpRequests((requests) -> requests .requestMatchers(HttpMethod.POST, "/login").permitAll() .anyRequest().authenticated()) .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)); return http.build(); } @Bean public AuthenticationProvider authenticationProvider(){ CustomAuthenticationProvider authenticationProvider = new CustomAuthenticationProvider(); return authenticationProvider; } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } }
JWTGeneratorFilter
@RequiredArgsConstructor @Component public class JWTGeneratorFilter extends OncePerRequestFilter { private final SecurityUtil securityUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (null == authentication) { ObjectMapper mapper = new ObjectMapper(); CredentialsDTO credentialsDTO = mapper.readValue(request.getInputStream(), CredentialsDTO.class); User user = securityUtil.findByCredentialsDTO(credentialsDTO); SecretKey key = Keys.hmacShaKeyFor(ConstantStore.JWT_SECRET.getBytes(StandardCharsets.UTF_8)); String jwt = Jwts.builder().setIssuer("FPE-Weblap").setSubject("JWT") .claim("username", user.getUsername()) .claim("authorities", populateAuthorities(user.getRoles())) .setIssuedAt(new Date()) .setExpiration(new Date((new Date()).getTime() + 30000000)) .signWith(key).compact(); response.setHeader(ConstantStore.TOKEN_HEADER, jwt); request.setAttribute(ConstantStore.TOKEN_HEADER, jwt); SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword(), user.getRoles())); } filterChain.doFilter(request, response); } private String populateAuthorities(Collection<? extends GrantedAuthority> collection) { Set<String> authoritiesSet = new HashSet<>(); for (GrantedAuthority authority : collection) { authoritiesSet.add(authority.getAuthority()); } return String.join(",", authoritiesSet); } }
JWTAuthenticationFilter
@Component public class JWTAuthenticationFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String jwt = response.getHeader(ConstantStore.TOKEN_HEADER); if (null != jwt) { try { SecretKey key = Keys.hmacShaKeyFor( ConstantStore.JWT_SECRET.getBytes(StandardCharsets.UTF_8)); Claims claims = Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(jwt) .getBody(); String username = String.valueOf(claims.get("username")); String authorities = (String) claims.get("authorities"); Authentication auth = new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authorities)); //Yes, my roles start with ROLE_ SecurityContextHolder.getContext().setAuthentication(auth); } catch (Exception e) { throw new BadCredentialsException("Invalid Token received!"); } } filterChain.doFilter(request, response); } }
UserDetailsImpl
public class UserDetailsImpl implements UserDetails { private User user; public UserDetailsImpl(User user) { this.user = user; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { Collection<GrantedAuthority> authorities = new HashSet<>(); List<Role> roles= user.getRoles(); for(Role role : roles) { authorities.add(new SimpleGrantedAuthority(role.getName())); } return authorities; } }
解决建议
修正过滤器顺序与实例化方式
当前手动new过滤器实例会跳过Spring依赖注入,且顺序配置错误。改为注入Spring管理的过滤器Bean,调整顺序:// 修改SecurityConfiguration构造器,注入两个过滤器 private final SecurityUtil securityUtil; private final JWTGeneratorFilter jwtGeneratorFilter; private final JWTAuthenticationFilter jwtAuthenticationFilter; public SecurityConfiguration(SecurityUtil securityUtil, JWTGeneratorFilter jwtGeneratorFilter, JWTAuthenticationFilter jwtAuthenticationFilter) { this.securityUtil = securityUtil; this.jwtGeneratorFilter = jwtGeneratorFilter; this.jwtAuthenticationFilter = jwtAuthenticationFilter; } // 配置过滤器链时调整顺序 http.addFilterBefore(jwtGeneratorFilter, BasicAuthenticationFilter.class) .addFilterAfter(jwtAuthenticationFilter, JWTGeneratorFilter.class);修复JWTAuthenticationFilter的token获取逻辑
从请求头而非响应头读取token:String jwt = request.getHeader(ConstantStore.TOKEN_HEADER);限制JWTGeneratorFilter的触发范围
该过滤器仅需处理POST /login请求,避免干扰其他请求(输入流仅能读取一次):@Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { return !"/login".equals(request.getRequestURI()) || !HttpMethod.POST.name().equals(request.getMethod()); }规范用户认证流程
不要直接通过securityUtil.findByCredentialsDTO验证用户,改用AuthenticationManager执行认证,适配Spring Security的认证机制:// 在JWTGeneratorFilter注入AuthenticationManager private final AuthenticationManager authenticationManager; // 认证逻辑替换为 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(credentialsDTO.getUsername(), credentialsDTO.getPassword()); Authentication authentication = authenticationManager.authenticate(authToken); SecurityContextHolder.getContext().setAuthentication(authentication); // 再基于authentication生成JWT修正Authentication对象的创建
使用UserDetailsImpl构建Authentication,确保权限信息正确:UserDetails userDetails = new UserDetailsImpl(user); Authentication auth = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(auth);移除重复的sessionManagement配置
将两次sessionManagement配置合并为一次,避免冗余。
内容的提问来源于stack exchange,提问作者Csicskusz
相关产品推荐
相关产品推荐

