在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