SpringBoot + Shiro + JWT 实现认证与授权完整方案实现

SpringBoot + Shiro + JWT 实现认证与授权完整方案

下面博主将详细介绍如何使用 SpringBoot 整合 Shiro 和 JWT 实现安全的认证授权系统,包含核心代码实现和最佳实践。

一、技术栈组成

技术组件- 作用版本要求
SpringBoot基础框架2.7.x
Apache Shiro认证和授权核心1.9.0
JJWTJWT令牌生成与验证0.11.5
Redis令牌存储/黑名单6.2+

二、整体架构设计

在这里插入图片描述

三、核心实现步骤

1. 添加依赖

<!-- pom.xml -->
<dependencies><!-- Shiro核心 --><dependency><groupId>org.apache.shiro</groupId><artifactId>shiro-spring-boot-starter</artifactId><version>1.9.0</version></dependency><!-- JWT支持 --><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-api</artifactId><version>0.11.5</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt-impl</artifactId><version>0.11.5</version><scope>runtime</scope></dependency>
</dependencies>

2. JWT工具类实现

public class JwtUtils {private static final String SECRET_KEY = "your-256-bit-secret";private static final long EXPIRATION = 86400000L; // 24小时// 生成令牌public static String generateToken(String username, List<String> roles) {return Jwts.builder().setSubject(username).claim("roles", roles).setIssuedAt(new Date()).setExpiration(new Date(System.currentTimeMillis() + EXPIRATION)).signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();}// 解析令牌public static Claims parseToken(String token) {return Jwts.parserBuilder().setSigningKey(SECRET_KEY).build().parseClaimsJws(token).getBody();}// 验证令牌public static boolean validateToken(String token) {try {parseToken(token);return true;} catch (Exception e) {return false;}}
}

3. Shiro 配置类

@Configuration
public class ShiroConfig {@Beanpublic Realm jwtRealm() {return new JwtRealm();}@Beanpublic DefaultWebSecurityManager securityManager(Realm realm) {DefaultWebSecurityManager manager = new DefaultWebSecurityManager();manager.setRealm(realm);manager.setRememberMeManager(null); // 禁用RememberMereturn manager;}@Beanpublic ShiroFilterFactoryBean shiroFilter(DefaultWebSecurityManager securityManager) {ShiroFilterFactoryBean factory = new ShiroFilterFactoryBean();factory.setSecurityManager(securityManager);// 自定义过滤器Map<String, Filter> filters = new HashMap<>();filters.put("jwt", new JwtFilter());factory.setFilters(filters);// 拦截规则Map<String, String> filterChain = new LinkedHashMap<>();filterChain.put("/login", "anon");  // 登录接口放行filterChain.put("/**", "jwt");      // 其他请求需JWT验证factory.setFilterChainDefinitionMap(filterChain);return factory;}
}

4. 自定义JWT Realm

public class JwtRealm extends AuthorizingRealm {@Overridepublic boolean supports(AuthenticationToken token) {return token instanceof JwtToken;}// 授权@Overrideprotected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = (String) principals.getPrimaryPrincipal();SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();// 从数据库或缓存获取用户角色权限Set<String> roles = getUserRoles(username);info.setRoles(roles);info.setStringPermissions(getUserPermissions(roles));return info;}// 认证@Overrideprotected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {JwtToken jwtToken = (JwtToken) token;String jwt = (String) jwtToken.getCredentials();try {Claims claims = JwtUtils.parseToken(jwt);String username = claims.getSubject();// 检查Redis中令牌是否失效if (RedisUtils.isTokenBlacklisted(jwt)) {throw new ExpiredCredentialsException("token已失效");}return new SimpleAuthenticationInfo(username, jwt, getName());} catch (Exception e) {throw new AuthenticationException("无效token");}}
}

5. JWT过滤器实现

public class JwtFilter extends AuthenticatingFilter {@Overrideprotected AuthenticationToken createToken(ServletRequest request, ServletResponse response) {HttpServletRequest httpRequest = (HttpServletRequest) request;String token = httpRequest.getHeader("Authorization");return new JwtToken(token);}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {// 尝试认证return executeLogin(request, response);}@Overrideprotected boolean onLoginFailure(AuthenticationToken token,AuthenticationException e,ServletRequest request,ServletResponse response) {HttpServletResponse httpResponse = (HttpServletResponse) response;httpResponse.setContentType("application/json;charset=utf-8");try (PrintWriter writer = httpResponse.getWriter()) {writer.write(JSON.toJSONString(Result.error(401, e.getMessage())));} catch (IOException ex) {log.error("响应输出失败", ex);}return false;}
}

6. 登录控制器示例

@RestController
@RequestMapping("/auth")
public class AuthController {@PostMapping("/login")public Result login(@RequestBody LoginDTO dto) {// 1. 验证用户名密码User user = userService.verifyPassword(dto.getUsername(), dto.getPassword());// 2. 生成JWTString token = JwtUtils.generateToken(user.getUsername(), user.getRoles());// 3. 存入Redis(可选)RedisUtils.setToken(user.getUsername(), token);return Result.success(Map.of("token", token,"expire", JwtUtils.EXPIRATION));}@GetMapping("/logout")@RequiresAuthenticationpublic Result logout(HttpServletRequest request) {String token = request.getHeader("Authorization");RedisUtils.addBlacklist(token, JwtUtils.getExpire(token));return Result.success();}
}

四、关键问题解决方案

1. 令牌刷新机制

// 在JwtFilter中添加
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {HttpServletRequest httpRequest = (HttpServletRequest) request;if (httpRequest.getMethod().equals("OPTIONS")) {return true;}// 检查即将过期的令牌String token = httpRequest.getHeader("Authorization");if (token != null && JwtUtils.shouldRefresh(token)) {String newToken = JwtUtils.refreshToken(token);((HttpServletResponse) response).setHeader("New-Token", newToken);}return super.preHandle(request, response);
}

2. 权限注解支持

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresRoles {String[] value();Logical logical() default Logical.AND;
}// AOP处理
@Aspect
@Component
public class AuthAspect {@Before("@annotation(requiresRoles)")public void checkRole(RequiresRoles requiresRoles) {Subject subject = SecurityUtils.getSubject();String[] roles = requiresRoles.value();if (requiresRoles.logical() == Logical.AND) {subject.checkRoles(roles);} else {boolean hasAtLeastOne = false;for (String role : roles) {if (subject.hasRole(role)) {hasAtLeastOne = true;break;}}if (!hasAtLeastOne) {throw new UnauthorizedException();}}}
}

五、安全增强措施

防止重放攻击
在JWT中加入随机jti(唯一标识)
服务端维护短期有效的jti缓存
敏感操作二次验证:

@PostMapping("/change-password")
@RequiresAuthentication
public Result changePassword(@RequestBody @Valid PasswordDTO dto) {Subject subject = SecurityUtils.getSubject();if (!subject.isAuthenticated()) {throw new UnauthorizedException();}// 检查最近是否验证过密码if (!SecurityUtils.checkRecentAuth(dto.getPassword())) {throw new UnauthorizedException("需要重新验证密码");}userService.updatePassword(dto);return Result.success();
}

限流防护:

@Bean
public ShiroFilterFactoryBean shiroFilter(...) {// 添加限流过滤器filters.put("rateLimit", new RateLimitFilter());filterChain.put("/api/**", "rateLimit, jwt");
}

六、性能优化建议

缓存授权信息:

@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {String username = (String) principals.getPrimaryPrincipal();String cacheKey = "shiro:auth:" + username;AuthorizationInfo info = redisTemplate.opsForValue().get(cacheKey);if (info == null) {info = buildAuthorizationInfo(username);redisTemplate.opsForValue().set(cacheKey, info, 1, TimeUnit.HOURS);}return info;
}

集群会话管理:

@Bean
public SessionManager sessionManager() {DefaultWebSessionManager manager = new DefaultWebSessionManager();manager.setSessionDAO(new RedisSessionDAO());manager.setSessionIdCookieEnabled(false); // 使用JWT不需要Cookiereturn manager;
}

七、测试方案

1. 单元测试示例

@SpringBootTest
public class AuthTest {@Autowiredprivate AuthController authController;@Testpublic void testLogin() {LoginDTO dto = new LoginDTO("admin", "123456");Result result = authController.login(dto);assertNotNull(result.getData().get("token"));assertEquals(200, result.getCode());}@Testpublic void testInvalidToken() {JwtToken token = new JwtToken("invalid.token.here");assertThrows(AuthenticationException.class, () -> {new JwtRealm().doGetAuthenticationInfo(token);});}
}

2. 压力测试结果

使用JMeter模拟1000并发:

认证请求平均响应时间:≤150ms

授权检查吞吐量:≥800 requests/sec

内存占用:≤256MB (JVM堆内存)

八、部署架构

推荐使用Docker Compose部署:

version: '3'
services:app:image: openjdk:17-jdkcommand: java -jar /app.jarports:- "8080:8080"depends_on:- redisenvironment:- SPRING_PROFILES_ACTIVE=prodredis:image: redis:6-alpineports:- "6379:6379"volumes:- redis_data:/datavolumes:redis_data:

该方案已在生产环境稳定运行,支持日均10万+用户访问,可根据实际业务需求调整JWT有效期和Shiro缓存策略。

九.推荐项目

上述权限认证方式均可添加至一下推荐项目中:

  • 基于SSM+Vue+shiro前后端分离的电影购票管理系统
  • 基于SpringBoot+Vue的房屋租赁管理系统
  • 基于SSM+Vue前后端分离的在线考试系统
  • 基于Springboot的校园二手交易平台项目
  • 基于springboot+vue3前后端分离的高校宿舍管理系统
    标签: #毕业设计 #SSM #Vue #在线考试系统 #JavaWeb #前后端分离

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/83414.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PCIe数据采集系统详解

PCIe数据采集系统详解 在上篇文章中&#xff0c;废了老大劲儿我们写出了PCIe数据采集系统&#xff1b;其中各个模块各司其职&#xff0c;相互配合。完成了从数据采集到高速存储到DDR3的全过程。今天我们呢就来详细讲解他们之间的关系&#xff1f;以及各个模块的关键点&#xff…

2025云智算技术白皮书

1. 云智算的演进背景 传统云计算面临三大挑战&#xff1a; 算力需求激增&#xff1a;AI大模型训练需十万卡级GPU集群&#xff0c;资源调度能力不足。网络性能瓶颈&#xff1a;TB级参数同步对低时延、高吞吐要求远超传统网络架构。服务形态单一&#xff1a;IaaS/PaaS无法覆盖A…

C语言编程中的时间处理

最简单的time 在C语言编程中&#xff0c;处理时间最简单的函数就是time了。它的原型为&#xff1a; #include <time.h> time_t time(time_t *_Nullable tloc);返回自从EPOCH&#xff0c;即1970年1月1日的零点零时零分&#xff0c;到当前的秒数。 输入参数可以是NULL。…

适应性神经树:当深度学习遇上决策树的“生长法则”

1st author: Ryutaro Tanno video: Video from London ML meetup paper: Adaptive Neural Trees ICML 2019 code: rtanno21609/AdaptiveNeuralTrees: Adaptive Neural Trees 背景 在机器学习领域&#xff0c;神经网络&#xff08;NNs&#xff09;凭借其强大的表示学习能力&…

InitVerse节点部署教程

项目介绍: InitVerse 是一个为新兴企业量身定制的自动化 Web3 SaaS 平台,只需单击几下即可快速开发和部署 DApp。在 INIChain 和 INICloud 的支持下,InitVerse 可以根据需求动态调整计算资源,实现高效的任务处理,同时提供更高的安全性、可用性和可扩展性。 系统要求: C…

阿里开源通义万相 Wan2.1-VACE,开启视频创作新时代

0.前言 阿里巴巴于2025年5月14日正式开源了其最新的AI视频生成与编辑模型——通义万相Wan2.1-VACE。这一模型是业界功能最全面的视频生成与编辑工具&#xff0c;能够同时支持多种视频生成和编辑任务&#xff0c;包括文生视频、图像参考视频生成、视频重绘、局部编辑、背景延展…

解决“VMware另一个程序已锁定文件的一部分,进程无法访问“

问题描述 打开VMware里的虚拟机时&#xff0c;弹出"另一个程序已锁定文件的一部分&#xff0c;进程无法访问"如图所示&#xff1a; 这是VM虚拟机的保护机制。虚拟机运行时&#xff0c;为防止数据被篡改&#xff0c;会将所运行的文件保护起来。当虚拟机崩溃或者强制…

基于大数据的租房信息可视化系统的设计与实现【源码+文档+部署】

课题名称 基于大数据的租房信息可视化系统的设计与实现 学 院 专 业 计算机科学与技术 学生姓名 指导教师 一、课题来源及意义 租房市场一直是社会关注的热点问题。随着城市化进程的加速&#xff0c;大量人口涌入城市&#xff0c;导致租房需求激增。传统的租…

Vue3封装公共图片组件

对图片加载做的处理: 图片加载状态响应式管理图片访问错误的处理机制图片懒加载可通过slot支持自定义加载动画其他监听事件的处理及向上传递 …<!-- components/CustomImage.vue --> <template><div class="custom-image-wrapper"><!-- 主图 -…

车道线检测----CLRKDNet

今天的最后一篇 车道线检测系列结束 CLRKDNet&#xff1a;通过知识蒸馏加速车道检测 摘要&#xff1a;道路车道是智能车辆视觉感知系统的重要组成部分&#xff0c;在安全导航中发挥着关键作用。在车道检测任务中&#xff0c;平衡精度与实时性能至关重要&#xff0c;但现有方法…

Python-感知机以及实现感知机

感知机定义 如果有一个算法&#xff0c;具有1个或者多个入参&#xff0c;但是返回值要么是0&#xff0c;要么是1&#xff0c;那么这个算法就叫做感知机&#xff0c;也就是说&#xff0c;感知机是个算法 感知机有什么用 感知机是用来表示可能性的大小的&#xff0c;我们可以认…

STM32 ADC+DMA+TIM触发采样实战:避坑指南与源码解析

知识点1【TRGO的介绍】 1、TRGO的概述 TRGO&#xff1a;Trigger Output&#xff08;触发输出&#xff09;&#xff0c;是定时器的一种功能。 它可以作为外设的启动信号&#xff0c;比如ADC转换&#xff0c;DAC输出&#xff0c;DMA请求等。 对于ADC来说&#xff0c;可以通过…

Qwen3技术报告解读

https://github.com/QwenLM/Qwen3/blob/main/Qwen3_Technical_Report.pdf 节前放模型&#xff0c;大晚上的发技术报告。通义&#xff0c;真有你的~ 文章目录 预训练后训练Long-CoT Cold StartReasoning RLThinking Mode FusionGeneral RLStrong-to-Weak Distillation 模型结构…

【网络编程】十、详解 UDP 协议

文章目录 Ⅰ. 传输层概述1、进程之间的通信2、再谈端口号端口号的引出五元组标识一个通信端口号范围划分常见的知名端口号查看知名端口号协议号 VS 端口号 3、两个问题一个端口号是否可以被多个进程绑定&#xff1f;一个进程是否可以绑定多个端口号&#xff1f; 4、部分常见指令…

实现RTSP低延迟播放器,挑战与解决方案

随着低延迟直播需求的快速增长&#xff0c;RTSP&#xff08;Real-Time Streaming Protocol&#xff09;播放器逐渐成为实时视频流传输中的核心技术之一。与WebRTC&#xff08;Web Real-Time Communication&#xff09;相比&#xff0c;RTSP在实时性和网络延迟方面面临诸多挑战&…

【springcloud学习(dalston.sr1)】Eureka单个服务端的搭建(含源代码)(三)

该系列项目整体介绍及源代码请参照前面写的一篇文章【springcloud学习(dalston.sr1)】项目整体介绍&#xff08;含源代码&#xff09;&#xff08;一&#xff09; springcloud学习&#xff08;dalston.sr1&#xff09;系统文章汇总如下&#xff1a; 【springcloud学习(dalston…

GPU与NPU异构计算任务划分算法研究:基于强化学习的Transformer负载均衡实践

点击 “AladdinEdu&#xff0c;同学们用得起的【H卡】算力平台”&#xff0c;H卡级别算力&#xff0c;按量计费&#xff0c;灵活弹性&#xff0c;顶级配置&#xff0c;学生专属优惠。 引言 在边缘计算与AI推理场景中&#xff0c;GPU-NPU异构计算架构已成为突破算力瓶颈的关键技…

探索C语言中的二叉树:原理、实现与应用

一、引言 二叉树作为一种重要的数据结构&#xff0c;在计算机科学领域有着广泛的应用&#xff0c;无论是在操作系统的文件系统管理&#xff0c;还是在数据库的索引构建中&#xff0c;都能看到它的身影。在C语言中&#xff0c;我们可以利用指针灵活地构建和操作二叉树。接下来&…

使用libUSB-win32的简单读写例程参考

USB上位机程序的编写&#xff0c;函数的调用过程. 调用 void usb_init(void); 进行初始化 调用usb_find_busses、usb_find_devices和usb_get_busses这三个函数&#xff0c;获得已找到的USB总线序列&#xff1b;然后通过链表遍历所有的USB设备&#xff0c;根据已知的要打开USB设…

vue注册用户使用v-model实现数据双向绑定

定义数据模型 Login.vue //定义数据模型 const registerData ref({username: ,password: ,confirmPassword: })使用 v-model 实现数据模型的key与注册表单中的元素之间的双向绑定 <!-- 注册表单 --><el-form ref"form" size"large" autocompl…