
本文旨在提供一份关于在spring boot应用中实现基于jwt(json web token)的角色授权的教程。我们将详细探讨核心安全配置、jwt请求过滤器的工作原理以及用户认证与令牌生成过程。此外,文章还将深入分析导致“401 unauthorized”错误(特别是在应用`hasauthority()`进行权限控制时)的常见原因,并提供相应的排查策略,重点关注权限数据模型与加载机制。
在Spring Boot中实现基于JWT的权限控制,主要涉及以下几个核心组件:安全配置 (WebSecurityConfigurerAdapter)、JWT请求过滤器 (OncePerRequestFilter) 以及用户认证与令牌生成逻辑。这些组件协同工作,确保请求的认证和授权过程顺畅且安全。
安全配置是定义应用安全策略的关键。在这里,我们配置了Spring Security如何处理HTTP请求,包括禁用CSRF、CORS,设置会话管理策略为无状态,并定义URL路径的访问权限。
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.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
private final JwtRequestFilter jwtRequestFilter;
private final InvalidUserAuthEntryPoint invaildUserAuthEntryPoint; // 自定义认证入口点
public WebSecurityConfig(JwtRequestFilter jwtRequestFilter, InvalidUserAuthEntryPoint invaildUserAuthEntryPoint) {
this.jwtRequestFilter = jwtRequestFilter;
this.invaildUserAuthEntryPoint = invaildUserAuthEntryPoint;
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() // 禁用CSRF
.cors().disable() // 禁用CORS(根据实际需求配置)
.authorizeRequests()
// 针对不同角色定义访问权限
.antMatchers("/**", "/user/**", "/document/**", "/appointment/**", "/activity/**").hasAuthority(UserRole.ADMIN.name())
.antMatchers("/user/**", "/activity/**", "/appointment/", "/document/", "/appointment/**", "/document/**").hasAuthority(UserRole.SUPPORTEXECUTIVE.name())
.antMatchers("/user/**", "/activity/**", "/appointment/", "/document/", "/appointment/**").hasAuthority(UserRole.FIELDEXECUTIVE.name())
// 其他路径可以根据需要添加 permitAll() 或 authenticated()
.anyRequest().authenticated() // 任何其他未匹配的请求都需要认证
.and()
.exceptionHandling().authenticationEntryPoint(invaildUserAuthEntryPoint) // 配置未认证入口点
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 设置会话管理为无状态
.and()
// 在UsernamePasswordAuthenticationFilter之前添加自定义的JWT过滤器
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
}配置说明:
JwtRequestFilter 负责拦截所有受保护的HTTP请求,从请求头中提取JWT,验证其有效性,并根据令牌中的信息设置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 j*ax.servlet.FilterChain;
import j*ax.servlet.ServletException;
import j*ax.servlet.http.HttpServletRequest;
import j*ax.servlet.http.HttpServletResponse;
import j*a.io.IOException;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final UserDetailsService userDetailsService;
private final JwtUtil util; // JWT工具类,用于生成、解析和验证JWT
public JwtRequestFilter(UserDetailsService userDetailsService, JwtUtil util) {
this.userDetailsService = userDetailsService;
this.util = util;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwtToken = null;
// 检查Authorization头是否包含Bearer令牌
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwtToken = authorizationHeader.substring(7); // 提取JWT
try {
username = util.extractUsername(jwtToken); // 从JWT中提取用户名
} catch (Exception e) {
// 处理JWT解析异常,例如令牌过期、无效签名等
logger.error("Error extracting username from JWT: " + e.getMessage());
}
}
// 如果成功提取到用户名且当前SecurityContext中没有认证信息
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); // 加载用户详情
// 验证令牌是否有效
if (util.validateToken(jwtToken, userDetails.getUsername())) {
// 构建认证对象
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
// 将认证信息设置到SecurityContext中
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
filterChain.doFilter(request, response); // 继续过滤器链
}
}过滤器说明:
用户通过提供用户名和密码进行登录时,控制器会负责认证这些凭据,并在认证成功后生成一个JWT返回给客户端。
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") // 假设登录接口为 /authenticate
public ResponseEntity<UserResponse> loginUser(@RequestBody UserRequest request) throws Exception {
try {
// 使用AuthenticationManager进行用户认证
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getUserEmail(), request.getPassword()));
// 认证成功后,生成JWT
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);
}
}
}
// 假设的请求和响应类
class UserRequest {
private String userEmail;
private String password;
// Getters and Setters
public String getUserEmail() { return userEmail; }
public void setUserEmail(String userEmail) { this.userEmail = userEmail; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
}
class UserResponse {
private String jwtToken;
public UserResponse(String jwtToken) { this.jwtToken = jwtToken; }
public String getJwtToken() { return jwtToken; }
public void setJwtToken(String jwtToken) { this.jwtToken = jwtToken; }
}认证说明:
当hasAuthority()检查失败并返回401 Unauthorized时,一个常见但容易被忽视的原因是权限数据本身的问题。即使JWT令牌有效,如果Spring Security无法从UserDetails中获取到正确的权限信息,授权也会失败。
在数据库中,你需要为用户存储其对应的角色或权限。这通常通过以下方式实现:
无论哪种方式,关键是当UserDetailsService加载用户时,能够获取到这些权限信息。
Magician
Figma插件,AI生成图标、图片和UX文案
412
查看详情
UserDetailsService 的 loadUserByUsername 方法是加载用户详情的核心。它不仅要加载用户名和密码,更重要的是要加载用户所拥有的权限,并将其封装到 UserDetails 对象的 getAuthorities() 方法中返回。
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 j*a.util.Collections;
import j*a.util.List;
import j*a.util.stream.Collectors;
@Service
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository; // 假设有一个UserRepository来访问数据库
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 从数据库加载用户信息
com.example.demo.model.User appUser = userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found with email: " + username));
// 关键:将从数据库获取的角色/权限转换为GrantedAuthority对象
// 假设User实体中有一个getRole()方法返回角色字符串
List<GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority(appUser.getRole()));
// 如果用户有多个角色,可能需要从关联表中获取并转换为List<GrantedAuthority>
/*
List<GrantedAuthority> authorities = appUser.getRoles().stream()
.map(role -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toList());
*/
return new User(appUser.getEmail(), appUser.getPassword(), authorities);
}
}注意事项:
当您遇到 401 Unauthorized 错误,特别是当 permitAll() 工作正常但 hasAuthority() 失败时,请按照以下步骤进行排查:
这是最基本也是最常见的错误原因。
这是 hasAuthority() 失败的核心原因。
虽然通常 permitAll() 成功意味着令牌生成和基本解析没问题,但仍需考虑:
实现Spring Boot JWT权限控制需要对Spring Security的工作原理有清晰的理解。当遇到 401 Unauthorized 错误时,尤其是涉及 hasAuthority() 的情况,问题的根源往往在于:
最佳实践:
通过遵循上述指南和排查步骤,您将能够有效地在Spring Boot应用中实现健壮的JWT角色授权,并快速解决常见的权限相关问题。
以上就是Spring Boot JWT 角色授权实现与401错误排查指南的详细内容,更多请关注其它相关文章!
# 美国站外推广网站排名
# 文档
# 自定义
# 您的
# 是否正确
# 客户端
# 它会
# 网站优化日报模板怎么做
# 提供网站建设费用
# 这是
# 新疆网络推广网站建设
# 临沂抖音seo厂家排名
# 静安网站建设工程
# 保定网站优化推荐高中
# 餐饮店营销推广策略研究
# 黑马seo软件网站
# seo计费查询
# ai
# java
# js
# 前端
# json
# 编码
# app
# 工具
# session
# word
# stream
# 会话管理
# 常见问题
# s
# 令牌
# 加载
# 转换为
相关栏目:
【
Google疑问12 】
【
Facebook疑问10 】
【
优化推广96088 】
【
技术知识133117 】
【
IDC资讯59369 】
【
网络运营7196 】
【
IT资讯61894 】
相关推荐:
视频号视频怎么免费保存到相册?保存到相册需要注意什么?
《真我》申请退款方法
如何在Golang中处理表单文件上传_Golang 表单文件上传示例
漫蛙manwa漫画官网链接_漫蛙manwa最新可用网址推荐
追剧达人如何发弹幕
《花瓣》创建专辑方法
b站怎么设置动态仅粉丝可见_b站动态粉丝可见设置方法
PHP魔术方法__set与__isset:设计考量、性能权衡与静态分析的视角
win11如何运行chkdsk命令 Win11检查和修复磁盘逻辑错误教程【修复】
《kimi智能助手》制作ppt教程
顺丰官方查单号入口 顺丰快递单号查询官网入口
AO3中文版手机快速通道_AO3最新稳定链接更新
Python csv 模块处理非字符串数据:列表写入 CSV 文件的机制解析
Excel如何快速找到并断开外部数据源链接_Excel外部数据源断开方法
德邦快递会员怎么开通
2025SNH48年度青春盛典门票价格及购买方式
谷歌邮箱怎么换绑定邮箱Gmail安全备份邮箱修改方法
多闪APP官方下载安装入口_多闪最新版本获取入口
PHP使用DOMDocument与XPath精准追加XML元素教程
京东快递物流信息不更新怎么办_物流停滞原因与处理方法
支付宝网页版在线入口 支付宝官网电脑登录入口
Flexbox布局实践:实现底部页脚与顶部粘性导航条的完美结合
Python定时发送QQ消息
C++ cast类型转换总结_C++ reinterpret_cast与const_cast的使用
谷歌学术论文搜索引擎 谷歌学术官网入口论坛永久链接
VS Code中的Tailwind CSS IntelliSense插件使用技巧
如何取消数字签名
照片整理的黄金法则是怎样的? 理解“收集-筛选-归档-备份”四步流程
《猎聘》筛选猎头岗位方法
圆通快递官方入口不需要登录 在线查询入口快速查询
美发店速赢秘籍
《广发易淘金》国债逆回购操作教程
AO3永久镜像入口开放_AO3最新网址兼容所有浏览器
优化 WooCommerce 产品价格显示与自定义短代码集成
《360浏览器》自动保存账号密码设置方法
windows10怎么更改下载路径_windows10默认存储位置修改教程
LocoySpider如何批量采集电商商品_LocoySpider电商采集的模板应用
《下一站江湖2》心法融合技巧
创建快捷方式启动系统保护
Composer如何使用composer-plugin-api开发自定义插件
金牛福袋获取攻略
在React中正确处理HTML input type="number"的数值类型
c++如何使用std::thread::join和detach_c++线程生命周期管理
Python中安全地将环境变量转换为整数的类型注解指南
b站如何管理订阅_b站订阅标签分类管理
Yandex无需登录畅游 俄罗斯搜索引擎最新官网指南
电脑视频号|直播|如何分享屏幕
汽水音乐在线入口 汽水音乐网页端官方页面快速打开
使用TinyButStrong生成HTML并结合Dompdf创建PDF教程
PHP odbc_fetch_array 返回值处理:如何正确访问嵌套数组元素
2025-12-04
运城市盐湖区信雨科技有限公司是一家深耕海外推广领域十年的专业服务商,作为谷歌推广与Facebook广告全球合作伙伴,聚焦外贸企业出海痛点,以数字化营销为核心,提供一站式海外营销解决方案。公司凭借十年行业沉淀与平台官方资源加持,打破传统外贸获客壁垒,助力企业高效开拓全球市场,成为中小企业出海的可靠合作伙伴。