Spring Boot集成SpringSecurity+JWT时@PreAuthorize的hasAuthority失效返回403
问题根因
根据你提供的代码和现象,问题出在JWT权限解析后 SecurityContext 被覆盖,或者权限解析逻辑异常两个方向,核心是权限集合在写入 SecurityContext 之前就丢失了 admin:read 等非角色类权限。
具体排查&解决方案
1. 优先验证JWT解析逻辑是否正确
你当前的权限解析代码存在强制类型转换的隐式风险,部分JSON解析库会把JWT中的authorities字段解析为List<LinkedHashMap>而非你预期的List<Map<String,String>>,导致权限提取不完整。
替换JwtTokenVerifier中的权限解析逻辑为更健壮的写法:
@Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 省略原有header校验逻辑 try { Jws<Claims> claimsJws = Jwts.parser() .setSigningKey(secretKey) .parseClaimsJws(token); Claims body = claimsJws.getBody(); String username = body.getSubject(); // 替换原有权限解析代码 Object authObj = body.get("authorities"); Set<SimpleGrantedAuthority> simpleGrantedAuthorities = new HashSet<>(); if (authObj instanceof List<?>) { simpleGrantedAuthorities = ((List<?>) authObj).stream() .filter(Map.class::isInstance) .map(Map.class::cast) .map(map -> map.get("authority")) .filter(Objects::nonNull) .map(String::valueOf) .map(SimpleGrantedAuthority::new) .collect(Collectors.toSet()); } // 打印日志验证解析结果,上线可删除 System.out.println("解析到的权限:" + simpleGrantedAuthorities); Authentication authentication = new UsernamePasswordAuthenticationToken( username, null, simpleGrantedAuthorities ); SecurityContextHolder.getContext().setAuthentication(authentication); filterChain.doFilter(request, response); } catch (JwtException e ){ throw new IllegalStateException(String.format("Token %s is not to be trusted", token)); } }
2. 调整过滤器执行顺序,避免SecurityContext被覆盖
你当前将JwtTokenVerifier放在用户名密码认证过滤器之后,存在被其他默认认证逻辑覆盖SecurityContext的风险,调整为放在用户名密码认证过滤器之前执行:
修改SecurityConfig的configure(HttpSecurity http)方法中的过滤器配置:
http .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .addFilter(new JwtUsernameAndPasswordAuthenticationFilter(authenticationManager(), jwtConfig, secretKey)) // 把addFilterAfter改成addFilterBefore .addFilterBefore(new JwtTokenVerifier(secretKey, jwtConfig), JwtUsernameAndPasswordAuthenticationFilter.class) .authorizeRequests() .antMatchers("/", "index","/css/*", "/js/*").permitAll() .anyRequest() .authenticated();
3. 辅助验证手段
如果修改后仍未生效,可在目标接口中打印当前SecurityContext中的权限集合,确认权限是否正确写入:
@RequestMapping(value = "/accounts/{uid}", method = RequestMethod.GET) @PreAuthorize("hasAuthority('admin:read')") ResponseEntity<?> findByUId(@PathVariable String uid) throws Exception { // 打印当前用户权限,上线可删除 Collection<? extends GrantedAuthority> authorities = SecurityContextHolder.getContext().getAuthentication().getAuthorities(); authorities.forEach(auth -> System.out.println("当前用户权限:" + auth.getAuthority())); AccountDTO dto = this.accountService.findByUid(uid); return new ResponseEntity<>(new ResponseWrapper(dto), HttpStatus.OK); }
内容的提问来源于stack exchange,提问作者nenad.arbutina
相关产品推荐
相关产品推荐

