You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 00:18:00