Apache Shiro安全框架詳解:認(rèn)證、授權(quán)與加密功能

2024-12-27 14:11 更新

大家好,我是 V 哥。Apache Shiro 是一個(gè)強(qiáng)大且靈活的 Java 安全框架,專(zhuān)注于提供認(rèn)證、授權(quán)、會(huì)話(huà)管理和加密功能。它常用于保護(hù) Java 應(yīng)用的訪(fǎng)問(wèn)控制,特別是在 Web 應(yīng)用中。相比于 Spring Security,Shiro 的設(shè)計(jì)更簡(jiǎn)潔,適合輕量級(jí)應(yīng)用,并且在許多方面具有更好的易用性和擴(kuò)展性,今天 V 哥就來(lái)聊聊 Shiro 安全框架。

Shiro 的核心概念

按照慣例,和 V 哥一起來(lái)了解一下 Shiro 的核心概念:

  1. Subject
    Subject 是 Shiro 框架中一個(gè)核心的接口,表示應(yīng)用中的“用戶(hù)”或“實(shí)體”,用于交互和存儲(chǔ)認(rèn)證狀態(tài)。通常通過(guò) SecurityUtils.getSubject() 獲取當(dāng)前的 Subject。它代表了用戶(hù)的身份信息和權(quán)限數(shù)據(jù)。

  1. SecurityManager
    SecurityManager 是 Shiro 的核心控制器,負(fù)責(zé)管理所有的安全操作和認(rèn)證。通過(guò)配置 SecurityManager,可以控制用戶(hù)的認(rèn)證、授權(quán)、會(huì)話(huà)等管理。

  1. Realm
    Realm 是 Shiro 從數(shù)據(jù)源獲取用戶(hù)、角色和權(quán)限信息的途徑。通過(guò)實(shí)現(xiàn)自定義的 Realm,可以將 Shiro 與數(shù)據(jù)庫(kù)、LDAP、文件等數(shù)據(jù)源整合。Shiro 會(huì)把用戶(hù)的認(rèn)證和授權(quán)數(shù)據(jù)從 Realm 中獲取。

  1. Session
    Shiro 自帶會(huì)話(huà)管理,不依賴(lài)于 Servlet 容器提供的會(huì)話(huà)。即使在非 Web 環(huán)境下,也可以使用 Shiro 的會(huì)話(huà)管理。Shiro 的會(huì)話(huà)管理提供了更細(xì)致的控制,比如會(huì)話(huà)超時(shí)、存儲(chǔ)和共享等功能。

  1. Authentication(認(rèn)證)
    認(rèn)證是指驗(yàn)證用戶(hù)身份的過(guò)程。Shiro 提供了簡(jiǎn)單的 API 來(lái)實(shí)現(xiàn)認(rèn)證過(guò)程,比如 subject.login(token)。在實(shí)際應(yīng)用中,通常通過(guò)用戶(hù)名和密碼的組合進(jìn)行認(rèn)證,但 Shiro 也支持其他方式(如 OAuth2、JWT 等)。

  1. Authorization(授權(quán))
    授權(quán)是指驗(yàn)證用戶(hù)是否具備某些權(quán)限或角色的過(guò)程。Shiro 支持基于角色和基于權(quán)限的授權(quán),允許更精細(xì)的權(quán)限控制。通過(guò) subject.hasRolesubject.isPermitted 方法,開(kāi)發(fā)者可以檢查用戶(hù)的角色和權(quán)限。

  1. Cryptography(加密)
    Shiro 內(nèi)置了加密功能,提供對(duì)密碼和敏感信息的加密和解密支持。它支持多種加密算法,并且在密碼存儲(chǔ)時(shí)支持散列和鹽值。

Shiro 的主要功能和優(yōu)勢(shì)

V 哥總結(jié)幾點(diǎn)Shiro 的主要功能和優(yōu)勢(shì),這個(gè)在面試時(shí)吹牛逼用得到。

  1. 易于集成
    Shiro 的 API 設(shè)計(jì)簡(jiǎn)單,易于集成到各種 Java 應(yīng)用中。開(kāi)發(fā)者可以基于 Shiro 提供的默認(rèn)實(shí)現(xiàn)快速搭建一個(gè)基本的安全架構(gòu),也可以根據(jù)需要自定義各種功能。

  1. 獨(dú)立的會(huì)話(huà)管理
    與基于 Web 容器的會(huì)話(huà)管理不同,Shiro 提供了跨環(huán)境的會(huì)話(huà)管理,可以應(yīng)用于 Web 和非 Web 的環(huán)境,增加了應(yīng)用的靈活性。

  1. 權(quán)限控制簡(jiǎn)單而靈活
    Shiro 的權(quán)限管理可以通過(guò)配置文件、注解或代碼實(shí)現(xiàn),提供了細(xì)粒度的訪(fǎng)問(wèn)控制。通過(guò)權(quán)限和角色的組合,開(kāi)發(fā)者可以非常靈活地控制訪(fǎng)問(wèn)權(quán)限。

  1. 支持多種數(shù)據(jù)源
    Shiro 可以從多種數(shù)據(jù)源(如數(shù)據(jù)庫(kù)、LDAP、文件等)獲取用戶(hù)和權(quán)限信息,方便與各種現(xiàn)有系統(tǒng)整合。

  1. 支持 Web 和非 Web 環(huán)境
    Shiro 不僅可以在 Web 應(yīng)用中使用,也支持在桌面應(yīng)用或微服務(wù)等環(huán)境中使用。

Shiro 的基本使用示例

光講概念不是 V 哥風(fēng)格,接下來(lái),通過(guò)一個(gè)典型的 Shiro 應(yīng)用來(lái)了解一下如何使用,包含配置 SecurityManager、配置 Realm、進(jìn)行認(rèn)證和授權(quán)等步驟。

  1. 配置 Shiro 環(huán)境 可以通過(guò) shiro.ini 文件配置 Shiro,也可以通過(guò)代碼進(jìn)行配置。

  1. [main]
  2. # 配置 SecurityManager
  3. securityManager = org.apache.shiro.mgt.DefaultSecurityManager
  4. # 配置 Realm
  5. myRealm = com.wg.MyCustomRealm
  6. securityManager.realms = $myRealm

  1. 創(chuàng)建自定義 Realm

自定義 Realm 通過(guò)繼承 AuthorizingRealm 并實(shí)現(xiàn) doGetAuthenticationInfodoGetAuthorizationInfo 方法來(lái)提供用戶(hù)和權(quán)限數(shù)據(jù)。

  1. public class MyCustomRealm extends AuthorizingRealm {
  2. @Override
  3. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  4. // 獲取用戶(hù)名和密碼等信息,查詢(xún)數(shù)據(jù)庫(kù)進(jìn)行認(rèn)證
  5. return new SimpleAuthenticationInfo(username, password, getName());
  6. }
  7. @Override
  8. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  9. // 獲取用戶(hù)角色和權(quán)限信息
  10. SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
  11. info.addRole("admin");
  12. info.addStringPermission("user:read");
  13. return info;
  14. }
  15. }

  1. 使用 Shiro 進(jìn)行認(rèn)證和授權(quán)

  1. Subject currentUser = SecurityUtils.getSubject();
  2. if (!currentUser.isAuthenticated()) {
  3. UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
  4. try {
  5. currentUser.login(token);
  6. System.out.println("認(rèn)證成功");
  7. } catch (AuthenticationException ae) {
  8. System.out.println("認(rèn)證失敗");
  9. }
  10. }
  11. // 檢查權(quán)限
  12. if (currentUser.hasRole("admin")) {
  13. //用輸出模擬一下哈
  14. System.out.println("用戶(hù)擁有 admin 角色");
  15. }
  16. if (currentUser.isPermitted("user:read")) {
  17. //用輸出模擬一下哈
  18. System.out.println("用戶(hù)具有 user:read 權(quán)限");
  19. }

通過(guò)這個(gè)簡(jiǎn)單的案例學(xué)習(xí),咱們可以了解 Shiro 的基本使用,但這不是全部,聽(tīng)V哥繼續(xù)慢慢道來(lái)。

場(chǎng)景案例

這點(diǎn)很重要,強(qiáng)調(diào)一下哈,Shiro 適合需要簡(jiǎn)潔易用、安全控制要求靈活的 Java 應(yīng)用,如中小型 Web 應(yīng)用、桌面應(yīng)用、分布式微服務(wù)等。對(duì)于大型企業(yè)應(yīng)用或需要集成多種認(rèn)證方式(如 OAuth2、JWT 等)的項(xiàng)目,Spring Security 可能會(huì)更合適。

要在微服務(wù)架構(gòu)中實(shí)現(xiàn)基于 Apache Shiro 的安全認(rèn)證和授權(quán),比如一個(gè)訂單管理系統(tǒng)為例。這個(gè)系統(tǒng)包含兩個(gè)主要服務(wù):

  1. 用戶(hù)服務(wù):負(fù)責(zé)用戶(hù)的注冊(cè)、登錄、認(rèn)證等操作。
  2. 訂單服務(wù):允許用戶(hù)創(chuàng)建、查看、刪除訂單,并限制訪(fǎng)問(wèn)權(quán)限。

咱們來(lái)看一下,這個(gè)應(yīng)該怎么設(shè)計(jì)呢?

微服務(wù)案例設(shè)計(jì)

在這個(gè)場(chǎng)景中,我們需要以下幾項(xiàng)核心功能:

  1. 用戶(hù)認(rèn)證:用戶(hù)通過(guò)用戶(hù)名和密碼登錄。
  2. 權(quán)限控制:僅管理員能刪除訂單,普通用戶(hù)只能查看和創(chuàng)建訂單。
  3. Token機(jī)制:使用 JWT Token(JSON Web Token)來(lái)管理用戶(hù)的登錄狀態(tài),實(shí)現(xiàn)無(wú)狀態(tài)認(rèn)證,使得在分布式環(huán)境下不依賴(lài)于單一會(huì)話(huà)。
  4. 跨服務(wù)認(rèn)證:訂單服務(wù)在接收到請(qǐng)求時(shí),檢查并驗(yàn)證用戶(hù)的身份和權(quán)限。

架構(gòu)和技術(shù)選型

  • Spring Boot:用于快速搭建微服務(wù)。
  • Shiro:實(shí)現(xiàn)認(rèn)證、授權(quán)。
  • JWT:生成和驗(yàn)證 Token,保持無(wú)狀態(tài)認(rèn)證。
  • Spring Data JPA:訪(fǎng)問(wèn)數(shù)據(jù)庫(kù)存儲(chǔ)用戶(hù)和訂單數(shù)據(jù)。

系統(tǒng)結(jié)構(gòu)

  1. +------------------+ +---------------------+
  2. | 用戶(hù)服務(wù) | | 訂單服務(wù) |
  3. | | | |
  4. | 用戶(hù)注冊(cè)、登錄 | | 查看、創(chuàng)建、刪除訂單|
  5. +------------------+ +---------------------+
  6. | |
  7. |----用戶(hù) Token ------|

步驟:實(shí)現(xiàn)微服務(wù)中的認(rèn)證和授權(quán)

1. 引入必要的依賴(lài)

pom.xml 文件中,添加 Shiro、JWT 和 Spring Data JPA 等依賴(lài):

  1. <dependency>
  2. <groupId>org.apache.shiro</groupId>
  3. <artifactId>shiro-spring</artifactId>
  4. <version>1.8.0</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>io.jsonwebtoken</groupId>
  8. <artifactId>jjwt</artifactId>
  9. <version>0.9.1</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>org.springframework.boot</groupId>
  13. <artifactId>spring-boot-starter-data-jpa</artifactId>
  14. </dependency>
  15. <dependency>
  16. <groupId>org.springframework.boot</groupId>
  17. <artifactId>spring-boot-starter-web</artifactId>
  18. </dependency>
  19. <dependency>
  20. <groupId>com.h2database</groupId>
  21. <artifactId>h2</artifactId>
  22. </dependency>

2. 配置 Shiro 與 JWT 過(guò)濾器

使用 Shiro 的自定義 JWT 過(guò)濾器實(shí)現(xiàn)無(wú)狀態(tài)認(rèn)證,通過(guò) Token 驗(yàn)證用戶(hù)。

  1. public class JwtFilter extends BasicHttpAuthenticationFilter {
  2. @Override
  3. protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
  4. HttpServletRequest httpServletRequest = (HttpServletRequest) request;
  5. String token = httpServletRequest.getHeader("Authorization");
  6. if (StringUtils.isBlank(token)) {
  7. return false;
  8. }
  9. try {
  10. // 解析 JWT token
  11. JwtToken jwtToken = new JwtToken(token);
  12. getSubject(request, response).login(jwtToken);
  13. return true;
  14. } catch (Exception e) {
  15. return false;
  16. }
  17. }
  18. }

3. 實(shí)現(xiàn)自定義 Realm

自定義 Realm,從數(shù)據(jù)庫(kù)獲取用戶(hù)和角色信息,并使用 JWT Token 進(jìn)行無(wú)狀態(tài)認(rèn)證。

  1. public class JwtRealm extends AuthorizingRealm {
  2. @Override
  3. public boolean supports(AuthenticationToken token) {
  4. return token instanceof JwtToken;
  5. }
  6. @Override
  7. protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
  8. String jwtToken = (String) token.getPrincipal();
  9. // 驗(yàn)證 Token
  10. String username = JwtUtil.getUsernameFromToken(jwtToken);
  11. if (username == null) {
  12. throw new AuthenticationException("Token 無(wú)效");
  13. }
  14. // 查詢(xún)用戶(hù)
  15. User user = userService.findByUsername(username);
  16. if (user == null) {
  17. throw new AuthenticationException("用戶(hù)不存在");
  18. }
  19. return new SimpleAuthenticationInfo(jwtToken, jwtToken, getName());
  20. }
  21. @Override
  22. protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
  23. String username = JwtUtil.getUsernameFromToken(principals.toString());
  24. SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
  25. User user = userService.findByUsername(username);
  26. // 添加角色和權(quán)限
  27. authorizationInfo.addRole(user.getRole());
  28. authorizationInfo.addStringPermission(user.getPermission());
  29. return authorizationInfo;
  30. }
  31. }

4. 創(chuàng)建 JWT 工具類(lèi)

編寫(xiě)一個(gè)工具類(lèi),用于生成和解析 JWT Token。

  1. public class JwtUtil {
  2. //這里替換一下你自己的secret_key
  3. private static final String SECRET_KEY = "這里打碼了";
  4. public static String generateToken(String username) {
  5. return Jwts.builder()
  6. .setSubject(username)
  7. .setIssuedAt(new Date())
  8. .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
  9. .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
  10. .compact();
  11. }
  12. public static String getUsernameFromToken(String token) {
  13. Claims claims = Jwts.parser()
  14. .setSigningKey(SECRET_KEY)
  15. .parseClaimsJws(token)
  16. .getBody();
  17. return claims.getSubject();
  18. }
  19. public static boolean isTokenExpired(String token) {
  20. Claims claims = Jwts.parser()
  21. .setSigningKey(SECRET_KEY)
  22. .parseClaimsJws(token)
  23. .getBody();
  24. return claims.getExpiration().before(new Date());
  25. }
  26. }

5. 編寫(xiě)用戶(hù)服務(wù)和訂單服務(wù)接口

用戶(hù)服務(wù)接口

用戶(hù)服務(wù)提供注冊(cè)和登錄 API。

  1. @RestController
  2. @RequestMapping("/user")
  3. public class UserController {
  4. @PostMapping("/register")
  5. public ResponseEntity<?> register(@RequestBody User user) {
  6. userService.save(user);
  7. return ResponseEntity.ok("用戶(hù)注冊(cè)成功");
  8. }
  9. @PostMapping("/login")
  10. public ResponseEntity<?> login(@RequestBody User user) {
  11. User dbUser = userService.findByUsername(user.getUsername());
  12. if (dbUser != null && dbUser.getPassword().equals(user.getPassword())) {
  13. String token = JwtUtil.generateToken(user.getUsername());
  14. return ResponseEntity.ok(token);
  15. }
  16. return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("登錄失敗");
  17. }
  18. }

訂單服務(wù)接口

訂單服務(wù)在操作訂單時(shí)會(huì)驗(yàn)證用戶(hù)的角色和權(quán)限。

  1. @RestController
  2. @RequestMapping("/order")
  3. public class OrderController {
  4. @GetMapping("/{orderId}")
  5. public ResponseEntity<?> getOrder(@PathVariable Long orderId) {
  6. Subject currentUser = SecurityUtils.getSubject();
  7. if (currentUser.isPermitted("order:read")) {
  8. // 查詢(xún)訂單
  9. return ResponseEntity.ok("訂單詳情");
  10. }
  11. return ResponseEntity.status(HttpStatus.FORBIDDEN).body("無(wú)權(quán)限查看訂單");
  12. }
  13. @DeleteMapping("/{orderId}")
  14. public ResponseEntity<?> deleteOrder(@PathVariable Long orderId) {
  15. Subject currentUser = SecurityUtils.getSubject();
  16. if (currentUser.hasRole("admin")) {
  17. // 刪除訂單
  18. return ResponseEntity.ok("訂單已刪除");
  19. }
  20. return ResponseEntity.status(HttpStatus.FORBIDDEN).body("無(wú)權(quán)限刪除訂單");
  21. }
  22. }

最后

這個(gè)案例中咱們通過(guò)如何使用 Shiro、JWT 和 Spring Boot 來(lái)構(gòu)建一個(gè)無(wú)狀態(tài)的微服務(wù)認(rèn)證授權(quán)機(jī)制。通過(guò) Shiro 實(shí)現(xiàn)用戶(hù)認(rèn)證和權(quán)限控制,使用 JWT 實(shí)現(xiàn)無(wú)狀態(tài) Token 驗(yàn)證。在輕量級(jí)的分布式微服務(wù)應(yīng)用中,是不是使用 Shiro 感覺(jué)更加清爽呢,歡迎評(píng)論區(qū)一起討論,關(guān)注威哥愛(ài)編程,愛(ài)上Java,一輩子。

以上內(nèi)容是否對(duì)您有幫助:
在線(xiàn)筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)