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)行配置。

   [main]
   # 配置 SecurityManager
   securityManager = org.apache.shiro.mgt.DefaultSecurityManager


   # 配置 Realm
   myRealm = com.wg.MyCustomRealm
   securityManager.realms = $myRealm

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

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

   public class MyCustomRealm extends AuthorizingRealm {
       @Override
       protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
           // 獲取用戶(hù)名和密碼等信息,查詢(xún)數(shù)據(jù)庫(kù)進(jìn)行認(rèn)證
           return new SimpleAuthenticationInfo(username, password, getName());
       }


       @Override
       protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
           // 獲取用戶(hù)角色和權(quán)限信息
           SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
           info.addRole("admin");
           info.addStringPermission("user:read");
           return info;
       }
   }

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

   Subject currentUser = SecurityUtils.getSubject();
   if (!currentUser.isAuthenticated()) {
       UsernamePasswordToken token = new UsernamePasswordToken("username", "password");
       try {
           currentUser.login(token);
           System.out.println("認(rèn)證成功");
       } catch (AuthenticationException ae) {
           System.out.println("認(rèn)證失敗");
       }
   }


   // 檢查權(quán)限
   if (currentUser.hasRole("admin")) {
       //用輸出模擬一下哈
       System.out.println("用戶(hù)擁有 admin 角色");
   }
   if (currentUser.isPermitted("user:read")) {
       //用輸出模擬一下哈
       System.out.println("用戶(hù)具有 user:read 權(quán)限");
   }

通過(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)

+------------------+      +---------------------+
|   用戶(hù)服務(wù)        |      |     訂單服務(wù)        |
|                  |      |                     |
| 用戶(hù)注冊(cè)、登錄    |      |   查看、創(chuàng)建、刪除訂單|
+------------------+      +---------------------+
            |                    |
            |----用戶(hù) Token ------|

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

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

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

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.8.0</version>
</dependency>


<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
</dependency>

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

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

public class JwtFilter extends BasicHttpAuthenticationFilter {


    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String token = httpServletRequest.getHeader("Authorization");


        if (StringUtils.isBlank(token)) {
            return false;
        }


        try {
            // 解析 JWT token
            JwtToken jwtToken = new JwtToken(token);
            getSubject(request, response).login(jwtToken);
            return true;
        } catch (Exception e) {
            return false;
        }
    }
}

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

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

public class JwtRealm extends AuthorizingRealm {


    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }


    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        String jwtToken = (String) token.getPrincipal();


        // 驗(yàn)證 Token
        String username = JwtUtil.getUsernameFromToken(jwtToken);
        if (username == null) {
            throw new AuthenticationException("Token 無(wú)效");
        }


        // 查詢(xún)用戶(hù)
        User user = userService.findByUsername(username);
        if (user == null) {
            throw new AuthenticationException("用戶(hù)不存在");
        }


        return new SimpleAuthenticationInfo(jwtToken, jwtToken, getName());
    }


    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        String username = JwtUtil.getUsernameFromToken(principals.toString());


        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        User user = userService.findByUsername(username);


        // 添加角色和權(quán)限
        authorizationInfo.addRole(user.getRole());
        authorizationInfo.addStringPermission(user.getPermission());


        return authorizationInfo;
    }
}

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

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

public class JwtUtil {


    //這里替換一下你自己的secret_key
    private static final String SECRET_KEY = "這里打碼了"; 


    public static String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis() + 3600000)) // 1 hour
                .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                .compact();
    }


    public static String getUsernameFromToken(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return claims.getSubject();
    }


    public static boolean isTokenExpired(String token) {
        Claims claims = Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
        return claims.getExpiration().before(new Date());
    }
}

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

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

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

@RestController
@RequestMapping("/user")
public class UserController {


    @PostMapping("/register")
    public ResponseEntity<?> register(@RequestBody User user) {
        userService.save(user);
        return ResponseEntity.ok("用戶(hù)注冊(cè)成功");
    }


    @PostMapping("/login")
    public ResponseEntity<?> login(@RequestBody User user) {
        User dbUser = userService.findByUsername(user.getUsername());
        if (dbUser != null && dbUser.getPassword().equals(user.getPassword())) {
            String token = JwtUtil.generateToken(user.getUsername());
            return ResponseEntity.ok(token);
        }
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("登錄失敗");
    }
}

訂單服務(wù)接口

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

@RestController
@RequestMapping("/order")
public class OrderController {


    @GetMapping("/{orderId}")
    public ResponseEntity<?> getOrder(@PathVariable Long orderId) {
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.isPermitted("order:read")) {
            // 查詢(xún)訂單
            return ResponseEntity.ok("訂單詳情");
        }
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("無(wú)權(quán)限查看訂單");
    }


    @DeleteMapping("/{orderId}")
    public ResponseEntity<?> deleteOrder(@PathVariable Long orderId) {
        Subject currentUser = SecurityUtils.getSubject();
        if (currentUser.hasRole("admin")) {
            // 刪除訂單
            return ResponseEntity.ok("訂單已刪除");
        }
        return ResponseEntity.status(HttpStatus.FORBIDDEN).body("無(wú)權(quán)限刪除訂單");
    }
}

最后

這個(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)