Spring Boot集成JWT认证时服务器环境出现"Full authentication is required to access this resource"错误求助
Spring Boot集成JWT认证时服务器环境出现"Full authentication is required to access this resource"错误求助
嘿,我仔细梳理了你的代码和日志,找到了几个核心问题,咱们一步步来解决:
先从日志里抓关键线索
- JWT验证是通过的(
JWT token is valid.),说明签名和密钥匹配没问题 - 但从JWT解析出的用户名居然是
nullpointer(str nullpointer),这明显是异常值 - 认证后的用户角色是
ROLE_SUBSCRIBED,但你的/daily控制器要求的是ROLE_BASIC权限(@PreAuthorize("hasRole('BASIC')")) - 最后报错显示请求的是
/error,这是因为Spring Security拒绝访问后默认跳转到错误端点,而这个端点也被你的认证逻辑拦截了,才抛出了看似"未认证"的错误——实际是权限不足导致的访问被拒
问题根源拆解
- JWT解析出异常用户名:用jwt.io解码日志里的JWT会发现,payload的
sub字段就是nullpointer,说明生成JWT时userPrincipal.getUsername()返回了这个值,大概率是服务器环境下用户信息存储/获取逻辑出了问题,或者用户表中存在异常用户名的用户 - 权限不匹配:控制器要求
BASIC角色,但当前认证用户只有SUBSCRIBED角色,直接触发访问拒绝 - 错误端点被拦截:Spring Security拒绝访问后转发到
/error,这个路径没被排除在认证外,所以触发了AuthEntryPointJwt的错误日志
解决步骤
1. 修复JWT用户名异常问题
- 先确认服务器上的
bezkoder.app.jwtSecret配置和本地完全一致,避免密钥不一致导致解析异常 - 排查JWT生成逻辑:检查
JwtUtils.generateJwtToken方法中,userPrincipal.getUsername()为什么会返回nullpointer?是不是服务器环境下用户登录时的信息存储有问题? - 给
getUserNameFromJwtToken添加异常捕获,避免解析失败导致后续逻辑混乱:
public String getUserNameFromJwtToken(String token) { log.info("getUserNameFromJwtToken {} ", token); try { String str = Jwts.parserBuilder().setSigningKey(key()).build() .parseClaimsJws(token).getBody().getSubject(); log.info("str {} ", str); return str; } catch (Exception e) { log.error("Failed to parse username from JWT: {}", e.getMessage()); return null; } }
2. 解决权限不匹配问题
你有两个选择:
- 调整控制器的权限要求,匹配当前用户的角色:
@PreAuthorize("hasRole('SUBSCRIBED')")
- 或者给目标用户添加
ROLE_BASIC权限,确保角色和控制器要求一致
3. 排除错误端点的认证拦截
在你的Spring Security配置类里添加对/error的忽略,避免拒绝访问后触发二次错误:
@Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/error"); }
4. 优化AuthTokenFilter的逻辑
在过滤器里添加用户名非空判断,避免加载不存在的用户:
@Override protected void doFilterInternal(@NotNull HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException { try { log.info(String.valueOf(request)); String jwt = parseJwt(request); log.info("jwt: " + jwt); if (jwt != null && jwtUtils.validateJwtToken(jwt)) { String username = jwtUtils.getUserNameFromJwtToken(jwt); log.info("username: " + username); // 添加用户名非空判断 if (username != null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); log.info("userDetails: " + userDetails); UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); log.info("authentication: " + authentication); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); log.info("authentication: " + authentication); SecurityContextHolder.getContext().setAuthentication(authentication); log.info("authentication: " + authentication); } } } catch (Exception e) { log.error("Cannot set user authentication: {}", e); } filterChain.doFilter(request, response); }
另外,你的控制器里的@RequestHeader(value = "Authorization") String authHeader参数可以去掉,因为AuthTokenFilter已经处理了这个请求头,不需要再手动接收。
备注:内容来源于stack exchange,提问作者Nunyet Calçada




