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.pswp.cn/diannao/83414.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/83414.shtml
英文地址,請注明出處:http://en.pswp.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…