0

0

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

DDD

DDD

发布时间:2025-12-04 17:33:07

|

961人浏览过

|

来源于php中文网

原创

spring boot jwt 角色权限控制:解决 401 未授权问题

本教程旨在解决 Spring Boot 中使用 JWT 进行角色权限控制时遇到的 401 未授权错误。文章将深入探讨 Spring Security、JWT 认证与授权的关键组件,包括安全配置、JWT 过滤器、用户详情服务以及认证流程。核心内容聚焦于排查并解决因用户权限数据缺失或配置不当导致的授权失败问题,并提供详细的代码示例和调试建议。

在 Spring Boot 应用中集成 JWT (JSON Web Token) 实现无状态的认证和基于角色的授权是常见的实践。然而,在配置 hasAuthority() 或 hasRole() 来保护 API 端点时,开发者常会遇到 401 未授权错误,即使 JWT token 能够正确生成。本文将详细解析这一问题,并提供一套完整的解决方案和最佳实践。

1. Spring Boot JWT 认证与授权核心组件

要实现基于 JWT 的角色权限控制,Spring Security 需要以下几个核心组件协同工作:

  • WebSecurityConfigurerAdapter (或 SecurityFilterChain): 配置 Spring Security 的整体行为,包括禁用 CSRF、CORS,设置会话管理策略为无状态,以及定义授权规则和添加自定义过滤器。
  • JWT 认证过滤器 (OncePerRequestFilter): 拦截所有受保护的请求,从请求头中提取 JWT token,验证其有效性,并根据 token 中的信息构建认证对象 (Authentication),将其设置到 Spring Security 上下文 (SecurityContextHolder) 中。
  • UserDetailsService: 负责根据用户名加载用户详情 (UserDetails),其中包含用户的密码和最重要的——用户的权限集合 (GrantedAuthority)。
  • AuthenticationManager: 处理用户登录请求,验证用户凭据。
  • JWT 工具: 负责 JWT token 的生成、解析和验证。

2. 安全配置 (WebSecurityConfigurerAdapter)

这是 Spring Security 的入口点,定义了哪些路径需要认证、哪些路径需要特定权限。

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final JwtAuthenticationEntryPoint unauthorizedHandler;
    private final JwtRequestFilter jwtRequestFilter;

    public SecurityConfig(JwtAuthenticationEntryPoint unauthorizedHandler, JwtRequestFilter jwtRequestFilter) {
        this.unauthorizedHandler = unauthorizedHandler;
        this.jwtRequestFilter = jwtRequestFilter;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable() // 禁用CSRF
            .cors().disable() // 禁用CORS (根据实际需求配置)
            .authorizeRequests()
                // 允许所有用户访问登录接口
                .antMatchers("/authenticate", "/register").permitAll()
                // 根据角色分配权限
                .antMatchers("/user/**", "/document/**", "/appointment/**", "/activity/**")
                    .hasAuthority(UserRole.ADMIN.name())
                .antMatchers("/user/**", "/activity/**", "/appointment/", "/document/")
                    .hasAnyAuthority(UserRole.SUPPORTEXECUTIVE.name(), UserRole.FIELDEXECUTIVE.name())
                // 其他所有请求都需要认证
                .anyRequest().authenticated()
            .and()
            .exceptionHandling().authenticationEntryPoint(unauthorizedHandler) // 处理未认证请求
            .and()
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置为无状态会话
            .and()
            // 在UsernamePasswordAuthenticationFilter之前添加JWT过滤器
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

关键点:

千问APP
千问APP

阿里最强大模型官方AI助手

下载
  • http.csrf().disable().cors().disable(): 对于无状态的 RESTful API,通常会禁用 CSRF。CORS 根据前端部署情况决定是否禁用或配置。
  • antMatchers().permitAll(): 允许未经认证的请求访问某些公共资源,如登录接口。
  • antMatchers().hasAuthority(UserRole.ADMIN.name()): 这是核心的授权规则。它要求访问匹配路径的用户必须拥有 ADMIN 权限。
  • sessionCreationPolicy(SessionCreationPolicy.STATELESS): 明确指示 Spring Security 不创建或使用 HTTP 会话,这对于 JWT 是必需的。
  • addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class): 将自定义的 JWT 过滤器添加到 Spring Security 过滤器链中,确保在标准的基于表单的认证过滤器之前执行。

3. JWT 认证过滤器 (JwtRequestFilter)

此过滤器负责解析和验证传入请求中的 JWT token,并将认证信息设置到 Spring Security 上下文。

import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
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;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    private final UserDetailsService userDetailsService;
    private final JwtUtil jwtUtil; // 假设有一个JWT工具类

    public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil jwtUtil) {
        this.userDetailsService = userDetailsService;
        this.jwtUtil = jwtUtil;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        final String authorizationHeader = request.getHeader("Authorization");

        String username = null;
        String jwt = null;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7); // 提取token
            try {
                username = jwtUtil.extractUsername(jwt); // 从token中提取用户名
            } catch (Exception e) {
                logger.error("Error extracting username from token: " + e.getMessage());
                // 可以添加更详细的异常处理,例如设置HTTP状态码
            }
        }

        // 如果用户名不为空且当前安全上下文中没有认证信息
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

            // 验证token是否有效
            if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { // 验证token和用户名
                // 构建认证对象
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                        userDetails, null, userDetails.getAuthorities()); // 注意这里传入userDetails,而不是仅仅username
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                // 将认证对象设置到安全上下文
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        filterChain.doFilter(request, response);
    }
}

关键点:

  • userDetails.getAuthorities(): 这是最重要的一步。UsernamePasswordAuthenticationToken 的第三个参数必须是用户的权限集合 (Collection extends GrantedAuthority>)。Spring Security 的 hasAuthority() 方法会检查这个集合中是否存在所需的权限。
  • SecurityContextHolder.getContext().setAuthentication(authenticationToken): 将认证信息放入安全上下文,后续的授权检查才能生效。

4. 用户详情服务 (UserDetailsService)

UserDetailsService 是 Spring Security 加载用户认证和授权信息的核心接口。它需要返回一个 UserDetails 对象,其中包含了用户的用户名、密码以及权限。

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Service
public class CustomUserDetailsService implements UserDetailsService {

    private final UserRepository userRepository; // 假设有一个用户仓库

    public CustomUserDetailsService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 从数据库或其他存储中加载用户信息
        com.example.demo.model.User user = userRepository.findByEmail(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + username));

        // 核心:构建用户的权限集合
        // 假设User实体中有一个getRoles()方法返回角色列表
        Collection authorities = new ArrayList<>();
        user.getRoles().forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName())));

        // 返回Spring Security的User对象,其中包含用户名、密码和权限
        return new User(user.getEmail(), user.getPassword(), authorities);
    }
}

关键点:

  • user.getRoles() 或 user.getAuthorities(): 这是解决 401 问题的关键所在。UserDetailsService 必须从数据库中正确地加载用户的角色或权限信息,并将其转换为 GrantedAuthority 对象的集合。
  • SimpleGrantedAuthority: 这是一个 GrantedAuthority 的简单实现,通常用于表示角色名或权限字符串。
  • return new User(...): 返回的 User 对象(Spring Security 提供的 UserDetails 实现)必须包含正确的权限列表。如果此列表为空或不包含所需的权限,那么 hasAuthority() 检查将失败。

5. 用户登录 (AuthController)

用户通过提供凭据进行登录,成功后生成 JWT token。

import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AuthController {

    private final AuthenticationManager authenticationManager;
    private final JwtUtil jwtUtil; // 假设有一个JWT工具类

    public AuthController(AuthenticationManager authenticationManager, JwtUtil jwtUtil) {
        this.authenticationManager = authenticationManager;
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/authenticate")
    public ResponseEntity loginUser(@RequestBody UserRequest request) throws Exception {
        try {
            // 使用AuthenticationManager验证用户凭据
            authenticationManager.authenticate(
                    new UsernamePasswordAuthenticationToken(request.getUserEmail(), request.getPassword()));

            // 凭据验证成功,生成JWT token
            String token = jwtUtil.generateToken(request.getUserEmail());
            System.out.println("Generated Token: " + token);
            return ResponseEntity.ok(new UserResponse(token));
        } catch (DisabledException e) {
            throw new Exception("USER_DISABLED", e);
        } catch (BadCredentialsException e) {
            throw new Exception("INVALID_CREDENTIALS", e);
        }
    }
}

关键点:

  • authenticationManager.authenticate(): 这是 Spring Security 进行认证的标准方式。它会调用 UserDetailsService 来加载用户,并使用配置的密码编码器来验证密码。
  • jwtUtil.generateToken(): 认证成功后,生成 JWT token 并返回给客户端。客户端在后续请求中携带此 token。

6. 解决 401 未授权问题的核心:权限数据

当您在 SecurityConfig 中使用 hasAuthority(Role) 保护端点时遇到 401 错误,而 permitAll() 却能正常工作,这几乎总是意味着以下问题:

最主要的原因:UserDetailsService 返回的 UserDetails 对象中,getAuthorities() 方法返回的权限集合不包含 hasAuthority() 所需的权限。

排查步骤:

  1. 检查数据库中的权限数据:

    • 确保您的用户表或关联的角色表中确实存储了用户的角色信息。例如,如果 UserRole.ADMIN.name() 是 "ADMIN",那么数据库中该用户的角色字段应该包含 "ADMIN"。
    • 检查大小写敏感性。Spring Security 默认是大小写敏感的,ADMIN 和 admin 是不同的权限。
  2. 调试 CustomUserDetailsService.loadUserByUsername() 方法:

    • 在 loadUserByUsername 方法中,打印或调试 user.getRoles() 以及最终构建的 authorities 集合。
    • 确认 authorities 集合中包含了您期望的 SimpleGrantedAuthority("ADMIN") 或其他角色。
  3. 调试 JwtRequestFilter.doFilterInternal() 方法:

    • 在 doFilterInternal 方法中,当 UserDetails usr = userDetailsService.loadUserByUsername(username); 执行后,打印 usr.getAuthorities()。
    • 确认这里返回的 UserDetails 对象的权限集合与 SecurityConfig 中 hasAuthority() 所需的权限完全匹配。
    • 如果 usr.getAuthorities() 为空或不包含正确权限,那么即使 token 有效,授权检查也会失败。

示例:假设用户表结构

CREATE TABLE users (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL
);

CREATE TABLE roles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);

CREATE TABLE user_roles (
    user_id BIGINT NOT NULL,
    role_id INT NOT NULL,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id),
    FOREIGN KEY (role_id) REFERENCES roles(id)
);

-- 示例数据
INSERT INTO users (email, password) VALUES ('admin@example.com', '$2a$10$YourEncodedAdminPassword');
INSERT INTO roles (name) VALUES ('ADMIN'), ('SUPPORTEXECUTIVE'), ('FIELDEXECUTIVE');
INSERT INTO user_roles (user_id, role_id) VALUES (1, 1); -- 给admin@example.com分配ADMIN角色

确保您的 UserRepository 和 User 实体能够正确地加载 User 及其关联的 Role。

7. 注意事项与最佳实践

  • 密码编码器: 始终使用 BCryptPasswordEncoder 或其他强大的密码编码器来存储用户密码。
  • 错误处理: 为 JwtAuthenticationEntryPoint 提供清晰的错误响应,以便客户端能够理解认证失败的原因。
  • JWT 密钥管理: 生产环境中,JWT 的密钥应该安全存储,并定期轮换。
  • 权限命名: 保持权限命名的一致性,推荐使用大写字符串,例如 ROLE_ADMIN 或 ADMIN。如果使用 hasRole(),Spring Security 会自动添加 ROLE_ 前缀,而 hasAuthority() 则直接匹配。
  • 日志记录: 在过滤器和 UserDetailsService 中添加详细的日志,有助于在开发和生产环境中快速定位问题。

总结

解决 Spring Boot JWT 角色权限控制中 401 未授权问题的关键在于确保 UserDetailsService 能够正确地从数据源加载用户的权限信息,并将其封装到 UserDetails 对象中。这些权限随后会被 JwtRequestFilter 用于构建 Authentication 对象,最终由 Spring Security 的授权管理器进行匹配。通过仔细检查数据库中的权限数据、调试 UserDetailsService 和 JwtRequestFilter 的权限加载过程,您将能够有效地诊断并解决此类授权问题。

相关专题

更多
spring框架介绍
spring框架介绍

本专题整合了spring框架相关内容,想了解更多详细内容,请阅读专题下面的文章。

103

2025.08.06

spring boot框架优点
spring boot框架优点

spring boot框架的优点有简化配置、快速开发、内嵌服务器、微服务支持、自动化测试和生态系统支持。本专题为大家提供spring boot相关的文章、下载、课程内容,供大家免费下载体验。

135

2023.09.05

spring框架有哪些
spring框架有哪些

spring框架有Spring Core、Spring MVC、Spring Data、Spring Security、Spring AOP和Spring Boot。详细介绍:1、Spring Core,通过将对象的创建和依赖关系的管理交给容器来实现,从而降低了组件之间的耦合度;2、Spring MVC,提供基于模型-视图-控制器的架构,用于开发灵活和可扩展的Web应用程序等。

389

2023.10.12

Java Spring Boot开发
Java Spring Boot开发

本专题围绕 Java 主流开发框架 Spring Boot 展开,系统讲解依赖注入、配置管理、数据访问、RESTful API、微服务架构与安全认证等核心知识,并通过电商平台、博客系统与企业管理系统等项目实战,帮助学员掌握使用 Spring Boot 快速开发高效、稳定的企业级应用。

68

2025.08.19

Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性
Java Spring Boot 4更新教程_Java Spring Boot 4有哪些新特性

Spring Boot 是一个基于 Spring 框架的 Java 开发框架,它通过 约定优于配置的原则,大幅简化了 Spring 应用的初始搭建、配置和开发过程,让开发者可以快速构建独立的、生产级别的 Spring 应用,无需繁琐的样板配置,通常集成嵌入式服务器(如 Tomcat),提供“开箱即用”的体验,是构建微服务和 Web 应用的流行工具。

33

2025.12.22

Java Spring Boot 微服务实战
Java Spring Boot 微服务实战

本专题深入讲解 Java Spring Boot 在微服务架构中的应用,内容涵盖服务注册与发现、REST API开发、配置中心、负载均衡、熔断与限流、日志与监控。通过实际项目案例(如电商订单系统),帮助开发者掌握 从单体应用迁移到高可用微服务系统的完整流程与实战能力。

114

2025.12.24

PHP API接口开发与RESTful实践
PHP API接口开发与RESTful实践

本专题聚焦 PHP在API接口开发中的应用,系统讲解 RESTful 架构设计原则、路由处理、请求参数解析、JSON数据返回、身份验证(Token/JWT)、跨域处理以及接口调试与异常处理。通过实战案例(如用户管理系统、商品信息接口服务),帮助开发者掌握 PHP构建高效、可维护的RESTful API服务能力。

146

2025.11.26

json数据格式
json数据格式

JSON是一种轻量级的数据交换格式。本专题为大家带来json数据格式相关文章,帮助大家解决问题。

412

2023.08.07

PHP WebSocket 实时通信开发
PHP WebSocket 实时通信开发

本专题系统讲解 PHP 在实时通信与长连接场景中的应用实践,涵盖 WebSocket 协议原理、服务端连接管理、消息推送机制、心跳检测、断线重连以及与前端的实时交互实现。通过聊天系统、实时通知等案例,帮助开发者掌握 使用 PHP 构建实时通信与推送服务的完整开发流程,适用于即时消息与高互动性应用场景。

11

2026.01.19

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 2.7万人学习

C# 教程
C# 教程

共94课时 | 7万人学习

Java 教程
Java 教程

共578课时 | 47.8万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号