Spring Boot JWT 角色权限控制如何解决401未授权问题

来源:站长平台作者:灯下变量头衔:程序员
导读:本期聚焦于小伙伴创作的《Spring Boot JWT 角色权限控制如何解决401未授权问题》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《Spring Boot JWT 角色权限控制如何解决401未授权问题》有用,将其分享出去将是对创作者最好的鼓励。

在Spring Boot项目中集成JWT实现角色权限控制时,401未授权是高频出现的问题,通常和令牌处理、权限配置、请求传递等环节的疏漏有关。要解决这个问题,需要先理清整个权限控制的流程链路,再针对性排查问题点。

Spring Boot JWT 角色权限控制如何解决401未授权问题

401未授权的常见原因

出现401未授权的问题,大多可以归为以下几类情况:

  • JWT令牌生成时未正确写入角色信息,或者令牌过期、签名校验失败
  • 请求头中携带令牌的格式不符合要求,比如没有加Bearer前缀,或者令牌存在空格、截断问题
  • Spring Security的权限规则配置错误,没有正确关联JWT解析出的角色和接口权限要求
  • 接口权限注解使用不当,比如角色前缀没有和JWT中存储的角色匹配

JWT工具类正确实现

首先要保证JWT的生成和解析逻辑正确,尤其是角色信息的存储和提取。以下是完整的JWT工具类示例:

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;

@Component
public class JwtTokenUtil {
    // 密钥,实际项目可以放到配置文件中
    @Value("${jwt.secret:mySecretKey}")
    private String secret;
    // 令牌过期时间,单位毫秒,这里设置1小时
    @Value("${jwt.expiration:3600000}")
    private Long expiration;

    /**
     * 生成JWT令牌,携带用户角色信息
     * @param username 用户名
     * @param roles 用户角色集合
     * @return 生成的令牌字符串
     */
    public String generateToken(String username, Set<String> roles) {
        Map<String, Object> claims = new HashMap<>();
        // 把角色信息放到claims中,key为roles
        claims.put("roles", roles);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + expiration))
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    /**
     * 从令牌中解析用户名
     * @param token JWT令牌
     * @return 用户名
     */
    public String getUsernameFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        return claims.getSubject();
    }

    /**
     * 从令牌中解析角色集合
     * @param token JWT令牌
     * @return 角色集合
     */
    public Set<String> getRolesFromToken(String token) {
        Claims claims = getClaimsFromToken(token);
        // 解析存储的角色信息,转换为Set<String>
        List<String> roleList = claims.get("roles", List.class);
        if (roleList == null) {
            return new HashSet<>();
        }
        return new HashSet<>(roleList);
    }

    /**
     * 校验令牌是否有效
     * @param token JWT令牌
     * @return 是否有效
     */
    public boolean validateToken(String token) {
        try {
            Claims claims = getClaimsFromToken(token);
            return !claims.getExpiration().before(new Date());
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 解析令牌获取Claims对象
     * @param token JWT令牌
     * @return Claims对象
     */
    private Claims getClaimsFromToken(String token) {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }
}

JWT请求过滤器实现

需要自定义过滤器,从请求头中提取令牌,校验通过后把用户信息放到Spring Security的上下文中,否则就会直接返回401。以下是过滤器实现:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 从请求头中获取Authorization字段
        String authHeader = request.getHeader("Authorization");
        if (authHeader != null && authHeader.startsWith("Bearer ")) {
            // 提取令牌,去掉Bearer前缀和后面的空格
            String token = authHeader.substring(7);
            // 校验令牌是否有效
            if (jwtTokenUtil.validateToken(token)) {
                // 从令牌中获取用户名和角色
                String username = jwtTokenUtil.getUsernameFromToken(token);
                Set<String> roles = jwtTokenUtil.getRolesFromToken(token);
                // 把角色转换为Spring Security需要的权限格式,注意角色默认需要加ROLE_前缀
                Set<SimpleGrantedAuthority> authorities = roles.stream()
                        .map(role -> new SimpleGrantedAuthority("ROLE_" + role))
                        .collect(Collectors.toSet());
                // 构建UserDetails对象
                UserDetails userDetails = new User(username, "", authorities);
                // 构建认证对象
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities());
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // 把认证信息放到Security上下文中
                SecurityContextHolder.getContext().setAuthentication(authentication);
            } else {
                // 令牌无效,返回401
                response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
                return;
            }
        }
        filterChain.doFilter(request, response);
    }
}

Spring Security权限配置

配置Spring Security时,需要把自定义过滤器加到合适的位置,同时正确配置接口的权限规则,避免权限匹配错误导致401。以下是配置类示例:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Autowired
    private JwtAuthenticationFilter jwtAuthenticationFilter;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            // 关闭csrf,因为JWT是无状态的
            .csrf().disable()
            // 设置会话管理为无状态,不创建session
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
            .and()
            .authorizeRequests()
            // 登录接口允许匿名访问
            .antMatchers("/api/login").anonymous()
            // 管理员角色才能访问的接口
            .antMatchers("/api/admin/**").hasRole("ADMIN")
            // 普通用户角色可以访问的接口
            .antMatchers("/api/user/**").hasRole("USER")
            // 其他接口需要认证
            .anyRequest().authenticated()
            .and()
            // 把JWT过滤器加到用户名密码认证过滤器之前
            .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
}

接口权限注解使用

除了在配置类中统一配置权限规则,也可以在接口方法上使用@PreAuthorize注解单独控制权限,需要注意角色前缀和JWT中存储的角色一致:

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {
    // 只有ADMIN角色可以访问
    @GetMapping("/api/admin/test")
    @PreAuthorize("hasRole('ADMIN')")
    public String adminTest() {
        return "管理员接口访问成功";
    }

    // USER或者ADMIN角色都可以访问
    @GetMapping("/api/user/test")
    @PreAuthorize("hasAnyRole('USER','ADMIN')")
    public String userTest() {
        return "用户接口访问成功";
    }
}

排查401问题的步骤

如果还是出现401未授权问题,可以按照以下步骤排查:

  • 先检查请求头中的Authorization字段是否正确,格式是否为Bearer 令牌字符串,令牌是否完整没有截断
  • 把令牌拿到JWT解析工具中解析,看是否过期,角色信息是否正确存储
  • 检查JWT工具类中角色解析逻辑和过滤器中权限转换逻辑是否匹配,比如是否都加了ROLE_前缀
  • 检查Spring Security的权限规则是否和接口要求的角色一致,有没有把接口路径配置错误
  • 开启Spring Security的调试日志,查看权限校验的具体失败原因

只要按照上述流程实现JWT角色权限控制,并且排查清楚每个环节的配置,就可以有效解决401未授权的问题,保证权限控制逻辑正常运行。

Spring_BootJWT角色权限控制401未授权修改时间:2026-07-14 22:18:41

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。