
本教程旨在解决 Spring Boot 中使用 JWT 进行角色权限控制时遇到的 401 未授权错误。文章将深入探讨 Spring Security、JWT 认证与授权的关键组件,包括安全配置、JWT 过滤器、用户详情服务以及认证流程。核心内容聚焦于排查并解决因用户权限数据缺失或配置不当导致的授权失败问题,并提供详细的代码示例和调试建议。
在 Spring Boot 应用中集成 JWT (JSON Web Token) 实现无状态的认证和基于角色的授权是常见的实践。然而,在配置 hasAuthority() 或 hasRole() 来保护 API 端点时,开发者常会遇到 401 未授权错误,即使 JWT token 能够正确生成。本文将详细解析这一问题,并提供一套完整的解决方案和最佳实践。
要实现基于 JWT 的角色权限控制,Spring Security 需要以下几个核心组件协同工作:
这是 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);
}
}关键点:
此过滤器负责解析和验证传入请求中的 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);
}
}关键点:
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<GrantedAuthority> authorities = new ArrayList<>();
user.getRoles().forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName())));
// 返回Spring Security的User对象,其中包含用户名、密码和权限
return new User(user.getEmail(), user.getPassword(), authorities);
}
}关键点:
用户通过提供凭据进行登录,成功后生成 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<UserResponse> 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);
}
}
}关键点:
当您在 SecurityConfig 中使用 hasAuthority(Role) 保护端点时遇到 401 错误,而 permitAll() 却能正常工作,这几乎总是意味着以下问题:
最主要的原因:UserDetailsService 返回的 UserDetails 对象中,getAuthorities() 方法返回的权限集合不包含 hasAuthority() 所需的权限。
排查步骤:
检查数据库中的权限数据:
调试 CustomUserDetailsService.loadUserByUsername() 方法:
调试 JwtRequestFilter.doFilterInternal() 方法:
示例:假设用户表结构
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。
解决 Spring Boot JWT 角色权限控制中 401 未授权问题的关键在于确保 UserDetailsService 能够正确地从数据源加载用户的权限信息,并将其封装到 UserDetails 对象中。这些权限随后会被 JwtRequestFilter 用于构建 Authentication 对象,最终由 Spring Security 的授权管理器进行匹配。通过仔细检查数据库中的权限数据、调试 UserDetailsService 和 JwtRequestFilter 的权限加载过程,您将能够有效地诊断并解决此类授权问题。
以上就是Spring Boot JWT 角色权限控制:解决 401 未授权问题的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号